Spaces:
Sleeping
Sleeping
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() |