sonic-tts-webui / app.py
daswer123's picture
Upload 3 files
643e012 verified
raw
history blame
19.8 kB
from typing import List
import gradio as gr
from pathlib import Path
from sonic_api_wrapper import CartesiaVoiceManager, VoiceAccessibility, improve_tts_text
import os
import json
# Инициализация базовых переменных
DEFAULT_API_KEY = ""
LANGUAGE_CHOICES = ["all", "ru", "en", "es", "pl", "de", "fr"]
ACCESS_TYPE_MAP = {
"Все": VoiceAccessibility.ALL,
"Только кастомные": VoiceAccessibility.ONLY_CUSTOM,
"Апи": VoiceAccessibility.ONLY_PUBLIC
}
# Обновленные константы
SPEED_CHOICES = ["Очень медленно", "Медленно", "Нормально", "Быстро", "Очень быстро"]
EMOTION_CHOICES = ["Нейтрально", "Весело", "Грустно", "Злобно", "Удивленно", "Любопытно"]
EMOTION_INTENSITY = ["Очень слабая", "Слабая", "Средняя", "Сильная", "Очень сильная"]
# Глобальная переменная для хранения экземпляра менеджера
manager = None
import datetime
def map_speed(speed_type: str) -> float:
speed_map = {
"Очень медленно": -1.0,
"Медленно": -0.5,
"Нормально": 0.0,
"Быстро": 0.5,
"Очень быстро": 1.0
}
return speed_map[speed_type]
def generate_output_filename(language: str) -> str:
"""Генерация имени файла с временной меткой и языком"""
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
return f"output/{timestamp}_{language}.wav"
def extract_voice_id_from_label(voice_label: str) -> str:
"""
Извлекает ID голоса из метки в dropdown
Например: "John (en) [Custom]" -> извлечет ID из словаря голосов
"""
if not manager:
return None
# Получаем все голоса и их метки
choices = manager.get_voice_choices()
# Находим голос по метке и берем его ID
voice_data = next((c for c in choices if c["label"] == voice_label), None)
return voice_data["value"] if voice_data else None
def initialize_manager(api_key: str) -> str:
global manager
try:
manager = CartesiaVoiceManager(api_key=api_key, base_dir=Path("voice2voice"))
return "✅ Менеджер инициализирован"
except Exception as e:
return f"❌ Ошибка: {str(e)}"
def get_initial_voices():
"""Получение начального списка голосов"""
if not manager:
initialize_manager(DEFAULT_API_KEY)
choices = manager.get_voice_choices()
return [c["label"] for c in choices], choices[0]["label"] if choices else None
def update_voice_list (language: str, access_type: str, current_voice: str = None):
"""
Обновление списка голосов с сохранением текущего выбора
"""
if not manager:
return gr.update(choices=[], value=None), "❌ Менеджер не инициализирован"
try:
choices = manager.get_voice_choices(
language=None if language == "all" else language,
accessibility=ACCESS_TYPE_MAP[access_type]
)
# Преобразуем в список меток
choice_labels = [c["label"] for c in choices]
# Определяем значение для выбора
if current_voice in choice_labels:
# Сохраняем текущий выбор, если он доступен
new_value = current_voice
else:
# Иначе берем первый доступный голос
new_value = choice_labels[0] if choice_labels else None
return gr.update(choices=choice_labels, value=new_value), "✅ Список голосов обновлен"
except Exception as e:
return gr.update(choices=[], value=None), f"❌ Ошибка: {str(e)}"
def update_voice_info(voice_label: str) -> str:
"""Обновление информации о голосе"""
if not manager or not voice_label:
return ""
try:
voice_id = extract_voice_id_from_label(voice_label)
if not voice_id:
return "❌ Голос не найден"
info = manager.get_voice_info(voice_id)
return (
f"Имя: {info['name']}\n"
f"Язык: {info['language']}\n"
f"Тип: {'Кастомный' if info.get('is_custom') else 'API'}\n"
f"ID: {info['id']}"
)
except Exception as e:
return f"❌ Ошибка: {str(e)}"
def create_custom_voice(name: str, language: str, audio_data: tuple) -> tuple:
"""
Создание кастомного голоса и обновление списка голосов
Возвращает: (статус, обновленный dropdown, информация о голосе)
"""
if not manager:
return "❌ Менеджер не инициализирован", gr.update(), ""
if not name or not audio_data:
return "❌ Необходимо указать имя и файл голоса", gr.update(), ""
try:
# Получаем путь к аудио файлу
audio_path = audio_data[0] if isinstance(audio_data, tuple) else audio_data
# Создаем голос
voice_id = manager.create_custom_embedding(
file_path=audio_path,
name=name,
language=language
)
print(voice_id)
# Получаем обновленный список голосов
choices = manager.get_voice_choices()
choice_labels = [c["label"] for c in choices]
# Находим метку для нового голоса
new_voice_label = next(c["label"] for c in choices if c["value"] == voice_id)
# Получаем информацию о новом голосе
voice_info = manager.get_voice_info(voice_id)
info_text = (
f"Имя: {voice_info['name']}\n"
f"Язык: {voice_info['language']}\n"
f"Тип: Кастомный\n"
f"ID: {voice_info['id']}"
)
return (
f"✅ Создан кастомный голос: {voice_id}",
gr.update(choices=choice_labels, value=new_voice_label),
info_text
)
except Exception as e:
return f"❌ Ошибка создания голоса: {str(e)}", gr.update(), ""
def on_auto_language_change(auto_language: bool):
"""Обработчик изменения галочки автоопределения языка"""
return gr.update(visible=not auto_language)
def map_emotions(selected_emotions, intensity):
emotion_map = {
"Весело": "positivity",
"Грустно": "sadness",
"Злобно": "anger",
"Удивленно": "surprise",
"Любопытно": "curiosity"
}
intensity_map = {
"Очень слабая": "lowest",
"Слабая": "low",
"Средняя": "medium",
"Сильная": "high",
"Очень сильная": "highest"
}
emotions = []
for emotion in selected_emotions:
if emotion == "Нейтрально":
continue
if emotion in emotion_map:
emotions.append({
"name": emotion_map[emotion],
"level": intensity_map[intensity]
})
return emotions
def generate_speech(
text: str,
voice_label: str,
improve_text: bool,
auto_language: bool,
manual_language: str,
speed_type: str,
use_custom_speed: bool,
custom_speed: float,
emotions: List[str],
emotion_intensity: str
):
"""Генерация речи с учетом настроек языка"""
if not manager:
return None, "❌ Менеджер не инициализирован"
if not text or not voice_label:
return None, "❌ Необходимо указать текст и голос"
try:
# Извлекаем ID голоса из метки
voice_id = extract_voice_id_from_label(voice_label)
if not voice_id:
return None, "❌ Голос не найден"
# Устанавливаем голос по ID
manager.set_voice(voice_id)
# Если автоопределение выключено, устанавливаем язык вручную
if not auto_language:
manager.set_language(manual_language)
# В функции generate_speech обновите установку скорости:
if use_custom_speed:
manager.speed = custom_speed
else:
manager.speed = map_speed(speed_type)
# Установка эмоций
emotion_map = {
"Нейтрально": None,
"Весело": "positivity",
"Грустно": "sadness",
"Злобно": "anger",
"Удивленно": "surprise",
"Любопытно": "curiosity"
}
intensity_map = {
"Слабая": "low",
"Средняя": "medium",
"Сильная": "high"
}
if emotions and emotions != ["Нейтрально"]:
manager.set_emotions(map_emotions(emotions, emotion_intensity))
else:
manager.set_emotions() # Сброс эмоций
# Генерация имени файла
output_file = generate_output_filename(
manual_language if not auto_language else manager.current_language
)
# Создаем директорию для выходных файлов, если её нет
os.makedirs("output", exist_ok=True)
# Генерация речи
output_path = manager.speak(
text=text if not improve_text else improve_tts_text(text, manager.current_language),
output_file=output_file
)
return output_path, "✅ Аудио сгенерировано успешно"
except Exception as e:
return None, f"❌ Ошибка генерации: {str(e)}"
# Создание интерфейса
with gr.Blocks() as demo:
# API ключ
cartesia_api_key = gr.Textbox(
label="API ключ Cartesia",
value=DEFAULT_API_KEY,
type='password'
)
with gr.Row():
# Левая колонка
with gr.Column():
cartesia_text = gr.TextArea(label="Текст")
with gr.Accordion(label="Настройки", open=True):
# Фильтры
with gr.Accordion("Фильтры", open=True):
cartesia_setting_filter_lang = gr.Dropdown(
label="Язык",
choices=LANGUAGE_CHOICES,
value="all"
)
cartesia_setting_filter_type = gr.Dropdown(
label="Тип",
choices=ACCESS_TYPE_MAP,
value="Все"
)
# Вкладки настроек
with gr.Tab("Стандарт"):
cartesia_setting_voice_info = gr.Textbox(
label="Информация о голосе",
interactive=False
)
with gr.Row():
initial_choices, initial_value = get_initial_voices()
cartesia_setting_voice = gr.Dropdown(
label="Голос",
choices=initial_choices,
value=initial_value
)
cartesia_setting_voice_update = gr.Button("Обновить")
cartesia_setting_auto_language = gr.Checkbox(
label="Автоматически определять язык из голоса",
value=True
)
cartesia_setting_manual_language = gr.Dropdown(
label="Язык озвучки",
choices=["ru", "en", "es", "fr", "de", "pl", "it", "ja", "ko", "zh", "hi"],
value="en",
visible=False # Изначально скрыт
)
with gr.Tab("Кастомный"):
cartesia_setting_custom_name = gr.Textbox(label="Имя")
cartesia_setting_custom_lang = gr.Dropdown(
label="Язык",
choices=LANGUAGE_CHOICES[1:] # Исключаем "all"
)
cartesia_setting_custom_voice = gr.Audio(label="Файл голоса",type='filepath')
cartesia_setting_custom_add = gr.Button("Добавить")
# with gr.Tab("Микс"):
# cartesia_setting_custom_mix = gr.Dropdown(
# label="Выберите голоса",
# multiselect=True,
# choices=[]
# )
# cartesia_setting_custom_mix_update = gr.Button("Обновить")
# for i in range(5):
# setattr(
# demo,
# f'mix_voice_{i+1}',
# gr.Slider(
# label=f"Голос {i+1}",
# value=0.5,
# minimum=0,
# maximum=1,
# step=0.01,
# visible=False
# )
# )
# Контроль эмоций
with gr.Accordion(label="Контроль эмоций (Beta)", open=False):
cartesia_emotions = gr.Dropdown(
label="Эмоции",
multiselect=True,
choices=EMOTION_CHOICES
)
cartesia_emotions_intensity = gr.Dropdown(
label="Интенсивность",
choices=EMOTION_INTENSITY,
value="Средняя"
)
# Настройки скорости
with gr.Accordion("Скорость", open=True):
cartesia_speed_speed = gr.Dropdown(
label="Скорость речи",
choices=SPEED_CHOICES,
value="Нормально"
)
cartesia_speed_speed_allow_custom = gr.Checkbox(
label="Использовать кастомное значение скорости"
)
cartesia_speed_speed_custom = gr.Slider(
label="Скорость",
value=0,
minimum=-1,
maximum=1,
step=0.1,
visible=False
)
cartesia_setting_improve_text = gr.Checkbox(
label="Улучшить текст согласно рекомендациям",
value=True
)
# Правая колонка
with gr.Column():
cartessia_status_bar = gr.Label(value="Статус")
cartesia_output_audio = gr.Audio(
label="Результат",
interactive=False
)
cartesia_output_button = gr.Button("Генерация")
# События
cartesia_api_key.change(
initialize_manager,
inputs=[cartesia_api_key],
outputs=[cartessia_status_bar]
)
cartesia_setting_filter_lang.change(
update_voice_list,
inputs=[
cartesia_setting_filter_lang,
cartesia_setting_filter_type,
cartesia_setting_voice # Передаем текущий выбор
],
outputs=[cartesia_setting_voice, cartessia_status_bar]
)
cartesia_setting_filter_type.change(
update_voice_list,
inputs=[
cartesia_setting_filter_lang,
cartesia_setting_filter_type,
cartesia_setting_voice # Передаем текущий выбор
],
outputs=[cartesia_setting_voice, cartessia_status_bar]
)
cartesia_setting_voice.change(
update_voice_info,
inputs=[cartesia_setting_voice],
outputs=[cartesia_setting_voice_info]
)
cartesia_setting_voice_update.click(
update_voice_list,
inputs=[cartesia_setting_filter_lang, cartesia_setting_filter_type],
outputs=[cartesia_setting_voice]
)
cartesia_speed_speed_allow_custom.change(
lambda x: gr.update(visible=x),
inputs=[cartesia_speed_speed_allow_custom],
outputs=[cartesia_speed_speed_custom]
)
cartesia_setting_custom_add.click(
create_custom_voice,
inputs=[
cartesia_setting_custom_name,
cartesia_setting_custom_lang,
cartesia_setting_custom_voice
],
outputs=[
cartessia_status_bar,
cartesia_setting_voice, # Обновляем dropdown
cartesia_setting_voice_info # Обновляем информацию о голосе
]
)
# Обновляем привязки событий
cartesia_setting_auto_language.change(
on_auto_language_change,
inputs=[cartesia_setting_auto_language],
outputs=[cartesia_setting_manual_language]
)
cartesia_output_button.click(
generate_speech,
inputs=[
cartesia_text,
cartesia_setting_voice,
cartesia_setting_improve_text,
cartesia_setting_auto_language,
cartesia_setting_manual_language,
cartesia_speed_speed,
cartesia_speed_speed_allow_custom,
cartesia_speed_speed_custom,
cartesia_emotions,
cartesia_emotions_intensity
],
outputs=[
cartesia_output_audio,
cartessia_status_bar
]
)
# Запуск приложения
if __name__ == "__main__":
# Инициализация менеджера при запуске
initialize_manager(DEFAULT_API_KEY)
# Запуск интерфейса
demo.launch()