"""Gradio demo for the Dialogs-RU expressive Russian TTS model (VITS2). Free CPU demo. Type Russian text, pick a voice and an emotion, and the model speaks. Stress (´) is placed automatically; you can also mark it yourself with a ``+`` before the stressed vowel (e.g. "прив+ет"). """ import os # Keep native thread pools small and avoid duplicate-OpenMP aborts between # torch and onnxruntime (ruaccent) on the small CPU Space. os.environ.setdefault("OMP_NUM_THREADS", "2") os.environ.setdefault("KMP_DUPLICATE_LIB_OK", "TRUE") os.environ.setdefault("TOKENIZERS_PARALLELISM", "false") import gradio as gr from tts import DialogsTTS, SPEAKERS, EMOTIONS # ---------------------------------------------------------------------------- # Load the model once at startup. # ---------------------------------------------------------------------------- TTS = DialogsTTS() EMOJI = { "neutral": "😐", "happy": "😊", "surprise": "😲", "arrogance": "😏", "yawn": "🥱", "fear": "😨", "laughing": "😄", "whisper": "🤫", "disgust": "🤢", "angry": "😠", "sad": "😢", "tongue-twister": "👅", "poem": "📜", } SPEAKER_LABEL_TO_ID = { f"{'👩' if g == 'female' else '👨'} {en} / {ru}": sid for sid, (en, ru, g) in SPEAKERS.items() } EMOTION_LABEL_TO_ID = { f"{EMOJI.get(en, '🎭')} {en} / {ru}": eid for eid, (en, ru) in EMOTIONS.items() } SPEAKER_CHOICES = list(SPEAKER_LABEL_TO_ID.keys()) EMOTION_CHOICES = list(EMOTION_LABEL_TO_ID.keys()) DESCRIPTION = """ # 🎭 Dialogs-RU · Expressive Russian Text-to-Speech A **VITS2** speech synthesizer trained on **[Dialogs](https://huggingface.co/datasets/langswap/dialogs-ru-emotional-conversations)**, a studio-quality, expressive, conversational **Russian** speech corpus. Choose one of **three studio voices** and one of **13 emotional styles**, type some Russian, and press **Generate**. Runs on **CPU** — free for everyone to try. 🎉 - 📚 **Dataset:** [langswap/dialogs-ru-emotional-conversations](https://huggingface.co/datasets/langswap/dialogs-ru-emotional-conversations) · OpenRAIL - 🧠 **Model weights:** [frappuccino/dialogs-ru-vits2](https://huggingface.co/frappuccino/dialogs-ru-vits2) - 💻 **Training code:** [github.com/shigabeev/vits2-emotional](https://github.com/shigabeev/vits2-emotional) - 📄 **Paper:** *Dialogs: a studio-quality expressive conversational Russian speech corpus for dialog assistants* (Shigabeev & Latyshev, Langswap) """ TIPS = """ ### Tips - **Numbers, dates, money, units & abbreviations are read out in full** — normalized automatically with [`rutextnorm`](https://github.com/shigabeev/russian_tts_normalization) (e.g. `7,5%` → «семь целых и пять десятых процента», `№5` → «номер пять»). - **Stress matters in Russian.** Stress is placed automatically with [`ruaccent`](https://github.com/Den4ikAI/ruaccent). To override it, put a `+` right before the stressed vowel: `за́мок` → `з+амок`, `замо́к` → `зам+ок`. - **Voices:** Masha & Sveta are female, Dima is male. - **Styles** like *laughing*, *whisper*, *poem* or *tongue-twister* are rarer in the data, so they are more subtle. *Neutral*, *happy* and *sad* are the strongest. - This is a research proof-of-concept (UTMOS ≈ 3.36); expect lively, conversational prosody rather than perfectly clean studio audio. """ EXAMPLES = [ ["Привет! Меня зовут Маша. Чем могу помочь?", "👩 Masha / Маша", "😊 happy / радостный", True, True], ["Я так устала сегодня... совсем нет сил.", "👩 Sveta / Света", "😢 sad / грусть", True, True], ["В 2024 году цена выросла на 7,5%, до 1 500 руб.", "👨 Dima / Дима", "😐 neutral / нейтральный", True, True], ["Ты это серьёзно? Я даже не удивлена.", "👩 Masha / Маша", "😏 arrogance / высокомерие", True, True], ["Тише, дети уже спят, говори шёпотом.", "👩 Sveta / Света", "🤫 whisper / шёпот", True, True], ["Карл у Клары украл кораллы, а Клара у Карла украла кларнет.", "👨 Dima / Дима", "👅 tongue-twister / скороговорка", True, True], ] def generate(text, speaker_label, emotion_label, normalize, auto_stress, speed, expressiveness): speaker_id = SPEAKER_LABEL_TO_ID.get(speaker_label, 0) emotion_id = EMOTION_LABEL_TO_ID.get(emotion_label, 0) length_scale = 1.0 / max(0.1, float(speed)) try: sr, audio, used = TTS.synthesize( text, speaker_id=speaker_id, emotion_id=emotion_id, normalize=bool(normalize), auto_stress=bool(auto_stress), noise_scale=float(expressiveness), length_scale=length_scale, ) except ValueError as e: raise gr.Error(str(e)) return (sr, audio), used with gr.Blocks(title="Dialogs-RU · Expressive Russian TTS", theme=gr.themes.Default()) as demo: gr.Markdown(DESCRIPTION) with gr.Row(): with gr.Column(scale=3): text = gr.Textbox( label="Russian text / Текст", value="Привет! Это демо выразительного русского синтеза речи.", lines=3, placeholder="Введите текст на русском…", ) with gr.Row(): speaker = gr.Dropdown( SPEAKER_CHOICES, value=SPEAKER_CHOICES[0], label="Voice / Голос" ) emotion = gr.Dropdown( EMOTION_CHOICES, value=EMOTION_CHOICES[0], label="Emotion / Эмоция" ) normalize = gr.Checkbox( value=True, label="Normalize numbers, dates & abbreviations · нормализация (rutextnorm)", ) auto_stress = gr.Checkbox( value=True, label="Auto stress (ruaccent) · авто-ударение" ) with gr.Accordion("Advanced", open=False): speed = gr.Slider(0.5, 1.8, value=1.0, step=0.05, label="Speed / Скорость") expressiveness = gr.Slider( 0.0, 1.0, value=0.667, step=0.01, label="Expressiveness (noise_scale) / Выразительность", ) btn = gr.Button("🔊 Generate / Озвучить", variant="primary") with gr.Column(scale=2): audio_out = gr.Audio(label="Output / Результат", type="numpy") used_text = gr.Textbox( label="Text sent to the model (normalized + stressed)", interactive=False ) gr.Markdown(TIPS) btn.click( generate, inputs=[text, speaker, emotion, normalize, auto_stress, speed, expressiveness], outputs=[audio_out, used_text], ) gr.Examples( examples=EXAMPLES, inputs=[text, speaker, emotion, normalize, auto_stress], label="Examples / Примеры", ) gr.Markdown( "Model & demo released under **OpenRAIL** · " "trained on the [Dialogs](https://huggingface.co/datasets/langswap/dialogs-ru-emotional-conversations) corpus · " "[training code](https://github.com/shigabeev/vits2-emotional)" ) if __name__ == "__main__": demo.queue(max_size=20).launch(server_name="0.0.0.0", server_port=7860)