CoolFace
Apppublic

suraj-ml-projects/Video_editing

sourceHugging Facemitupdated 4d agoView on Hugging Face
0likes
tts_utils.py186 linesDownload Raw Back to root
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