Spaces:
Running on Zero
Running on Zero
File size: 11,178 Bytes
6784f71 b2e7faf 6784f71 b2e7faf 6784f71 b2e7faf 6784f71 38a247d 6784f71 78a2051 6784f71 38a247d 6784f71 38a247d 6784f71 b2e7faf 6784f71 38a247d | 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 237 238 | """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("""
<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(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)
|