import random import numpy as np import torch from src.chatterbox.mtl_tts import ChatterboxMultilingualTTS, SUPPORTED_LANGUAGES import gradio as gr import spaces DEVICE = "cuda" if torch.cuda.is_available() else "cpu" print(f"🚀 Запущено на устройстве: {DEVICE}") # --- Глобальная инициализация модели --- MODEL = None LANGUAGE_CONFIG = { "ar": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/ar_f/ar_prompts2.flac", "text": "في الشهر الماضي، وصلنا إلى معلم جديد بمليارين من المشاهدات على قناتنا على يوتيوب." }, "da": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/da_m1.flac", "text": "Sidste måned nåede vi en ny milepæl med to milliarder visninger på vores YouTube-kanal." }, "de": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/de_f1.flac", "text": "Letzten Monat haben wir einen neuen Meilenstein erreicht: zwei Milliarden Aufrufe auf unserem YouTube-Kanal." }, "el": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/el_m.flac", "text": "Τον περασμένο μήνα, φτάσαμε σε ένα νέο ορόσημο με δύο δισεκατομμύρια προβολές στο κανάλι μας στο YouTube." }, "en": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/en_f1.flac", "text": "Last month, we reached a new milestone with two billion views on our YouTube channel." }, "es": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/es_f1.flac", "text": "El mes pasado alcanzamos un nuevo hito: dos mil millones de visualizaciones en nuestro canal de YouTube." }, "fi": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/fi_m.flac", "text": "Viime kuussa saavutimme uuden virstanpylvään kahden miljardin katselukerran kanssa YouTube-kanavallamme." }, "fr": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/fr_f1.flac", "text": "Le mois dernier, nous avons atteint un nouveau jalon avec deux milliards de vues sur notre chaîne YouTube." }, "he": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/he_m1.flac", "text": "בחודש שעבר הגענו לאבן דרך חדשה עם שני מיליארד צפיות בערוץ היוטיוב שלנו." }, "hi": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/hi_f1.flac", "text": "पिछले महीने हमने एक नया मील का पत्थर छुआ: हमारे YouTube चैनल पर दो अरब व्यूज़।" }, "it": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/it_m1.flac", "text": "Il mese scorso abbiamo raggiunto un nuovo traguardo: due miliardi di visualizzazioni sul nostro canale YouTube." }, "ja": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/ja/ja_prompts1.flac", "text": "先月、私たちのYouTubeチャンネルで二十億回の再生回数という新たなマイルストーンに到達しました。" }, "ko": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/ko_f.flac", "text": "지난달 우리는 유튜브 채널에서 이십억 조회수라는 새로운 이정표에 도달했습니다." }, "ms": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/ms_f.flac", "text": "Bulan lepas, kami mencapai pencapaian baru dengan dua bilion tontonan di saluran YouTube kami." }, "nl": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/nl_m.flac", "text": "Vorige maand bereikten we een nieuwe mijlpaal met twee miljard weergaven op ons YouTube-kanaal." }, "no": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/no_f1.flac", "text": "Forrige måned nådde vi en ny milepæl med to milliarder visninger på YouTube-kanalen vår." }, "pl": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/pl_m.flac", "text": "W zeszłym miesiącu osiągnęliśmy nowy kamień milowy z dwoma miliardami wyświetleń na naszym kanale YouTube." }, "pt": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/pt_m1.flac", "text": "No mês passado, alcançámos um novo marco: dois mil milhões de visualizações no nosso canal do YouTube." }, "ru": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/ru_m.flac", "text": "В прошлом месяце мы достигли нового рубежа: два миллиарда просмотров на нашем YouTube-канале." }, "sv": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/sv_f.flac", "text": "Förra månaden nådde vi en ny milstolpe med två miljarder visningar på vår YouTube-kanal." }, "sw": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/sw_m.flac", "text": "Mwezi uliopita, tulifika hatua mpya ya maoni ya bilioni mbili kweny kituo chetu cha YouTube." }, "tr": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/tr_m.flac", "text": "Geçen ay YouTube kanalımızda iki milyar görüntüleme ile yeni bir dönüm noktasına ulaştık." }, "zh": { "audio": "https://storage.googleapis.com/chatterbox-demo-samples/mtl_prompts/zh_f2.flac", "text": "上个月,我们达到了一个新的里程碑。 我们的YouTube频道观看次数达到了二十亿次,这绝对令人难以置信。" }, } # --- Вспомогательные функции UI --- def default_audio_for_ui(lang: str) -> str | None: return LANGUAGE_CONFIG.get(lang, {}).get("audio") def default_text_for_ui(lang: str) -> str: return LANGUAGE_CONFIG.get(lang, {}).get("text", "") def get_supported_languages_display() -> str: """Генерирует форматированный список поддерживаемых языков.""" language_items = [] for code, name in sorted(SUPPORTED_LANGUAGES.items()): language_items.append(f"**{name}** (`{code}`)") # Разделяем на 2 строки mid = len(language_items) // 2 line1 = " • ".join(language_items[:mid]) line2 = " • ".join(language_items[mid:]) return f""" ### 🌍 Поддерживаемые языки ({len(SUPPORTED_LANGUAGES)} всего) {line1} {line2} """ def get_or_load_model(): """Загружает модель ChatterboxMultilingualTTS, если она еще не загружена, и гарантирует, что она находится на правильном устройстве.""" global MODEL if MODEL is None: print("Модель не загружена, инициализация...") try: MODEL = ChatterboxMultilingualTTS.from_pretrained(DEVICE) if hasattr(MODEL, 'to') and str(MODEL.device) != DEVICE: MODEL.to(DEVICE) print(f"Модель успешно загружена. Внутреннее устройство: {getattr(MODEL, 'device', 'N/A')}") except Exception as e: print(f"Ошибка загрузки модели: {e}") raise return MODEL # Пытаемся загрузить модель при запуске. try: get_or_load_model() except Exception as e: print(f"КРИТИЧЕСКАЯ ОШИБКА: Не удалось загрузить модель при запуске. Приложение может не работать. Ошибка: {e}") def set_seed(seed: int): """Устанавливает случайное зерно для воспроизводимости в torch, numpy и random.""" torch.manual_seed(seed) if DEVICE == "cuda": torch.cuda.manual_seed(seed) torch.cuda.manual_seed_all(seed) random.seed(seed) np.random.seed(seed) def resolve_audio_prompt(language_id: str, provided_path: str | None) -> str | None: """ Решает, какой аудиопромпт использовать: - Если пользователь указал путь (загрузка/микрофон/url), использует его. - Иначе, использует языкозависимый промпт по умолчанию (если есть). """ if provided_path and str(provided_path).strip(): return provided_path return LANGUAGE_CONFIG.get(language_id, {}).get("audio") @spaces.GPU def generate_tts_audio( text_input: str, language_id: str, audio_prompt_path_input: str = None, exaggeration_input: float = 0.6, temperature_input: float = 0.7, seed_num_input: int = 0, cfgw_input: float = 0.7 ) -> tuple[int, np.ndarray]: """ Генерирует высококачественную речь из текста с использованием многоязычной модели Chatterbox с опциональным стилем референсного аудио. Поддерживаемые языки: Английский, Французский, Немецкий, Испанский, Итальянский, Португальский и Хинди. Этот инструмент синтезирует естественно звучащую речь из входного текста. Когда предоставляется референсный аудиофайл, он захватывает характеристики голоса и манеру речи говорящего. Сгенерированное аудио сохраняет просодию, тон и вокальные качества референсного говорящего или использует голос по умолчанию, если референс не предоставлен. Аргументы: text_input (str): Текст для синтеза в речь (максимум 300 символов) language_id (str): Код языка для синтеза (например, en, fr, de, es, it, pt, hi) audio_prompt_path_input (str, опционально): Путь к файлу или URL референсного аудио, определяющего целевой стиль голоса. По умолчанию None. exaggeration_input (float, опционально): Управляет выразительностью речи (0.25-2.0, нейтрально=0.6, экстремальные значения могут быть нестабильны). По умолчанию 0.6. temperature_input (float, опционально): Управляет случайностью в генерации (0.05-5.0, выше=более разнообразно). По умолчанию 0.7. seed_num_input (int, опционально): Случайное зерно для воспроизводимых результатов (0 для случайной генерации). По умолчанию 0. cfgw_input (float, опционально): Вес CFG/Pace, управляющий направляющей генерации (0.2-1.0). По умолчанию 0.7, 0 для переноса языка. Возвращает: tuple[int, np.ndarray]: Кортеж, содержащий частоту дискретизации (int) и сгенерированную аудиоволну (numpy.ndarray) """ current_model = get_or_load_model() if current_model is None: raise RuntimeError("Модель TTS не загружена.") if seed_num_input != 0: set_seed(int(seed_num_input)) print(f"Генерация аудио для текста: '{text_input[:50]}...'") # Обработка опционального аудиопромпта chosen_prompt = audio_prompt_path_input or default_audio_for_ui(language_id) generate_kwargs = { "exaggeration": exaggeration_input, "temperature": temperature_input, "cfg_weight": cfgw_input, } if chosen_prompt: generate_kwargs["audio_prompt_path"] = chosen_prompt print(f"Используется аудиопромпт: {chosen_prompt}") else: print("Аудиопромпт не предоставлен; используется голос по умолчанию.") wav = current_model.generate( text_input[:500], # Обрезаем текст до макс. символов language_id=language_id, **generate_kwargs ) print("Генерация аудио завершена.") return (current_model.sr, wav.squeeze(0).numpy()) with gr.Blocks() as demo: gr.Markdown( """ # Демо Chatterbox Multilingual Генерация высококачественной многоязычной речи из текста со стилизацией под референсное аудио, поддерживает 23 языка. Для размещенной версии Chatterbox Multilingual и для дообучения посетите [resemble.ai](https://app.resemble.ai) """ ) # Отображение поддерживаемых языков gr.Markdown(get_supported_languages_display()) with gr.Row(): with gr.Column(): initial_lang = "ru" text = gr.Textbox( value=default_text_for_ui(initial_lang), label="Текст для синтеза (максимум 300 символов)", max_lines=5 ) language_id = gr.Dropdown( choices=list(ChatterboxMultilingualTTS.get_supported_languages().keys()), value=initial_lang, label="Язык", info="Выберите язык для синтеза речи из текста" ) ref_wav = gr.Audio( sources=["upload", "microphone"], type="filepath", label="Референсное аудио (Опционально)", value=default_audio_for_ui(initial_lang) ) gr.Markdown( "💡 **Примечание**: Убедитесь, что референсный клип соответствует указанному языку. В противном случае результаты переноса языка могут унаследовать акцент языка референсного клипа. Чтобы избежать этого, установите вес CFG на 0.", elem_classes=["audio-note"] ) exaggeration = gr.Slider( 0.25, 2, step=.05, label="Экспрессивность (Нейтрально = 0.5, экстремальные значения могут быть нестабильны)", value=0.6 ) cfg_weight = gr.Slider( 0.2, 1, step=.05, label="CFG/Темп", value=0.7 ) with gr.Accordion("Дополнительные настройки", open=False): seed_num = gr.Number(value=0, label="Случайное зерно (0 для случайной генерации)") temp = gr.Slider(0.05, 5, step=.05, label="Температура", value=0.7) run_btn = gr.Button("Сгенерировать", variant="primary") with gr.Column(): audio_output = gr.Audio(label="Выходное аудио") def on_language_change(lang, current_ref, current_text): return default_audio_for_ui(lang), default_text_for_ui(lang) language_id.change( fn=on_language_change, inputs=[language_id, ref_wav, text], outputs=[ref_wav, text], show_progress=False ) run_btn.click( fn=generate_tts_audio, inputs=[ text, language_id, ref_wav, exaggeration, temp, seed_num, cfg_weight, ], outputs=[audio_output], ) demo.launch(mcp_server=True)