Spaces:
Sleeping
Sleeping
File size: 7,947 Bytes
99f5113 | 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 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | import os
import tempfile
import gradio as gr
import numpy as np
# Lazy imports to make startup faster on Spaces CPU
ASR_MODEL = None
EN2HI = None
HI2EN = None
TTS_ENGINE = None
XTTS_AVAILABLE = False
# --- Utilities ---
def lazy_import_asr():
global ASR_MODEL
if ASR_MODEL is None:
import whisper
# small is ok for 15β60s clips on CPU. change to "medium" if GPU.
ASR_MODEL = whisper.load_model("small")
return ASR_MODEL
def lazy_import_mt():
global EN2HI, HI2EN
if EN2HI is None or HI2EN is None:
from transformers import pipeline
# MarianMT models (offline, no API). Good enough for EN<>HI.
EN2HI = pipeline("translation", model="Helsinki-NLP/opus-mt-en-hi")
HI2EN = pipeline("translation", model="Helsinki-NLP/opus-mt-hi-en")
return EN2HI, HI2EN
def lazy_import_tts(prefer_xtts=True):
"""
Try XTTS v2 (multi-lingual + cloning) if available, else fall back to a small CPU TTS.
Fallback voices are non-cloned.
"""
global TTS_ENGINE, XTTS_AVAILABLE
if TTS_ENGINE is not None:
return TTS_ENGINE, XTTS_AVAILABLE
try:
# Coqui TTS
from TTS.api import TTS
if prefer_xtts:
# XTTS v2 supports speaker embedding + multilingual
TTS_ENGINE = TTS("tts_models/multilingual/multi-dataset/xtts_v2")
XTTS_AVAILABLE = True
else:
raise RuntimeError("skip xtts")
except Exception:
try:
# Lightweight English-only fallback (CPU friendly)
from TTS.api import TTS
TTS_ENGINE = TTS("tts_models/en/vctk/vits")
XTTS_AVAILABLE = False
except Exception as e:
raise RuntimeError(f"No TTS backend could be loaded: {e}")
return TTS_ENGINE, XTTS_AVAILABLE
def extract_audio(video_path, out_wav):
import moviepy.editor as mp
clip = mp.VideoFileClip(video_path)
audio = clip.audio
audio.write_audiofile(out_wav, fps=16000, codec="pcm_s16le")
clip.close()
return out_wav
def transcribe(video_path, language_hint="auto"):
"""ASR using Whisper. Returns (text, lang_code)."""
asr = lazy_import_asr()
with tempfile.TemporaryDirectory() as td:
wav = os.path.join(td, "audio.wav")
extract_audio(video_path, wav)
result = asr.transcribe(wav, language=None if language_hint == "auto" else language_hint)
text = result.get("text", "").strip()
lang = result.get("language", "unknown")
return text, lang
def translate_text(text, target_lang):
"""Use MarianMT for EN<>HI only. If source==target, return original."""
if not text.strip():
return ""
en2hi, hi2en = lazy_import_mt()
tl = target_lang.lower()
# naive detection: if Devanagari present, treat as Hindi
is_devanagari = any(0x0900 <= ord(ch) <= 0x097F for ch in text)
if tl in ["hi", "hindi"]:
if is_devanagari:
return text
# assume english -> hindi
out = en2hi(text)[0]["translation_text"]
return out
elif tl in ["en", "english"]:
if not is_devanagari:
return text
out = hi2en(text)[0]["translation_text"]
return out
else:
# unsupported target -> return original
return text
def synthesize_tts(text, lang="en", ref_audio=None):
"""
text -> wav_path. If XTTS available and ref_audio provided + consent, clone voice.
"""
tts, xtts = lazy_import_tts(prefer_xtts=True)
tmp_wav = tempfile.mktemp(suffix=".wav")
if xtts:
# XTTS supports many langs: 'en', 'hi', etc.
speaker_wav = ref_audio if (ref_audio and os.path.exists(ref_audio)) else None
tts.tts_to_file(text=text, language=lang, speaker_wav=speaker_wav, file_path=tmp_wav)
else:
# fallback is English-only; if lang not en, we just attempt anyway
tts.tts_to_file(text=text, file_path=tmp_wav)
return tmp_wav
def merge_audio(video_path, dubbed_wav, out_mp4):
import moviepy.editor as mp
video = mp.VideoFileClip(video_path)
audio = mp.AudioFileClip(dubbed_wav).set_duration(video.duration)
final = video.set_audio(audio)
# write with AAC audio so browsers play fine
final.write_videofile(out_mp4, audio_codec="aac", codec="libx264")
video.close()
audio.close()
final.close()
return out_mp4
# --- Gradio UI callbacks ---
def full_dub(video, target_lang, language_hint, use_cloning, consent, ref_voice=None):
"""
End-to-end pipeline: ASR -> (optional) Translate -> TTS -> Merge
Returns: transcribed_text, translated_text, dubbed_video
"""
if not consent:
raise gr.Error("Please confirm consent for dubbing/cloning.")
if video is None:
raise gr.Error("Please upload a video (15β60 seconds recommended).")
# 1) ASR
text, detected = transcribe(video, language_hint=language_hint)
# 2) Translate if needed
translated = translate_text(text, target_lang)
# 3) TTS
# choose TTS language code
lang_code = "en" if target_lang.lower().startswith("en") else "hi"
# If no translation occurred (unsupported), speak original text
tts_text = translated if translated.strip() else text
# Only pass ref_voice if user ticked cloning
ref_path = None
if use_cloning and ref_voice is not None:
# Coqui XTTS expects a file path
ref_path = ref_voice
out_wav = synthesize_tts(tts_text, lang=lang_code, ref_audio=ref_path)
# 4) Merge
out_mp4 = tempfile.mktemp(suffix=".mp4")
merge_audio(video, out_wav, out_mp4)
# Small watermark text for transparency
mark = f"Dubbed ({'cloned' if use_cloning else 'synthetic'}) | Lang: {target_lang.upper()}"
return text, translated, out_mp4, mark
# --- Build UI ---
with gr.Blocks(theme=gr.themes.Soft(), css=".small {font-size: 12px}") as demo:
gr.Markdown("""
# π¬ Safe Video Dubbing (EN β HI) β CPU Friendly
**Hinglish UI**: Neeche steps follow karo. 15β60s video pe best results.
- ASR: Whisper-small (offline)
- Translation: MarianMT (offline)
- TTS: Tries **XTTS v2** (voice cloning) β agar available nahin hua toh English-only fallback TTS use karega.
- Output: MP4 with replaced audio
**Note (Safety)**: Sirf wahi awaaz clone/ dub karein jiske liye aapke paas clear consent ho. App ek chhota watermark lagata hai transparency ke liye.
""")
with gr.Row():
video_in = gr.Video(label="Input Video (mp4/mov)")
ref_voice = gr.Audio(label="(Optional) Reference Voice for Cloning β same person ki 5β10s WAV/MP3", type="filepath")
with gr.Row():
target_lang = gr.Radio(["en", "hi"], value="hi", label="Target Language (Output)")
language_hint = gr.Dropdown(["auto", "en", "hi"], value="auto", label="ASR Language Hint")
with gr.Row():
use_cloning = gr.Checkbox(False, label="Enable Voice Cloning (XTTS v2)")
consent = gr.Checkbox(False, label="I confirm consent for dubbing / cloning (required)")
with gr.Row():
btn = gr.Button("βΆοΈ Run Dubbing")
with gr.Row():
transcribed = gr.Textbox(label="Transcribed Text")
translated = gr.Textbox(label="Translated Text")
dubbed = gr.Video(label="Dubbed Output (MP4)")
watermark = gr.Markdown(visible=True)
btn.click(fn=full_dub, inputs=[video_in, target_lang, language_hint, use_cloning, consent, ref_voice],
outputs=[transcribed, translated, dubbed, watermark])
gr.Markdown("""
---
**Tips (Hinglish):**
- Agar Space CPU pe slow hai, 15β30 sec clips try karo.
- XTTS cloning ke liye GPU recommended. HF Space hardware settings me GPU select kar sakte ho.
- Hindi ke liye XTTS best; fallback TTS English-only ho sakta hai.
- Clean audio = better results. Background noise kam rakho.
""")
if __name__ == "__main__":
demo.launch() |