import json import asyncio import textwrap import numpy as np from PIL import Image, ImageDraw, ImageFont import google.generativeai as genai import edge_tts from moviepy import VideoFileClip, AudioFileClip, ImageClip, CompositeVideoClip from dotenv import load_dotenv import os load_dotenv() raw_api_key = os.getenv("GENAI_API_KEY") # 2. Check if it exists if not raw_api_key: raise ValueError("GENAI_API_KEY is missing in Hugging Face Secrets!") CLEAN_API_KEY = raw_api_key.strip() # 4. Configure Gemini with the clean key genai.configure(api_key=CLEAN_API_KEY) # GENAI_API_KEY = os.getenv("GENAI_API_KEY") # genai.configure(api_key=GENAI_API_KEY) def format_timestamp(seconds): """Converts seconds (float) to WebVTT format (HH:MM:SS.mmm)""" milliseconds = int((seconds % 1) * 1000) minutes = int(seconds // 60) hours = int(minutes // 60) minutes = minutes % 60 seconds = int(seconds % 60) return f"{hours:02}:{minutes:02}:{seconds:02}.{milliseconds:03}" async def generate_dubbing(text, voice, output_file): communicate = edge_tts.Communicate(text, voice) await communicate.save(output_file) def translate_and_dub(video_path, target_lang, gender="Female"): base_name = os.path.splitext(video_path)[0] audio_path = f"{base_name}_temp.mp3" dub_audio_path = f"{base_name}_dub.mp3" output_video_path = f"{base_name}_dubbed.mp4" output_sub_path = f"{base_name}_subs.vtt" # We create a VTT file now # 1. EXTRACT AUDIO & INFO with VideoFileClip(video_path) as video: video.audio.write_audiofile(audio_path, logger=None) duration = video.duration # 2. AI TRANSLATION & TIMESTAMPS model = genai.GenerativeModel("gemini-3-flash-preview") if target_lang == "Chinese": voice = "zh-CN-YunxiNeural" if gender == "Male" else "zh-CN-XiaoxiaoNeural" lang_prompt = "Simplified Chinese" else: voice = "vi-VN-NamMinhNeural" if gender == "Male" else "vi-VN-HoaiMyNeural" lang_prompt = "Vietnamese" prompt = f""" Listen to this audio. Return a JSON list of segments. For each segment, translate the spoken content into {lang_prompt}. Format: [ {{"start": 0.0, "end": 2.5, "text": "Translated text here"}}, {{"start": 2.5, "end": 5.0, "text": "Next text here"}} ] Use seconds for timestamps. Ensure the segments cover the whole video. """ print("Sending to Gemini...") audio_file = genai.upload_file(path=audio_path) response = model.generate_content([prompt, audio_file], generation_config={"response_mime_type": "application/json"}) try: segments = json.loads(response.text) except json.JSONDecodeError: segments = [{"start": 0, "end": duration, "text": "Translation Error: Could not parse JSON."}] # 3. GENERATE VTT FILE (SUBTITLES) print("Creating subtitles...") vtt_content = "WEBVTT\n\n" full_text_for_dub = [] for seg in segments: start_time = format_timestamp(float(seg['start'])) end_time = format_timestamp(float(seg['end'])) text = seg['text'] # Add to VTT vtt_content += f"{start_time} --> {end_time}\n{text}\n\n" # Collect text for dubbing full_text_for_dub.append(text) # Save VTT file with open(output_sub_path, "w", encoding="utf-8") as f: f.write(vtt_content) # 4. GENERATE DUBBING AUDIO print("Generating voice...") full_text = " ".join(full_text_for_dub) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(generate_dubbing(full_text, voice, dub_audio_path)) # 5. MERGE AUDIO ONLY (No video re-encoding needed usually, but MoviePy is safest) print("Merging new audio...") with VideoFileClip(video_path) as video: with AudioFileClip(dub_audio_path) as dub: # Handle duration mismatch if dub.duration > video.duration: dub = dub.subclipped(0, video.duration) final_clip = video.with_audio(dub) final_clip.write_videofile(output_video_path, codec="libx264", audio_codec="aac", logger=None) # Cleanup temp files if os.path.exists(audio_path): os.remove(audio_path) # Keeping dub audio and vtt might be useful for the user return output_video_path, output_sub_path, segments