File size: 5,435 Bytes
d24a2f3
8a409a5
21e6f34
a2554b6
 
 
42d1756
 
d24a2f3
 
08c3547
a2554b6
d24a2f3
a2554b6
08c3547
8a409a5
a2554b6
21e6f34
a2554b6
 
 
 
 
21e6f34
a2554b6
 
21e6f34
a2554b6
 
 
 
893e301
 
a2554b6
 
893e301
21e6f34
d24a2f3
a2554b6
ab46005
8a814dc
9807843
 
 
 
 
 
d24a2f3
 
a2554b6
 
 
 
 
 
352553f
a2554b6
 
 
 
 
 
 
8a409a5
a2554b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8a409a5
a2554b6
d6208ae
a2554b6
893e301
a2554b6
 
893e301
a2554b6
 
893e301
9807843
a2554b6
 
a30e87b
a2554b6
 
 
 
 
 
 
 
 
 
 
 
 
 
d6208ae
a2554b6
 
 
 
 
 
 
 
 
 
 
 
 
893e301
a2554b6
 
 
d6208ae
a2554b6
 
 
 
 
 
 
893e301
 
a2554b6
 
d6208ae
a2554b6
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import os
import tempfile
import uuid
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from moviepy.video.io.VideoFileClip import VideoFileClip
from moviepy.audio.io.AudioFileClip import AudioFileClip
import google.generativeai as genai
import requests
from dotenv import load_dotenv
from pathlib import Path

# Load environment variables
load_dotenv()

app = FastAPI()

# Configure directories
UPLOAD_DIR = "uploads"
DOWNLOAD_DIR = "downloads"
Path(UPLOAD_DIR).mkdir(exist_ok=True)
Path(DOWNLOAD_DIR).mkdir(exist_ok=True)

# Mount static files
app.mount("/downloads", StaticFiles(directory="downloads"), name="downloads")

# Configuration
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
TTS_API_URL = os.getenv("TTS_API_URL")
genai.configure(api_key=GEMINI_API_KEY)

VOICE_CHOICES = {
    "male": "Charon",
    "female": "Zephyr"
}

GEMINI_PROMPT = """
You are an expert AI scriptwriter. Your task is to watch the provided video and transcribe ALL spoken dialogue into a SINGLE, CONTINUOUS block of modern, colloquial Tamil.

**CRITICAL INSTRUCTIONS:**
1.  **Single Script:** Combine all dialogue from all speakers into one continuous script.
2.  **NO Timestamps or Speaker Labels:** Do NOT include any timestamps or speaker identifiers.
3.  **Incorporate Performance:** Add English style prompts (e.g., `Say happily:`, `Whisper mysteriously:`) and performance tags (e.g., `[laugh]`, `[sigh]`) directly into the text for an expressive narration.

**EXAMPLE OUTPUT:**
Say happily: வணக்கம்! [laugh] எப்படி இருக்கீங்க? Whisper mysteriously: அந்த ரகசியம் எனக்கு மட்டும் தான் தெரியும்.
"""

@app.post("/process")
async def process_video(
    file: UploadFile = File(...),
    voice: str = Form("male"),
    cheerful: bool = Form(False)
):
    try:
        # Save uploaded file
        file_ext = Path(file.filename).suffix
        file_name = f"{uuid.uuid4()}{file_ext}"
        file_path = os.path.join(UPLOAD_DIR, file_name)
        
        with open(file_path, "wb") as buffer:
            buffer.write(await file.read())

        # Generate script using Gemini
        script = await generate_script(file_path)
        
        # Generate audio
        audio_path = os.path.join(UPLOAD_DIR, f"audio_{uuid.uuid4()}.wav")
        await generate_audio(script, voice, cheerful, audio_path)
        
        # Create dubbed video
        output_name = f"dubbed_{file_name}"
        output_path = os.path.join(DOWNLOAD_DIR, output_name)
        await create_dubbed_video(file_path, audio_path, output_path)
        
        # Cleanup
        os.remove(file_path)
        os.remove(audio_path)
        
        return {
            "video_url": f"/downloads/{output_name}",
            "script": script
        }
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

async def generate_script(video_path: str) -> str:
    try:
        video_file = genai.upload_file(video_path, mime_type="video/mp4")
        
        while video_file.state.name == "PROCESSING":
            video_file = genai.get_file(video_file.name)
        
        if video_file.state.name != "ACTIVE":
            raise Exception("Gemini processing failed")
        
        model = genai.GenerativeModel("models/gemini-2.5-flash")
        response = model.generate_content([GEMINI_PROMPT, video_file])
        genai.delete_file(video_file.name)
        
        if hasattr(response, 'text'):
            return " ".join(response.text.strip().splitlines())
        raise Exception("No script generated")
    except Exception as e:
        raise Exception(f"Script generation failed: {str(e)}")

async def generate_audio(text: str, voice: str, cheerful: bool, output_path: str):
    try:
        voice_name = VOICE_CHOICES.get(voice, "Charon")
        payload = {
            "text": text,
            "voice_name": voice_name,
            "cheerful": cheerful
        }
        
        response = requests.post(TTS_API_URL, json=payload, timeout=300)
        if response.status_code != 200:
            raise Exception(f"TTS API error: {response.text}")
            
        with open(output_path, "wb") as f:
            f.write(response.content)
    except Exception as e:
        raise Exception(f"Audio generation failed: {str(e)}")

async def create_dubbed_video(video_path: str, audio_path: str, output_path: str):
    try:
        video = VideoFileClip(video_path)
        audio = AudioFileClip(audio_path)
        
        # Ensure audio matches video duration
        if audio.duration > video.duration:
            audio = audio.subclip(0, video.duration)
        
        video = video.set_audio(audio)
        video.write_videofile(
            output_path,
            codec="libx264",
            audio_codec="aac",
            threads=4,
            preset="fast"
        )
        
        video.close()
        audio.close()
    except Exception as e:
        raise Exception(f"Video processing failed: {str(e)}")

@app.get("/downloads/{file_name}")
async def download_file(file_name: str):
    file_path = os.path.join(DOWNLOAD_DIR, file_name)
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail="File not found")
    return FileResponse(file_path)