Spaces:
Paused
Paused
| 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") | |
| 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) |