File size: 7,584 Bytes
d24a2f3 74d8a08 8a409a5 d24a2f3 d6208ae d76810b 8a409a5 d24a2f3 d6208ae 8a409a5 d6208ae d24a2f3 8a409a5 d24a2f3 d6208ae d24a2f3 d6208ae d24a2f3 d6208ae d24a2f3 d6208ae d24a2f3 d6208ae 8a409a5 d6208ae 8a409a5 d24a2f3 d6208ae 8a409a5 d6208ae d24a2f3 d6208ae d24a2f3 d6208ae 8a409a5 d24a2f3 d6208ae 8a409a5 d6208ae 8a409a5 d6208ae 8a409a5 74d8a08 8a409a5 d6208ae 8a409a5 d6208ae 76a5733 8a409a5 d6208ae 8a409a5 d6208ae d24a2f3 d6208ae 8a409a5 d6208ae 8a409a5 d6208ae 8a409a5 d24a2f3 d6208ae 74d8a08 d6208ae 74d8a08 d6208ae 74d8a08 d6208ae 74d8a08 d6208ae 74d8a08 d6208ae 74d8a08 d6208ae 74d8a08 8a409a5 d6208ae 8a409a5 d6208ae 8a409a5 d6208ae 74d8a08 8a409a5 d6208ae 74d8a08 8a409a5 d6208ae 8a409a5 d6208ae d24a2f3 d6208ae 8a409a5 d6208ae 8a409a5 d6208ae 8a409a5 4ae7788 |
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 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
import os
import time
import tempfile
import google.generativeai as genai
import requests
from flask import Flask, request, render_template, send_from_directory, url_for, flash
from moviepy.video.io.VideoFileClip import VideoFileClip
from moviepy.audio.io.AudioFileClip import AudioFileClip
from werkzeug.utils import secure_filename
# --- 1. INITIALIZATION & CONFIGURATION ---
app = Flask(__name__)
# Load secrets from environment variables
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
TTS_API_URL = os.getenv("TTS_API_URL")
# Check if secrets were loaded correctly and provide clear error messages
if not GEMINI_API_KEY:
raise ValueError("SECURITY ERROR: GEMINI_API_KEY secret not found! Please set it as an environment variable.")
if not TTS_API_URL:
raise ValueError("CONFIGURATION ERROR: TTS_API_URL secret not found! Please set it as an environment variable.")
# Configure the Gemini API
genai.configure(api_key=GEMINI_API_KEY)
# Configure directories for temporary file storage
UPLOAD_FOLDER = 'uploads'
DOWNLOAD_FOLDER = 'downloads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['DOWNLOAD_FOLDER'] = DOWNLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100 MB upload limit
app.secret_key = 'supersecretkey' # Required for flash messages
# --- 2. VOICE CHOICES & GEMINI PROMPT ---
VOICE_CHOICES = {
"Male (Charon)": "Charon",
"Female (Zephyr)": "Zephyr"
}
GEMINI_PROMPT = """
You are an 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 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: அந்த ரகசியம் எனக்கு மட்டும் தான் தெரியும். Shout angrily: உடனே இங்கிருந்து போ!
"""
# --- 3. CORE LOGIC HELPER FUNCTIONS ---
def generate_tamil_script(video_file_path):
"""Generates a single, continuous Tamil script from the video using Gemini."""
print("Uploading file to Gemini for transcription...")
video_file = genai.upload_file(video_file_path)
print("Waiting for Gemini file processing...")
while video_file.state.name == "PROCESSING":
time.sleep(5)
video_file = genai.get_file(video_file.name)
if video_file.state.name != "ACTIVE":
raise Exception(f"Gemini file processing failed: {video_file.state.name}")
print("Generating narrator script...")
model = genai.GenerativeModel(model_name="models/gemini-2.5-flash")
response = model.generate_content([GEMINI_PROMPT, video_file])
genai.delete_file(video_file.name)
if response.text:
return " ".join(response.text.strip().splitlines())
raise Exception("No valid script was generated by Gemini.")
def generate_single_audio_track(dialogue_text, voice_name, is_cheerful, output_path):
"""Generates one audio track for the entire script via TTS API."""
print(f"Requesting audio from TTS API (Voice: {voice_name}, Cheerful: {is_cheerful})")
payload = {"text": dialogue_text, "voice_name": voice_name, "cheerful": is_cheerful}
response = requests.post(TTS_API_URL, json=payload, timeout=300) # 5-minute timeout
if response.status_code == 200:
with open(output_path, "wb") as f:
f.write(response.content)
print(f"Audio track saved to {output_path}")
return True
raise Exception(f"Error from TTS API: {response.status_code} - {response.text}")
def replace_video_audio(video_path, new_audio_path, output_path):
"""
Replaces the audio of a video with a new audio file.
This function is robustly designed to ensure file handles are always closed.
"""
print("Replacing video audio using MoviePy...")
video_clip = None
audio_clip = None
final_clip = None
try:
video_clip = VideoFileClip(video_path)
audio_clip = AudioFileClip(new_audio_path)
# The standard and correct way to set audio in modern moviepy
final_clip = video_clip.set_audio(audio_clip)
# Write the final video file with a progress bar in the console
final_clip.write_videofile(output_path, codec="libx264", audio_codec="aac", logger='bar')
print(f"Successfully created final video at {output_path}")
except Exception as e:
print(f"FATAL ERROR in replace_video_audio: {e}")
raise # Re-raise the exception to be caught by the Flask route
finally:
# This block ensures that all files are closed, preventing file lock issues.
if video_clip:
video_clip.close()
if audio_clip:
audio_clip.close()
if final_clip:
final_clip.close()
# --- 4. FLASK WEB ROUTES ---
@app.route('/', methods=['GET'])
def index():
"""Renders the main upload page."""
return render_template('index.html')
@app.route('/process', methods=['POST'])
def process_video():
"""Handles video upload, processing, and renders the result."""
if 'video' not in request.files:
flash("No video file selected. Please choose a file to upload.")
return render_template('index.html')
file = request.files['video']
if file.filename == '':
flash("No video file selected. Please choose a file to upload.")
return render_template('index.html')
try:
filename = secure_filename(file.filename)
upload_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(upload_path)
voice_choice = request.form['voice_choice']
is_cheerful = 'cheerful' in request.form
voice_name = VOICE_CHOICES[voice_choice]
# Core Logic Execution
script = generate_tamil_script(upload_path)
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_audio:
temp_audio_path = temp_audio.name
generate_single_audio_track(script, voice_name, is_cheerful, temp_audio_path)
final_video_name = f"dubbed_{filename}"
final_video_path = os.path.join(app.config['DOWNLOAD_FOLDER'], final_video_name)
replace_video_audio(upload_path, temp_audio_path, final_video_path)
# Cleanup temporary audio file
os.remove(temp_audio_path)
return render_template('index.html',
result_video=url_for('serve_video', filename=final_video_name),
script=script)
except Exception as e:
print(f"An error occurred during processing: {e}")
flash(f"An error occurred: {e}")
return render_template('index.html')
@app.route('/downloads/<filename>')
def serve_video(filename):
"""Serves the final dubbed video for display on the webpage."""
return send_from_directory(app.config['DOWNLOAD_FOLDER'], filename)
# --- 5. APPLICATION ENTRY POINT ---
if __name__ == '__main__':
app.run(host="0.0.0.0", port=7860) |