File size: 6,711 Bytes
63f1d6d
 
b6e79a7
9795009
 
 
192a34f
ddd6448
743c0e7
ddd6448
f81803f
743c0e7
ddd6448
e40a9fa
d7cf28b
743c0e7
 
 
ddd6448
 
e40a9fa
 
 
 
 
 
743c0e7
e40a9fa
743c0e7
e40a9fa
743c0e7
e40a9fa
743c0e7
 
 
e40a9fa
743c0e7
 
 
 
 
 
 
 
 
 
 
 
 
e40a9fa
743c0e7
e40a9fa
743c0e7
 
e40a9fa
 
743c0e7
e40a9fa
743c0e7
 
e40a9fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
743c0e7
e40a9fa
 
 
2cbf263
e40a9fa
 
 
2cbf263
e40a9fa
 
 
2cbf263
e40a9fa
 
b2496fc
e40a9fa
355ee60
e40a9fa
 
 
 
ddd6448
e40a9fa
 
 
 
a322c69
e40a9fa
 
 
 
 
 
 
 
 
 
 
b6e79a7
e40a9fa
7e3a62b
e40a9fa
743c0e7
 
e40a9fa
 
 
 
 
 
743c0e7
e40a9fa
ddd6448
743c0e7
e40a9fa
 
ddd6448
7e3a62b
743c0e7
7e3a62b
e40a9fa
7e3a62b
e40a9fa
b6e79a7
e40a9fa
 
 
 
ddd6448
e40a9fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90c0176
 
e40a9fa
 
 
 
743c0e7
e40a9fa
 
 
b2496fc
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
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()