Spaces:
Running on Zero
Running on Zero
Download space/app.py from hugging-apps/maya2-native: direct link, hf CLI and curl.
- Browser
- Download file 11 kB
-
https://huggingface.co/spaces/hugging-apps/maya2-native/resolve/main/space/app.py
- Command line
-
hf download hf://spaces/hugging-apps/maya2-native/space/app.py
-
curl -L -o app.py https://huggingface.co/spaces/hugging-apps/maya2-native/resolve/main/space/app.py
11 kB
| """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 | |
| 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 | |
| info = {} | |
| t0 = time.perf_counter() | |
| try: | |
| wav = tts.generate(text, lang=lang, voice=voice, normalize=normalize, seed=seed, info=info) | |
| 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 = """ | |
| .gradio-container {max-width: 900px !important; margin: 0 auto !important;} | |
| """ | |
| with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo: | |
| gr.HTML(""" | |
| <div style="display: flex; align-items: center; gap: 12px; justify-content: center; margin-bottom: 4px;"> | |
| <div style="font-size: 44px; line-height: 1;">🗣️</div> | |
| <div> | |
| <h1 style="margin: 0; font-size: 28px;">Maya 2 Native</h1> | |
| <p style="margin: 0; opacity: 0.75;">Multilingual streaming TTS — 12 Indic languages + English, with zero-shot voice cloning</p> | |
| </div> | |
| </div> | |
| <p style="text-align: center; opacity: 0.7; margin-top: 0;"> | |
| 2.5B params · Apache-2.0 · 24 kHz audio · <a href="https://huggingface.co/maya-research/maya2-native" target="_blank">model card</a> | |
| </p> | |
| """) | |
| 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( | |
| "<span style='opacity:0.7'>Upload a clip and its exact transcript; the same speaker " | |
| "will read your text — even in a different language.</span>" | |
| ) | |
| 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( | |
| "<span style='opacity:0.65'>Voice-cloning example: leave the examples alone, upload your own " | |
| "3–10 s clip in <b>Clone a voice</b>, type its exact transcript, then press Generate. " | |
| "A bundled reference clip (`samples/hi_ritvik.wav`) is in this Space's files for inspiration.</span>" | |
| ) | |
| 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(mcp_server=True) | |