suraj-ml-projects/Video_editing
0
1"""2utils/tts_utils.py3──────────────────4Hindi Text-to-Speech using gTTS (Google TTS via HTTP — free, no API key).5 6Why gTTS over Coqui?7 - Coqui TTS Hindi models are large (1–2GB) and slow on CPU8 - gTTS is instant, free, and produces natural Hindi speech9 - No model download, no GPU needed10 - Works perfectly on Hugging Face Spaces11 12Architecture:13 - Each translated segment → individual Hindi MP3 → WAV14 - Silence gaps are inserted between segments to match original timing15 - All segment WAVs are concatenated into one full dub track16"""17 18import os19import gc20import tempfile21from typing import List, Dict, Callable, Optional22 23from gtts import gTTS24from pydub import AudioSegment25 26 27# ── Available speaker "voices" ───────────────────────────────────────────────28# gTTS supports TLD variants which subtly affect the accent/voice29SPEAKER_OPTIONS = {30 "Hindi (India — Standard)": "com",31 "Hindi (India — Alternate)": "co.in",32}33 34 35def _text_to_wav(text: str, tld: str, out_path: str, speed_factor: float = 1.0) -> str:36 """37 Convert a Hindi text string to a WAV audio file.38 39 Parameters:40 text – Hindi text41 tld – gTTS top-level domain for voice accent42 out_path – where to save the WAV43 speed_factor – NOT used by gTTS directly; we use pydub speedup instead44 """45 if not text.strip():46 # Return a short silence for empty segments47 silence = AudioSegment.silent(duration=500)48 silence.export(out_path, format="wav")49 return out_path50 51 # Generate TTS (outputs MP3)52 mp3_path = out_path.replace(".wav", ".mp3")53 tts = gTTS(text=text, lang="hi", tld=tld, slow=False)54 tts.save(mp3_path)55 56 # Convert MP3 → WAV for consistent processing57 audio = AudioSegment.from_mp3(mp3_path)58 audio.export(out_path, format="wav")59 60 # Clean up MP361 try:62 os.remove(mp3_path)63 except Exception:64 pass65 66 return out_path67 68 69def _fit_speech_to_duration(speech: AudioSegment, target_duration_ms: int) -> AudioSegment:70 """71 Speed up or slow down speech to (roughly) fit the original segment duration.72 73 Strategy:74 - If speech is too long (> 130% of target), speed it up moderately75 - If speech is too short, pad with trailing silence76 - We don't slow down speech (sounds unnatural) — just pad instead77 78 This is the lightweight alternative to full lip-sync.79 """80 speech_dur = len(speech)81 82 if speech_dur == 0 or target_duration_ms == 0:83 return speech84 85 ratio = speech_dur / target_duration_ms86 87 if ratio > 1.3:88 # Speed up: increase frame rate (pitch changes too, but acceptable)89 # pydub speedup: multiply frame_rate90 speed_multiplier = min(ratio, 1.8) # cap at 1.8× to stay intelligible91 sped = speech._spawn(92 speech.raw_data,93 overrides={"frame_rate": int(speech.frame_rate * speed_multiplier)},94 ).set_frame_rate(speech.frame_rate)95 return sped96 97 elif speech_dur < target_duration_ms:98 # Pad with silence at end99 pad = AudioSegment.silent(duration=target_duration_ms - speech_dur)100 return speech + pad101 102 return speech103 104 105def generate_hindi_speech(106 segments: List[Dict],107 speaker: str = "com",108 speech_volume: float = 1.2,109 progress_callback: Optional[Callable] = None,110) -> str:111 """112 Generate a full Hindi dubbed audio track from translated segments.113 114 Process:115 1. For each segment, generate Hindi TTS audio116 2. Speed-fit to match original segment duration117 3. Insert silence between segments to preserve original timing118 4. Concatenate everything into one WAV file119 120 Returns path to the final dubbed WAV file.121 """122 if not segments:123 raise ValueError("No segments to synthesise")124 125 # Figure out total audio duration from last segment end time126 total_duration_ms = int(max(s["end"] for s in segments) * 1000) + 2000127 128 # Start with a silent base track (we'll fill it with speech)129 full_track = AudioSegment.silent(duration=total_duration_ms)130 131 temp_dir = tempfile.gettempdir()132 n = len(segments)133 134 for i, seg in enumerate(segments):135 if progress_callback:136 progress_callback(137 int((i + 1) / n * 90),138 f"Synthesising segment {i+1}/{n}…"139 )140 141 hindi_text = seg.get("hindi_text", "").strip()142 if not hindi_text:143 continue # skip segments with no translation144 145 # Target duration in milliseconds146 seg_start_ms = int(seg["start"] * 1000)147 seg_end_ms = int(seg["end"] * 1000)148 target_ms = seg_end_ms - seg_start_ms149 150 # Generate speech WAV for this segment151 wav_path = os.path.join(temp_dir, f"seg_{i:04d}.wav")152 try:153 _text_to_wav(hindi_text, tld=speaker, out_path=wav_path)154 except Exception as e:155 # If TTS fails for a segment, just leave silence there156 continue157 158 # Load generated speech159 speech = AudioSegment.from_wav(wav_path)160 161 # Optionally boost volume162 if speech_volume != 1.0:163 import math164 db_boost = 20 * math.log10(speech_volume)165 speech = speech + db_boost166 167 # Fit speech duration to original segment duration168 speech = _fit_speech_to_duration(speech, target_ms)169 170 # Overlay speech onto the silent base track at the right position171 full_track = full_track.overlay(speech, position=seg_start_ms)172 173 # Clean up temp file174 try:175 os.remove(wav_path)176 except Exception:177 pass178 179 gc.collect()180 181 # Export final dubbed track182 output_path = os.path.join(temp_dir, "hindi_dubbed_audio.wav")183 full_track.export(output_path, format="wav")184 185 return output_path186 