# Project export: Gita: AI-Powered Video-to-Music Generation Platform

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: It captures the emotional transformation from "bland to beautiful" while highlighting the speed and AI technology, making it both personal and impressive.
- Devpost: https://devpost.com/software/gita-ai-powered-video-to-music-generation-platform
- GitHub: https://github.com/erik-ksth/gita.git
- Team: 3 GitHub contributor(s) — erik-ksth (18 commits), aungbobo (5 commits), jav4Aung (5 commits)

## Devpost submission (written by the team)

### Inspiration

You finished your video. It looks amazing. You just need to find the right music. But that’s the hard part. You spend hours searching for a good song that won’t get your video taken down by a copyright claim. It’s a huge headache, and it kills your creative momentum. We think that’s backward. Finding music should be inspiring, not frustrating. That’s why we’re making Gita. What if you could create your own unique, copyright-safe music that matches the vibe of your video, just by uploading it?

### What it does

Finding the right music for your video is a huge pain. It takes forever, and the good stuff either costs a lot or comes with scary copyright rules. We built Gita to fix that. It’s simple. You give Gita your finished video, and it creates a brand new soundtrack that matches the energy and emotion of your footage. The music is 100% yours to use, and of course, copyright-free. No more searching. No more fees. Just your video, with the perfect background music. Tech stack React: Powers the user-friendly website where people upload their videos and interact with Gita. Python: Serves as the core programming language for our backend, connecting all the different services and agents together. FastAPI: Creates the high-speed API that allows our React frontend to communicate with the Python backend and AI agents. Google ADK: Provides the framework for building and orchestrating our team of specialized AI agents that analyze the video and create the music. Gemini (Lyria AI): Acts as the creative AI "brain" that actually generates unique, instrumental background music based on the text prompts our agents create. Groq: A specialized AI hardware platform we use to generate detailed prompt for Lyria AI to generate music. AI agents Video Processor: The initial agent that analyzes the uploaded video to extract key visual and audio information for the AI to understand. Prompt Generator: An AI agent that takes the video analysis and creatively writes the detailed text instruction needed for the music AI. Prompt Checker: A validation step or agent that ensures the generated text prompt is clear, safe, and well-structured before sending it to the music AI. Music Generator: The core creative AI, Lyria, that reads the final prompt and composes a unique, instrumental piece of background music. Orchestrator: The master agent that manages the entire workflow, telling each component when to start and passing the data between them from start to finish. Next Steps More Customization: We will give users more creative control by letting them select genres, choose specific instruments, or adjust the tempo of their generated music. More Fine-Tuning: We will train our AI agents on more specific examples to make them better at understanding the nuances of a video and creating more accurate, emotionally resonant music.

## README (from the GitHub repository)

# 🎵 Gita - Copyright-Free Music Generator

A simple app to help content creators find copyright-free background music for their videos.

## 🚀 Quick Start

### Option 1: Automated Setup (Recommended)

```bash
git clone <your-repo-url>
cd gita
./setup.sh
```

### Option 2: Manual Setup

#### Frontend (React)

```bash
cd frontend
npm install
npm start
```

#### Backend (Python)

```bash
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python api/search-music.py
```

## 🔑 Environment Variables Setup

**Important for Judges:** You need to create a `.env` file in the `backend/` directory with the following API keys to run the application:

### Required Environment Variables

Create a file named `.env` in the `backend/` directory with these variables:

```bash
# Supabase Configuration (Required)
SUPABASE_URL="your_supabase_project_url"
SUPABASE_ANON_KEY="your_supabase_anon_key"

# Groq API Key (Required for AI vision analysis)
GROQ_API_KEY="your_groq_api_key"

# Google Cloud Configuration (Required for Lyria music generation)
PROJECT_ID="your_google_cloud_project_id"
GOOGLE_CLOUD_PROJECT="your_google_cloud_project_id"
GOOGLE_CLOUD_LOCATION="us-central1"
GOOGLE_GENAI_USE_VERTEXAI="True"

# Gemini API Key (Alternative AI model)
GEMINI_API_KEY="your_gemini_api_key"

# CORS Configuration (Optional - defaults to http://localhost:3000)
CORS_ORIGINS=http://localhost:3000
```

### Frontend Environment Variables

Create a file named `.env` in the `frontend/` directory:

```bash
# API URL for connecting to backend
REACT_APP_API_URL=http://localhost:8000
```

### How to Get the Required API Keys

