"""Polyglot Me — HF Space for App 2 (Thousand Token Wood). Record ~10s of your voice, type a line, hear yourself across English / Hindi / Telugu / Tamil. VoxCPM2 (OpenBMB) clones your voice for every language; Sarvam translates. All on Modal; this Space is CPU-only. Hackathon: Thousand Token Wood · OpenBMB · Modal. """ from __future__ import annotations import io import gradio as gr import matplotlib import modal import numpy as np import soundfile as sf from theme import build_css matplotlib.use("Agg") import matplotlib.pyplot as plt # noqa: E402 MODAL_APP = "praxy-voice" LANGS = ["en", "hi", "te", "ta"] LANG_NAMES = {"en": "English", "hi": "Hindi", "te": "Telugu", "ta": "Tamil"} LANG_SCRIPT = {"en": "Aa", "hi": "हि", "te": "తె", "ta": "த"} LANG_LABEL = {"en": "LATIN", "hi": "DEVANAGARI", "te": "TELUGU", "ta": "TAMIL"} # Header colours: WCAG-AA verified with white text (≥5:1). Jewel tones on dark. LANG_HEADER = {"en": "#3B5998", "hi": "#C2410C", "te": "#0F766E", "ta": "#991B1B"} # Brighter accents for the matplotlib share-card waveforms (graphics, not text). LANG_WAVE = {"en": "#8B7FF9", "hi": "#FF7A7A", "te": "#4FC878", "ta": "#5BB8F2"} _TRANSLATOR = modal.Cls.from_name(MODAL_APP, "SarvamTranslator") _VOX = modal.Cls.from_name(MODAL_APP, "VoxCPM2Cloner") def _make_card(lines: dict, wav_paths: dict) -> str: order = ["English", "Hindi", "Telugu", "Tamil"] wave = {LANG_NAMES[l]: LANG_WAVE[l] for l in LANGS} fig = plt.figure(figsize=(10, 6.5), facecolor="#0E1020") fig.text(.5, .965, "Polyglot Me", ha="center", va="top", color="white", fontsize=23, fontweight="bold") fig.text(.5, .905, "one voice · four languages", ha="center", va="top", color="#8E8FB4", fontsize=12) for i, lang in enumerate(order): ax = fig.add_subplot(4, 1, i + 1); ax.set_facecolor("#0E1020") c = wave[lang]; path = wav_paths.get(lang) if path: arr, _ = sf.read(path, dtype="float32") xs = np.linspace(0, 1, len(arr)) ax.fill_between(xs, arr, alpha=.4, color=c); ax.plot(xs, arr, color=c, lw=.6, alpha=.9) ax.set_xlim(0, 1) ax.text(-.01, .5, lang, transform=ax.transAxes, ha="right", va="center", color=c, fontsize=11, fontweight="bold") snip = lines.get(lang, "") if len(snip) > 64: snip = snip[:61] + "…" ax.text(.015, .5, snip, transform=ax.transAxes, ha="left", va="center", color="white", fontsize=9.5, alpha=.88) ax.set_xticks([]); ax.set_yticks([]) for sp in ax.spines.values(): sp.set_visible(False) ax.axvline(0, color=c, lw=4, solid_capstyle="round") fig.subplots_adjust(left=.13, right=.98, top=.87, bottom=.03, hspace=.1) out = "/tmp/polyglot_card.png" fig.savefig(out, dpi=160, bbox_inches="tight", facecolor="#0E1020"); plt.close(fig) return out def _lang_header_html(lang: str) -> str: return ( f'
' f'
{LANG_SCRIPT[lang]}
' f'
' f'
{LANG_NAMES[lang]}
' f'
{LANG_LABEL[lang]} SCRIPT
') def generate(ref_audio_path, line): empty = [None, None, None, None, None, "Record a clip and type a line.", ""] if not ref_audio_path or not (line and line.strip()): return empty with open(ref_audio_path, "rb") as f: ref_bytes = f.read() translated = _TRANSLATOR().translate.remote(line, "en", ["hi", "te", "ta"]) lines = {"en": line, **translated} outs = [] for lang in LANGS: wb, _ = _VOX().clone.remote(text=lines[lang], ref_audio_bytes=ref_bytes) path = f"/tmp/polyglot_{lang}.wav" with open(path, "wb") as f: f.write(wb) outs.append(path) card = _make_card({LANG_NAMES[l]: lines[l] for l in LANGS}, {LANG_NAMES[l]: outs[i] for i, l in enumerate(LANGS)}) transcript = "\n".join(f"{LANG_NAMES[l]}: {lines[l]}" for l in LANGS) caption = ('I typed one line and heard myself say it in four languages 🎙️\n\n' f'"{line}"\n\n' + transcript + "\n\nBuilt with VoxCPM2 + Praxy for #BuildSmall #HuggingFace #PolyglotMe") return outs + [card, transcript, caption] HERO = """
🎙️

Polyglot Me

Record ten seconds of your voice — hear yourself speak English, Hindi, Telugu, and Tamil.

ENGLISH हिन्दी తెలుగు தமிழ்
VoxCPM2 · OPENBMB MODAL · SERVERLESS

⏳ First run warms the model on Modal (~2–4 min). After that it's quick.

""" EXTRA = """ @keyframes gsh {0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}} @keyframes orb {0%,100%{transform:scale(1) translateY(0)}50%{transform:scale(1.06) translateY(-6px)}} .lang-card{padding:0!important;overflow:hidden!important;} #share-card img{border-radius:16px!important;border:1px solid rgba(255,255,255,.1)!important;} #caption-out textarea{border-left:3px solid #5BB8F2!important;font-size:13px!important;line-height:1.8!important;} #transcript-out textarea{font-size:13px!important;} .rainbow-hr{height:2px;background:linear-gradient(90deg,#3B5998,#C2410C,#0F766E,#991B1B);border:none;margin:18px 0;opacity:.5;border-radius:2px;} """ with gr.Blocks(title="Polyglot Me") as demo: gr.HTML(f"") gr.HTML(HERO) with gr.Row(): ref_in = gr.Audio(sources=["microphone", "upload"], type="filepath", label="Your voice (~10 seconds)", scale=1) line_in = gr.Textbox(label="Say something (in English)", lines=3, scale=2, placeholder="Good morning Amma, hope you slept well.") speak_btn = gr.Button("🌍 Speak it in 4 languages", variant="primary", elem_id="cta") gr.HTML('
') audios = {} with gr.Row(): for lang in LANGS: with gr.Column(elem_classes=["lang-card"]): gr.HTML(_lang_header_html(lang)) audios[lang] = gr.Audio(label="", type="filepath", show_label=False) share_card = gr.Image(label="Share card", type="filepath", elem_id="share-card") with gr.Row(): transcript_out = gr.Textbox(label="Translations", lines=4, elem_id="transcript-out", scale=1) caption_out = gr.Textbox(label="Caption (copy for your post)", lines=6, elem_id="caption-out", scale=1) speak_btn.click(generate, inputs=[ref_in, line_in], outputs=[audios["en"], audios["hi"], audios["te"], audios["ta"], share_card, transcript_out, caption_out]) if __name__ == "__main__": demo.launch()