|
|
|
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 |
|
from dotenv import load_dotenv |
|
|
|
|
|
load_dotenv() |
|
app = Flask(__name__) |
|
|
|
|
|
app.config.update({ |
|
'GEMINI_API_KEY': os.getenv('GEMINI_API_KEY'), |
|
'TTS_API_URL': os.getenv('TTS_API_URL'), |
|
'UPLOAD_FOLDER': 'uploads', |
|
'DOWNLOAD_FOLDER': 'downloads', |
|
'MAX_CONTENT_LENGTH': 100 * 1024 * 1024, |
|
'SECRET_KEY': os.urandom(24), |
|
'ALLOWED_EXTENSIONS': {'mp4', 'mov', 'webm', 'avi'} |
|
}) |
|
|
|
|
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) |
|
os.makedirs(app.config['DOWNLOAD_FOLDER'], exist_ok=True) |
|
|
|
|
|
genai.configure(api_key=app.config['GEMINI_API_KEY']) |
|
|
|
|
|
VOICE_CHOICES = { |
|
"Male (Deep Voice)": "deep_male", |
|
"Female (Soft Tone)": "soft_female", |
|
"Neutral (Professional)": "neutral" |
|
} |
|
|
|
GEMINI_PROMPT = """ |
|
You are an expert AI scriptwriter. Analyze this video and: |
|
|
|
1. Transcribe ALL dialogue into continuous Tamil |
|
2. Remove timestamps/speaker labels |
|
3. Add expressive directions like [laugh] or [pause] |
|
4. Keep natural flow and cultural context |
|
|
|
Example Output: |
|
[cheerful] வணக்கம்! [laugh] இன்று நிலைமை எப்படி இருக்கிறது? [serious] இதை கவனமாக கேளுங்கள்... |
|
""" |
|
|
|
def allowed_file(filename): |
|
return '.' in filename and \ |
|
filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS'] |
|
|
|
def generate_script(video_path): |
|
"""Generate Tamil script using Gemini AI""" |
|
try: |
|
print("Uploading video to Gemini...") |
|
video_file = genai.upload_file(video_path, mime_type="video/mp4") |
|
|
|
|
|
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("Gemini processing failed") |
|
|
|
model = genai.GenerativeModel("models/gemini-pro-vision") |
|
response = model.generate_content([GEMINI_PROMPT, video_file]) |
|
genai.delete_file(video_file.name) |
|
|
|
return response.text.strip() if hasattr(response, 'text') else "" |
|
except Exception as e: |
|
print(f"Gemini Error: {str(e)}") |
|
raise |
|
|
|
def generate_audio(script, voice, tone): |
|
"""Generate audio using TTS API""" |
|
try: |
|
response = requests.post( |
|
app.config['TTS_API_URL'], |
|
json={ |
|
"text": script, |
|
"voice": voice, |
|
"tone": tone |
|
}, |
|
timeout=300 |
|
) |
|
response.raise_for_status() |
|
return response.content |
|
except requests.exceptions.RequestException as e: |
|
print(f"TTS API Error: {str(e)}") |
|
raise |
|
|
|
def process_video(input_path, audio_data, output_filename): |
|
"""Combine video with new audio track""" |
|
try: |
|
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as audio_temp: |
|
audio_temp.write(audio_data) |
|
audio_temp_path = audio_temp.name |
|
|
|
video = VideoFileClip(input_path) |
|
audio = AudioFileClip(audio_temp_path) |
|
|
|
|
|
if audio.duration > video.duration: |
|
audio = audio.subclip(0, video.duration) |
|
|
|
video.audio = audio |
|
output_path = os.path.join(app.config['DOWNLOAD_FOLDER'], output_filename) |
|
video.write_videofile( |
|
output_path, |
|
codec='libx264', |
|
audio_codec='aac', |
|
threads=4, |
|
logger=None |
|
) |
|
|
|
return output_path |
|
except Exception as e: |
|
print(f"Video Processing Error: {str(e)}") |
|
raise |
|
finally: |
|
if 'video' in locals(): video.close() |
|
if 'audio' in locals(): audio.close() |
|
if os.path.exists(audio_temp_path): os.remove(audio_temp_path) |
|
|
|
@app.route('/', methods=['GET']) |
|
def home(): |
|
return render_template('index.html', voices=VOICE_CHOICES) |
|
|
|
@app.route('/process', methods=['POST']) |
|
def process(): |
|
if 'video' not in request.files: |
|
flash('No file selected', 'error') |
|
return render_template('index.html', voices=VOICE_CHOICES) |
|
|
|
file = request.files['video'] |
|
if file.filename == '': |
|
flash('No file selected', 'error') |
|
return render_template('index.html', voices=VOICE_CHOICES) |
|
|
|
if not allowed_file(file.filename): |
|
flash('Invalid file type. Allowed: MP4, MOV, WEBM, AVI', 'error') |
|
return render_template('index.html', voices=VOICE_CHOICES) |
|
|
|
try: |
|
|
|
filename = secure_filename(file.filename) |
|
input_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) |
|
file.save(input_path) |
|
|
|
|
|
voice = request.form.get('voice', 'neutral') |
|
tone = 'cheerful' if request.form.get('tone') == 'on' else 'neutral' |
|
|
|
|
|
script = generate_script(input_path) |
|
audio_data = generate_audio(script, voice, tone) |
|
|
|
|
|
output_filename = f"dubbed_{filename}" |
|
output_path = process_video(input_path, audio_data, output_filename) |
|
|
|
flash('Processing completed successfully!', 'success') |
|
return render_template('result.html', |
|
video_url=url_for('download', filename=output_filename), |
|
script=script) |
|
|
|
except Exception as e: |
|
flash(f'Processing failed: {str(e)}', 'error') |
|
return render_template('index.html', voices=VOICE_CHOICES) |
|
finally: |
|
if 'input_path' in locals() and os.path.exists(input_path): |
|
os.remove(input_path) |
|
|
|
@app.route('/downloads/<filename>') |
|
def download(filename): |
|
return send_from_directory(app.config['DOWNLOAD_FOLDER'], filename) |
|
|
|
if __name__ == '__main__': |
|
app.run(host='0.0.0.0', port=5000) |