"""Maya 2 Native — multilingual streaming TTS for 12 Indic languages + English, with zero-shot voice cloning. Apache-2.0, 2.5B params, 24 kHz output. ZeroGPU note: the maya3 engine captures CUDA graphs and allocates pinned host buffers at init, which requires a real CUDA context — impossible at module scope under the ZeroGPU hijack. So the engine is built lazily inside the @spaces.GPU worker (cached in a module global, which persists for the warm worker process), while the ~5 GB of weights are pre-downloaded to the HF cache at module scope so worker-side loads are disk-bound. """ import os import time import spaces # MUST precede any torch / CUDA import os.environ.setdefault("TOKENIZERS_PARALLELISM", "false") # --- module scope: pre-warm the HF cache (pure disk/network, no CUDA) ------- from maya3 import hub RELEASE = "maya-research/maya2-native" _paths = hub.resolve(RELEASE) # downloads model shards + kyutai/mimi + speechbrain ECAPA CKPT_DIR = _paths["ckpt"] # release dir; from_pretrained re-resolves offline-first import gradio as gr import numpy as np LANGS = ["hi", "en", "as", "bn", "gu", "kn", "ml", "mr", "or", "pa", "ta", "te"] LANG_NAMES = { "hi": "Hindi", "en": "English", "as": "Assamese", "bn": "Bengali", "gu": "Gujarati", "kn": "Kannada", "ml": "Malayalam", "mr": "Marathi", "or": "Odia", "pa": "Punjabi", "ta": "Tamil", "te": "Telugu", } # 20 named voices shipped with the release (voices/voices.json). VOICES = [ "Ahana", "Vaidehi", "Malini", "Charvi", "Jahnavi", "Anirban", "Nakul", "Vasant", "Siddhant", "Satvik", "Abhay", "Nivedita", "Ishwari", "Ritvik", "Vrinda", "Sreekar", "Kayal", "Shaunak", "Akshat", "Tanushree", ] MAX_CHARS = 1200 # ~1 minute of speech; keeps duration bounded _tts = None def _get_tts(): """Build the engine once per worker process (weights load + CUDA graphs + warmup).""" global _tts if _tts is None: from maya3.api import Maya3TTS t0 = time.perf_counter() _tts = Maya3TTS.from_pretrained( CKPT_DIR, # release dir; re-resolves (offline-first) + registers named voices device="cuda", max_seats=1, # smallest graph grid; demo serves one request at a time capacity=1024, # halves KV cache vs default; still ~80 s of audio per piece ) print(f"[maya2] engine ready in {time.perf_counter() - t0:.1f}s", flush=True) return _tts @spaces.GPU(duration=90) # cold worker: ~48s engine init + a few s generation; warm: ~2s def generate( text: str, lang: str, voice: str, normalize: bool, seed: int, ref_audio=None, ref_text: str = "", ref_lang: str = "en", progress=gr.Progress(), ): """Synthesize speech from text in one of 12 Indic languages or English. Optionally clone a voice from a 3-10 s reference clip: upload the clip, give its exact transcript, and the same speaker will read your text — even in a different language. Returns 24 kHz mono audio. """ text = (text or "").strip() if not text: raise gr.Error("Please enter some text to synthesize.") if len(text) > MAX_CHARS: raise gr.Error(f"Text too long ({len(text)} chars); please keep it under {MAX_CHARS}.") tts = _get_tts() kw = {} if ref_audio: if not (ref_text or "").strip(): raise gr.Error("Voice cloning needs the reference clip's transcript (ref_text).") kw = dict(ref_audio=ref_audio, ref_text=ref_text, ref_lang=ref_lang) voice = None # engine forbids combining a named voice with ref_audio elif voice == "(none)": voice = None t0 = time.perf_counter() try: wav, info = tts.generate(text, lang=lang, voice=voice, normalize=normalize, seed=seed, return_info=True) except Exception as e: msg = str(e) code = getattr(e, "code", None) raise gr.Error(f"{code}: {msg}" if code else msg) dt = time.perf_counter() - t0 dur = len(wav) / 24000 print(f"[maya2] lang={lang} voice={voice or 'clone'} chars={len(text)} " f"audio={dur:.1f}s gpu={dt:.1f}s reason={info.get('reason')} frames={info.get('frames')}", flush=True) if len(wav) == 0: raise gr.Error("The model produced no audio — try rephrasing or raising max length.") return (24000, wav) CSS = """ main, .gradio-container {max-width: 900px !important; margin: 0 auto !important;} .dark .gradio-container {color: var(--body-text-color);} """ with gr.Blocks() as demo: gr.HTML("""
Multilingual streaming TTS — 12 Indic languages + English, with zero-shot voice cloning
2.5B params · Apache-2.0 · 24 kHz audio · model card
""") with gr.Row(): with gr.Column(scale=3): text_in = gr.Textbox( label="Text to speak", placeholder="Enter text in any of the 12 supported languages…", lines=4, ) with gr.Row(): lang_in = gr.Dropdown( choices=[(f"{LANG_NAMES[c]} ({c})", c) for c in LANGS], value="hi", label="Language", filterable=True, ) voice_in = gr.Dropdown( choices=["(none)"] + VOICES, value="Ritvik", label="Voice", filterable=True, info="Named voices ship with the model; or clone your own below.", ) audio_out = gr.Audio(label="Generated speech", type="filepath") btn = gr.Button("Generate speech", variant="primary") with gr.Column(scale=2): gr.Markdown("### Clone a voice (optional)") ref_audio = gr.Audio( label="Reference clip (3–10 s)", type="filepath", sources=["upload", "microphone"], format="wav", ) ref_text = gr.Textbox( label="Transcript of the reference clip", placeholder="What is said in the clip, exactly…", lines=2, ) ref_lang = gr.Dropdown( choices=[(f"{LANG_NAMES[c]} ({c})", c) for c in LANGS], value="hi", label="Reference language", ) gr.Markdown( "Upload a clip and its exact transcript; the same speaker " "will read your text — even in a different language." ) with gr.Accordion("Advanced options", open=False): normalize = gr.Checkbox( value=True, label="Normalize text (digits, currency, dates, URLs)", info="Recommended: the bundled normaliser expands numbers, dates and symbols into words.", ) seed = gr.Number( value=0, label="Seed (0 = random)", precision=0, minimum=0, maximum=2**31 - 1, ) gr.Examples( examples=[ ["क्या आपका वर्तमान पता 45, एमजी रोड, अंधेरी ईस्ट, मुंबई 400069 है? यदि हाँ, तो 1 दबाएं।", "hi", "Ritvik"], ["The EMI for your home loan is due by 12-10-24; please ensure your account has sufficient funds.", "en", "Siddhant"], ["જી બિલકુલ, મને સિસ્ટમમાં દેખાય છે કે તમારું ₹2,500 નું પેમેન્ટ 15મી તારીખે સક્સેસફુલ થઈ ગયું છે.", "gu", "Vaidehi"], ["ಹೌದಾ... ಅದು ನಿಜಕ್ಕೂ ಕಷ್ಟ. ನಿಮ್ಮ ಅಂತಿಮ ಬಿಲ್ ₹45,000 ಆಗಿದೆ, ಅದರಲ್ಲಿ ಇನ್ಶೂರೆನ್ಸ್ ಕವರ್ ಆಗಿದೆಯಾ ಅಂತ ಚೆಕ್ ಮಾಡ್ತೀನಿ.", "kn", "Satvik"], ["റദ്ദാക്കിയ ടിക്കറ്റിന്റെ റീഫണ്ട് തുകയായ ₹1,800 മൂന്ന് ദിവസത്തിനുള്ളിൽ ലഭിക്കും.", "ml", "Anirban"], ["मला 98765 43210 या नंबरवर OTP आला नाही, कृपया पुन्हा resend करा.", "mr", "Charvi"], ["ଦଯ଼ାକରି ମନେରଖନ୍ତୁ ଯେ EMI ପ୍ରତ୍ୟେକ ମାସର 5 ତାରିଖରେ କଟିବ ଏବଂ ଏହା ଆପଣଙ୍କର 3ୟ କିସ୍ତି।", "or", "Ahana"], ["NEFT ਰਾਹੀਂ ਫੰਡ ਟਰਾਂਸਫਰ 00:30 ਤੋਂ 23:30 ਤੱਕ ਉਪਲਬਧ ਹਨ।", "pa", "Malini"], ["கடந்த நிதியாண்டில் வங்கியின் மொத்த லாபம் ₹3.5 கோடி, இதில் ₹45 லட்சம் முதலீட்டாளர்களுக்கு வழங்கப்பட்டது.", "ta", "Abhay"], ["సేవింగ్స్ తో పోలిస్తే ఫిక్స్డ్ డిపాజిట్ వడ్డీ నిష్పత్తి 3:2 గా ఉండి, 7.25% ఇస్తుంది.", "te", "Nivedita"], ["এন ই এফ টি লেনদেনটো সম্পূৰ্ণ হ'বলৈ প্ৰায় 2 ঘণ্টা 30 মিনিট সময় লাগিব পাৰে।", "as", "Nakul"], ["12/08/2023 তারিখে আপনার 1,500 টাকার refund process হয়ে গেছে, status update চেক করুন।", "bn", "Jahnavi"], ], inputs=[text_in, lang_in, voice_in], fn=generate, outputs=[audio_out], cache_examples=True, cache_mode="lazy", label="Try the authors' showcase examples (one per supported language)", ) gr.Markdown( "Voice-cloning example: leave the examples alone, upload your own " "3–10 s clip in Clone a voice, type its exact transcript, then press Generate. " "A bundled reference clip (`samples/hi_ritvik.wav`) is in this Space's files for inspiration." ) btn.click( generate, inputs=[text_in, lang_in, voice_in, normalize, seed, ref_audio, ref_text, ref_lang], outputs=[audio_out], api_name="generate", concurrency_limit=1, ) if __name__ == "__main__": demo.launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)