1. **Supabase Setup:**

   - Go to [supabase.com](https://supabase.com) and create a new project
   - Navigate to Settings → API
   - Copy the "Project URL" and "anon public" key

2. **Groq API Key:**

   - Visit [console.groq.com](https://console.groq.com)
   - Sign up and create an API key
   - Copy the API key

3. **Google Cloud Project ID:**
   - Go to [Google Cloud Console](https://console.cloud.google.com)
   - Create a new project or select existing one
   - Enable the Vertex AI API
   - Set up authentication (run `gcloud auth application-default login`)
   - Copy your Project ID

### Database Setup

You'll also need to create the following tables in your Supabase database:

```sql
-- Videos table
CREATE TABLE videos (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  filename TEXT NOT NULL,
  original_filename TEXT,
  file_path TEXT NOT NULL,
  file_size_mb FLOAT,
  duration_seconds FLOAT,
  fps FLOAT,
  resolution TEXT,
  frame_count INTEGER,
  trim_start FLOAT,
  trim_end FLOAT,
  trim_duration FLOAT,
  processing_status TEXT DEFAULT 'uploaded',
  frames_extracted BOOLEAN DEFAULT FALSE,
  vision_analysis TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Music generations table
CREATE TABLE music_generations (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  video_id UUID REFERENCES videos(id),
  vision_prompt TEXT,
  music_prompt TEXT,
  music_file_path TEXT,
  music_file_size_mb FLOAT,
  generation_status TEXT DEFAULT 'pending',
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```

### Storage Buckets

Create these storage buckets in Supabase:

- `videos` - for uploaded video files
- `frames` - for extracted video frames
- `music` - for generated music files
- `final-videos` - for final videos with music

## 🌐 Access the App

- **Frontend:** http://localhost:3000
- **Backend API:** http://localhost:8000

### Local Development

1. Clone the repository
2. Run `./setup.sh` for automated setup
3. Start backend: `cd backend && source venv/bin/activate && python server.py`
4. Start frontend: `cd frontend && npm start`
5. Open http://localhost:3000


## Detected evidence (automated analysis)

Indexed codebase: 23 recognized source files, 197 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Supabase (technology) — detected in the code

## Codebase structure (from repository index)

### Files (30 of 30)

```
.gitignore
backend/.gitignore
backend/agents_test.py
backend/agents/__init__.py
backend/agents/music_generator_agent.py
backend/agents/orchestrator_agent.py
backend/agents/prompt_checker_agent.py
backend/agents/prompt_generator_agent.py
backend/agents/video_processor_agent.py
backend/api/index.py
backend/README.md
backend/requirements.txt
backend/runtime.txt
backend/server.py
backend/supabase_config.py
backend/vercel.json
frontend/package.json
frontend/public/index.html
frontend/src/App.css
frontend/src/App.js
frontend/src/components/About.css
frontend/src/components/About.js
frontend/src/components/Contact.css
frontend/src/components/Contact.js
frontend/src/components/VideoTrimModal.js
frontend/src/index.css
frontend/src/index.js
frontend/vercel.json
README.md
setup.sh
```

### Dependencies

- backend/requirements.txt: deprecated@==1.2.14, fastapi@==0.115.13, google-adk@==1.0.0, moviepy@==1.0.3, opencv-python-headless@==4.11.0.86, Pillow@==11.2.1, pydantic@==2.11.7, python-dotenv@==1.1.0, python-multipart@==0.0.20, requests@==2.31.0, supabase@==2.15.3, uvicorn[standard]@==0.34.3
- frontend/package.json: @ffmpeg/ffmpeg@^0.12.15, @ffmpeg/util@^0.12.2, react@^18.2.0, react-dom@^18.2.0, react-scripts@5.0.1

### Recent commits (newest first)

- update: readme
- update: readme
- updat: readMe
- update: README
- Update README.md
- update: agents functionalities
- fix: reduce file size
- fix: final video not showing
- feat: show the result
- fix: change UI Color
- feat: agents connection
- fix: get groq analysis into supabase
- fix: video upload to supabase:
- feat: agents implementation
- fix: update contact page
- Merge branch 'feature/ui-improvements'
- fix: add deprecated in requirement
- feat: add genre selection to upload page
- feat: railway prepare
- Merge pull request #1 from erik-ksth/feature/ui-improvements

## Key source files (fetched from GitHub, selected and truncated for size)

### backend/requirements.txt

```
fastapi==0.115.13
uvicorn[standard]==0.34.3
pydantic==2.11.7
python-dotenv==1.1.0
google-adk==1.0.0
python-multipart==0.0.20
opencv-python-headless==4.11.0.86
Pillow==11.2.1
supabase==2.15.3
moviepy==1.0.3
requests==2.31.0
deprecated==1.2.14
```

### frontend/package.json

```
{
  "name": "gita-frontend",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "@ffmpeg/ffmpeg": "^0.12.15",
    "@ffmpeg/util": "^0.12.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

```

### backend/server.py

```python
from fastapi import FastAPI, HTTPException, File, UploadFile, Form
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import uvicorn
import os
import uuid
from typing import Optional, List
from agents import run_video_to_music_workflow
from agents.video_processor_agent import (
    extract_frames,
    get_video_info,
    combine_video_with_audio_from_supabase,
)
from agents.prompt_generator_agent import analyze_video_frames_from_supabase
from agents.prompt_checker_agent import validate_and_fix_prompt
from agents.music_generator_agent import generate_music_from_video_id
from supabase_config import supabase, STORAGE_BUCKETS

app = FastAPI(title="Gita API", description="AI Music Generation API")

# Set up CORS using an environment variable
# For local dev, default to allowing http://localhost:3000 (standard React port)
CORS_ORIGINS = os.getenv("CORS_ORIGINS", "http://localhost:3000")
origins = [origin.strip() for origin in CORS_ORIGINS.split(",")]

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],  # Allows all methods
    allow_headers=["*"],  # Allows all headers
)

# Create temporary uploads directory for video processing (files are cleaned up after processing)
os.makedirs("uploads", exist_ok=True)


# Pydantic models for request/response
class GenerateMusicRequest(BaseModel):
    video_id: str  # UUID of the video stored in Supabase
    vision_prompt: str
    music_prompt: str


class GenerateMusicResponse(BaseModel):
    final_video_path: str


class HealthResponse(BaseModel):
    status: str
    message: str


class VideoUploadResponse(BaseModel):
    message: str
    filename: str
    file_path: str
    video_id: str
    trim_info: dict
    video_info: dict
    extracted_frames: List[str]
    music_generated: bool
    music_url: Optional[str] = None
    final_video_created: bool
    final_video_url: Optional[str] = None


class VideoListResponse(BaseModel):
    videos: List[dict]


class MusicGenerationListResponse(BaseModel):
    music_generations: List[dict]


def upload_video_to_supabase(video_data: bytes, filename: str) -> str:
    """
    Upload video to Supabase storage.

    Args:
        video_data: The video file data as bytes
        filename: The filename for the video

    Returns:
        The public URL of the uploaded video
    """
    try:
        # Upload video to Supabase storage
        result = supabase.storage.from_(STORAGE_BUCKETS["videos"]).upload(
            filename, video_data, {"content-type": "video/mp4"}
        )

        if result:
            # Get public URL
            public_url = supabase.storage.from_(
                STORAGE_BUCKETS["videos"]
            ).get_public_url(filename)
            print(f"Video uploaded to Supabase: {filename}")
            return public_url
        else:
            raise Exception("Failed to upload video to Supabase")

    except Exception as e:
        print(f"Error uploading video to Supabase: {e}")
        raise


def save_video_to_database(
    filename: str,
    original_filename: str,
    file_url: str,
    video_info: dict,
    trim_info: dict,
) -> str:
    """
    Save video metadata to Supabase database.

    Args:
        filename: The video filename
        original_filename: The original filename before processing
        file_url: The Supabase storage URL
        video_info: Dictionary containing video metadata
        trim_info: Dictionary containing trim information

    Returns:
        The video UUID
    """
    try:
        result = (
            supabase.table("videos")
            .insert(
                {
                    "filename": filename,
                    "original_filename": original_filename,
                    "file_path": file_url,
                    "file_size_mb": video_info.get("file_size_mb"),
                    "duration_seconds": video_info.get("duration_seconds"),
                    "fps": video_info.get("fps"),
                    "resolution": video_info.get("resolution"),
                    "frame_count": video_info.get("frame_count"),
                    "trim_start": trim_info.get("trimStart"),
                    "trim_end": trim_info.get("trimEnd"),
                    "trim_duration": trim_info.get("duration"),
                    "processing_status": "uploaded",
                    "frames_extracted": False,
                }
            )
            .execute()
        )

        if result.data:
            video_id = result.data[0]["id"]
            print(f"Video metadata saved to database: {filename} (ID: {video_id})")
            return video_id
        else:
            raise Exception("Failed to save video to database")

    except Exception as e:
        print(f"Error saving video to database: {e}")
        raise


def update_video_frames_extracted(video_id: str):
    """
    Update the video record to indicate frames have been extracted.

    Args:
        video_id: The UUID of the video
    """
    try:
        result = (
            supabase.table("videos")
            .update({"frames_extracted": True, "processing_status": "processed"})
            .eq("id", video_id)
            .execute()
        )

        if result.data:
            print(f"Updated video {video_id} - frames extracted")
        else:
            print(f"Warning: Could not update video {video_id}")

    except Exception as e:
        print(f"Error updating video frames status: {e}")


def save_vision_analysis_to_database(video_id: str, analysis_result: str) -> str:
    """
    Save vision analysis result to Supabase database.

    Args:
        video_id: The UUID of the video
        analysis_result: The generated music prompt from vision analysis

    Returns:
        Success message
    """
    try:
        result = (
            supabase.table("videos")
            .update(
                {
                    "vision_analysis": analysis_result,
        
[truncated — 14610 more characters]
```

### frontend/src/index.js

```javascript
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

```

### backend/api/index.py

```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import sys
import os

# Add the parent directory to the Python path so we can import our modules
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Import the app from server.py
from server import app

# Add CORS middleware for Vercel deployment
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Export the app for Vercel
handler = app 
```

### frontend/src/App.js

```javascript
import React, { useState, useRef } from "react";
import VideoTrimModal from "./components/VideoTrimModal";
import About from "./components/About";
import Contact from "./components/Contact";
import "./App.css";

function App() {
  const [selectedFile, setSelectedFile] = useState(null);
  const [isDragOver, setIsDragOver] = useState(false);
  const [uploading, setUploading] = useState(false);
  const [showTrimModal, setShowTrimModal] = useState(false);
  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
  const [currentPage, setCurrentPage] = useState("home");
  const [selectedGenre, setSelectedGenre] = useState("jazz");
  const [uploadResult, setUploadResult] = useState(null);
  const [processingStatus, setProcessingStatus] = useState(null);
  const fileInputRef = useRef(null);

  const apiUrl = process.env.REACT_APP_API_URL;

  const genreOptions = [
    { value: "jazz", label: "Jazz", icon: "🎷" },
    { value: "lofi", label: "Lo-Fi", icon: "🎧" },
    { value: "pop", label: "Pop", icon: "🎤" },
    { value: "rock", label: "Rock", icon: "🎸" },
    { value: "hiphop", label: "Hip Hop", icon: "🎵" },
  ];

  const handleFileSelect = (file) => {
    if (file && file.type.startsWith("video/")) {
      setSelectedFile(file);
      setShowTrimModal(true);
    } else {
      alert("Please select a valid video file.");
    }
  };

  const handleDragOver = (e) => {
    e.preventDefault();
    setIsDragOver(true);
  };

  const handleDragLeave = (e) => {
    e.preventDefault();
    setIsDragOver(false);
  };

  const handleDrop = (e) => {
    e.preventDefault();
    setIsDragOver(false);
    const file = e.dataTransfer.files[0];
    handleFileSelect(file);
  };

  const handleFileInputChange = (e) => {
    const file = e.target.files[0];
    handleFileSelect(file);
  };

  const handleBrowseClick = () => {
    fileInputRef.current?.click();
  };

  const handleCloseModal = () => {
    setShowTrimModal(false);
    setSelectedFile(null);
    if (fileInputRef.current) {
      fileInputRef.current.value = "";
    }
  };

  const toggleMobileMenu = () => {
    setIsMobileMenuOpen(!isMobileMenuOpen);
  };

  const closeMobileMenu = () => {
    setIsMobileMenuOpen(false);
  };

  const navigateToPage = (page) => {
    setCurrentPage(page);
    closeMobileMenu();
  };

  const handleUpload = async (trimData) => {
    setUploading(true);
    try {
      const formData = new FormData();

      // Use the trimmed video blob
      formData.append(
        "video",
        trimData.file,
        trimData.originalFileName || "trimmed-video.mp4"
      );
      formData.append("originalFileName", trimData.originalFileName || "");
      formData.append("trimStart", trimData.trimStart);
      formData.append("trimEnd", trimData.trimEnd);
      formData.append("duration", trimData.duration);
      formData.append("genre", selectedGenre);

      const response = await fetch(`${apiUrl}/upload-video`, {
        method: "POST",
        body: formData,
      });

      if (response.ok) {
        const result = await response.json();
        setUploadResult(result);
        setProcessingStatus("Processing completed successfully!");
        setCurrentPage("results"); // Navigate to results page
        handleCloseModal();
      } else {
        throw new Error("Upload failed");
      }
    } catch (error) {
      console.error("Error uploading video:", error);
      alert("Failed to upload video. Please try again.");
    } finally {
      setUploading(false);
    }
  };

  const handleDownload = async (url, filename) => {
    try {
      const response = await fetch(url);
      const blob = await response.blob();
      const downloadUrl = window.URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = downloadUrl;
      link.download = filename;
      document.body.appendChild(link);
      link.click();
      link.remove();
      window.URL.revokeObjectURL(downloadUrl);
    } catch (error) {
      console.error("Download failed:", error);
      alert("Download failed. Please try again.");
    }
  };

  const renderResultsPage = () => {
    if (!uploadResult) return null;

    return (
      <div className="results-container">
        <div className="results-header">
          <h2>🎉 Your Video is Ready!</h2>
          <p>{processingStatus}</p>
        </div>

        <div className="results-content">
          {/* Processing Summary */}
          <div className="processing-summary">
            <div className="summary-item">
              <span className="summary-label">Original File:</span>
              <span className="summary-value">
                {uploadResult.trim_info?.originalFileName || "N/A"}
              </span>
            </div>
            <div className="summary-item">
              <span className="summary-label">Duration:</span>
              <span className="summary-value">
                {uploadResult.trim_info?.duration?.toFixed(1) || "N/A"}s
              </span>
            </div>
            <div className="summary-item">
              <span className="summary-label">Resolution:</span>
              <span className="summary-value">
                {uploadResult.video_info?.resolution || "N/A"}
              </span>
            </div>
            <div className="summary-item">
              <span className="summary-label">Frames Extracted:</span>
              <span className="summary-value">
                {uploadResult.extracted_frames?.length || 0}
              </span>
            </div>
          </div>

          {/* Original Video */}
          <div className="video-section">
            <h3>📹 Original Video</h3>
            <div className="video-container">
              <video
                src={uploadResult.file_path}
                controls
                className="result-video"
                preload="metadata"
              >
                Your browser does not support the video tag.
              </video>
              <b
[truncated — 9204 more characters]
```

### setup.sh

```shell
#!/bin/bash

echo "🎵 Setting up Gita - Copyright-Free Music Finder"
echo "================================================"

# Setup Backend
echo "📦 Setting up Python backend..."
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
echo "✅ Backend dependencies installed!"

# Setup Frontend
echo "📦 Setting up React frontend..."
cd ../frontend
npm install
echo "✅ Frontend dependencies installed!"

echo ""
echo "🚀 Setup complete! To run the app:"
echo ""
echo "1. Start the backend:"
echo "   cd backend"
echo "   source venv/bin/activate"
echo "   python api/search-music.py"
echo ""
echo "2. Start the frontend (in a new terminal):"
echo "   cd frontend"
echo "   npm start"
echo ""
echo "3. Open http://localhost:3000 in your browser"
echo ""
echo "🎵 Happy coding!" 
```

### backend/supabase_config.py

```python
import os
from supabase import create_client, Client
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

# Supabase configuration
SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_ANON_KEY")

if not SUPABASE_URL or not SUPABASE_KEY:
    raise ValueError("Please set SUPABASE_URL and SUPABASE_ANON_KEY environment variables")

# Create Supabase client
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)

# Storage bucket names
STORAGE_BUCKETS = {
    "videos": "videos",
    "frames": "frames", 
    "music": "music",
    "final_videos": "final-videos"
} 
```

### backend/agents_test.py

```python
#!/usr/bin/env python3
"""
Comprehensive test suite for all agents in the Gita AI system.
This file contains all testing code that was previously in individual agent files.
"""

import os
import sys
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

# Add current directory to path for imports
sys.path.append(os.path.dirname(__file__))


def test_supabase_connection():
    """Test Supabase connection and configuration."""
    print("=== Supabase Connection Test ===")

    try:
        from supabase_config import supabase, STORAGE_BUCKETS

        print("✅ Supabase client imported successfully")
        print(f"Storage buckets: {STORAGE_BUCKETS}")

        # Test basic connection
        try:
            # Try a simple query to test connection
            result = (
                supabase.table("videos")
                .select("count", count="exact")
                .limit(1)
                .execute()
            )
            print("✅ Supabase connection successful")
        except Exception as e:
            print(f"⚠️ Supabase connection test failed: {e}")

        print("\n=== Supabase Connection Test Complete ===")

    except ImportError as e:
        print(f"Could not import Supabase config: {e}")
    except Exception as e:
        print(f"Error testing Supabase connection: {e}")


def test_video_processor_agent():
    """Test the video processor agent functionality."""
    print("=== Video Processor Agent Test ===")

    try:
        from agents.video_processor_agent import (
            get_video_info,
            extract_frames,
            attach_audio,
        )

        # Test video info function
        print("\n1. Testing video info function")
        test_video_path = "test_video.mp4"  # Replace with actual test video path

        if os.path.exists(test_video_path):
            try:
                video_info = get_video_info(test_video_path)
                print(f"Video info: {video_info}")
            except Exception as e:
                print(f"Error getting video info: {e}")
        else:
            print(f"Test video file not found: {test_video_path}")

        # Test frame extraction function
        print("\n2. Testing frame extraction function")
        if os.path.exists(test_video_path):
            try:
                frames = extract_frames(test_video_path, num_frames=3)
                print(f"Extracted {len(frames)} frames")
            except Exception as e:
                print(f"Error extracting frames: {e}")
        else:
            print(f"Test video file not found: {test_video_path}")

        print("\n=== Video Processor Test Complete ===")

    except ImportError as e:
        print(f"Could not import video processor agent: {e}")
    except Exception as e:
        print(f"Error testing video processor agent: {e}")


def test_prompt_generator_agent():
    """Test the prompt generator agent functionality."""
    print("=== Prompt Generator Agent Test ===")

    try:
        from agents.prompt_generator_agent import analyze_video_frames_from_supabase

        # Test with a video ID (replace with actual video ID from your database)
        test_video_id = "your-test-video-id-here"

        print(f"\n1. Testing vision analysis for video ID: {test_video_id}")
        try:
            result = analyze_video_frames_from_supabase(
                test_video_id, vision_prompt=None
            )
            print(f"Vision analysis result: {result[:100]}...")
        except Exception as e:
            print(f"Error: {e}")

        print("\n=== Prompt Generator Test Complete ===")

    except ImportError as e:
        print(f"Could not import prompt generator agent: {e}")
    except Exception as e:
        print(f"Error testing prompt generator agent: {e}")


def test_prompt_checker_agent():
    """Test the prompt checker agent functionality."""
    print("=== Prompt Checker Agent Test ===")

    try:
        from agents.prompt_checker_agent import (
            validate_and_fix_prompt,
            check_prompt_quality,
        )

        # Test cases
        test_prompts = [
            "Dark Hybrid Film Score, Los Angeles, Studio recording, ominous and relentless. Pristine contemporary Instrumental, recorded live London, Dark Trailer Music. A blend of driving percussive synths, distorted orchestral elements, and filmic pulse textures, with instruments such as synths, distorted strings, brass, and hybrid percussion, and a cinematic approach, featuring pulsing rhythms, dissonant harmonies, and a sense of impending dread, evoking a tense and foreboding atmosphere",
            "bad prompt",
            "Peaceful music with piano and strings",
            "Copyright music from famous artist",
            "Ambient atmospheric music with gentle textures and flowing melodies, suitable for a contemplative scene with natural elements and soft lighting.",
        ]

        for i, prompt in enumerate(test_prompts, 1):
            print(f"\n{i}. Testing prompt: {prompt[:50]}...")
            try:
                result = validate_and_fix_prompt(prompt)
                print(f"Result: {result}")
            except Exception as e:
                print(f"Error: {e}")

        print("\n=== Prompt Checker Test Complete ===")

    except ImportError as e:
        print(f"Could not import prompt checker agent: {e}")
    except Exception as e:
        print(f"Error testing prompt checker agent: {e}")


def test_music_generator_agent():
    """Test the music generator agent functionality."""
    print("=== Music Generation Agent Test ===")

    try:
        from agents.music_generator_agent import (
            generate_music_from_video_id,
            generate_music_with_lyria,
        )

        # Test with a video ID (replace with actual video ID from your database)
        test_video_id = "your-test-video-id-here"

        print(f"\n1. Testing music generation for video ID: {test_video_id}")
        try:
            result1 = generate_music_f
[truncated — 8123 more characters]
```

### frontend/public/index.html

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Gita - Find Copyright-Free Music</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

```

[13 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]