import gradio as gr import edge_tts import asyncio import tempfile import os import uuid import re import shutil import emoji from pydub import AudioSegment # --- ПРОВЕРКА СЕРВЕРА --- if not shutil.which("ffmpeg"): print("⚠️ FFmpeg не найден! Убедитесь, что он установлен на хостинге.") # --- ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ --- VOICES_CACHE = [] LANGUAGES_CACHE = [] TEMP_DIR = tempfile.gettempdir() # --- ОЧИСТКА ТЕКСТА --- def clean_text_server_side(text): """ Удаляет эмодзи и спецсимволы, чтобы робот их не читал. Выполняется на сервере. """ if not text: return "" # Удаляем звездочки, тильды и прочий мусор форматирования text = re.sub(r'[*_~><^]', '', text) # Удаляем эмодзи (превращаем их в пустоту) text = emoji.replace_emoji(text, replace='') # Убираем лишние пробелы text = re.sub(r'\s+', ' ', text).strip() return text # --- ЗАГРУЗКА ГОЛОСОВ --- async def load_voices_init(): global VOICES_CACHE, LANGUAGES_CACHE try: voices = await edge_tts.list_voices() VOICES_CACHE = sorted(voices, key=lambda x: x['Locale']) seen = set() LANGUAGES_CACHE = [] for v in VOICES_CACHE: if v['Locale'] not in seen: seen.add(v['Locale']) LANGUAGES_CACHE.append(v['Locale']) LANGUAGES_CACHE.sort() print(f"✅ Голоса загружены: {len(VOICES_CACHE)}") except Exception as e: print(f"❌ Ошибка: {e}") LANGUAGES_CACHE = ["ru-RU", "en-US"] # --- ФИЛЬТР ГОЛОСОВ (UI) --- def update_voice_list(language): if not language: return gr.Dropdown(choices=[]) filtered = [f"{v['ShortName']} ({v['Gender']})" for v in VOICES_CACHE if v['Locale'] == language] # Ищем Светлану по дефолту default_val = filtered[0] if filtered else None for v in filtered: if "Svetlana" in v: default_val = v break return gr.Dropdown(choices=filtered, value=default_val) # --- ГЕНЕРАЦИЯ (SERVER ENGINE) --- async def generate_server_audio(text, voice_raw, rate, pitch): if not text.strip(): raise gr.Warning("Текст пуст!") if not voice_raw: raise gr.Warning("Выберите голос!") # Очистка clean_txt = clean_text_server_side(text) voice = voice_raw.split(" (")[0] # Параметры rate_str = f"{int(rate):+d}%" pitch_str = f"{int(pitch):+d}Hz" # Пути temp_filename = f"raw_{uuid.uuid4().hex}.mp3" temp_path = os.path.join(TEMP_DIR, temp_filename) final_filename = f"RESULT_{uuid.uuid4().hex}.mp3" final_path = os.path.join(TEMP_DIR, final_filename) print(f"⚙️ [Server] Генерация: {voice} | Тон: {pitch_str}") try: # 1. Скачиваем аудио от Microsoft на диск сервера comm = edge_tts.Communicate(clean_txt, voice, rate=rate_str, pitch=pitch_str) await comm.save(temp_path) # 2. Обрабатываем через Pydub (чтобы задействовать CPU сервера и проверить файл) if os.path.exists(temp_path) and os.path.getsize(temp_path) > 0: audio = AudioSegment.from_mp3(temp_path) audio.export(final_path, format="mp3") # Удаляем черновик os.remove(temp_path) return final_path else: raise Exception("Файл не создался (пустой).") except Exception as e: # Ловим ошибки 403 и прочие if "403" in str(e): raise gr.Error("Ошибка доступа (403). Сервер Microsoft временно недоступен.") raise gr.Error(f"Ошибка сервера: {str(e)}") # --- ЗАПУСК --- # Грузим голоса перед стартом asyncio.run(load_voices_init()) # НАСТРОЙКИ ПО УМОЛЧАНИЮ DEFAULT_LANG = "ru-RU" # Фильтруем список для русского языка START_VOICES = [f"{v['ShortName']} ({v['Gender']})" for v in VOICES_CACHE if v['Locale'] == DEFAULT_LANG] # Ставим Светлану DEFAULT_VOICE = next((v for v in START_VOICES if "Svetlana" in v), START_VOICES[0] if START_VOICES else None) # Стилизация css = """ body {background-color: #111827; color: #e5e7eb;} .container {max-width: 850px; margin: auto;} h1 {color: #fbbf24; text-align: center; font-family: serif;} """ theme = gr.themes.Soft(primary_hue="amber", secondary_hue="slate") with gr.Blocks(theme=theme, css=css, title="TTS Server Classic") as demo: gr.Markdown("# 🎙️ TTS Server Classic") with gr.Row(): # КОЛОНКА НАСТРОЕК with gr.Column(scale=1): gr.Markdown("### ⚙️ Параметры") lang_dr = gr.Dropdown( choices=LANGUAGES_CACHE, value=DEFAULT_LANG, label="Язык", interactive=True ) voice_dr = gr.Dropdown( choices=START_VOICES, value=DEFAULT_VOICE, label="Голос", interactive=True ) gr.Markdown("---") # Дефолт: -7 Hz, как ты просил rate_sl = gr.Slider(-50, 50, value=0, step=1, label="Скорость (%)") pitch_sl = gr.Slider(-20, 20, value=-7, step=1, label="Тон (Hz)") # КОЛОНКА ТЕКСТА with gr.Column(scale=2): gr.Markdown("### 📝 Текст") text_in = gr.Textbox( label="", lines=10, placeholder="Введите текст...", value="" ) btn = gr.Button("🔊 Озвучить (Server)", variant="primary", size="lg") audio_out = gr.Audio(label="Готовый файл", type="filepath") # Логика интерфейса lang_dr.change(update_voice_list, inputs=lang_dr, outputs=voice_dr) btn.click(generate_server_audio, inputs=[text_in, voice_dr, rate_sl, pitch_sl], outputs=audio_out) if __name__ == "__main__": demo.queue().launch()