Kremon96's picture
Update app.py
80b73f0 verified
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)