VSPAN commited on
Commit
9795009
·
verified ·
1 Parent(s): b6e79a7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -141
app.py CHANGED
@@ -1,218 +1,155 @@
1
  import gradio as gr
2
  import edge_tts
3
- import tempfile
4
- import os
5
- import uuid
6
  import asyncio
 
7
  import re
8
  import emoji
 
 
9
 
10
- # --- КОНФИГУРАЦИЯ ---
11
- TEMP_DIR = tempfile.gettempdir()
 
 
 
12
 
13
- # Глобальные переменные для хранения списка голосов
14
  VOICES_CACHE = []
15
  LANGUAGES_CACHE = []
16
 
17
- # --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ---
18
 
19
  def clean_text(text):
20
- """
21
- Очистка текста для предотвращения ошибок парсера Microsoft.
22
- """
23
- if not text:
24
- return ""
25
- # Убираем символы, которые могут ломать SSML
26
  text = re.sub(r'[*_~><]', '', text)
27
- # Убираем эмодзи (библиотека emoji 2.6.0)
28
  text = emoji.replace_emoji(text, replace='')
29
- # Убираем лишние пробелы
30
  text = re.sub(r'\s+', ' ', text).strip()
31
  return text
32
 
33
- async def get_voices_async():
34
- """
35
- Асинхронная загрузка голосов и формирование структуры языков.
36
- """
37
  global VOICES_CACHE, LANGUAGES_CACHE
38
  try:
39
  voices = await edge_tts.list_voices()
40
- # Сортируем голоса
41
  VOICES_CACHE = sorted(voices, key=lambda x: x['Locale'])
42
 
43
- # Формируем список уникальных языков
44
  seen_langs = set()
45
- LANGUAGES_CACHE = []
46
  for v in VOICES_CACHE:
47
  if v['Locale'] not in seen_langs:
48
  seen_langs.add(v['Locale'])
49
  LANGUAGES_CACHE.append(v['Locale'])
50
-
51
  LANGUAGES_CACHE.sort()
52
- print(f"✅ Успешно загружено {len(VOICES_CACHE)} голосов.")
53
  except Exception as e:
54
- print(f"⚠️ Ошибка загрузки голосов: {e}")
55
- # Фолбэк значения, если интернет недоступен при старте
56
- LANGUAGES_CACHE = ["en-US", "ru-RU"]
57
 
58
  def filter_voices(language):
59
- """
60
- Фильтрация голосов при выборе языка в интерфейсе.
61
- """
62
- if not language:
63
- return gr.Dropdown(choices=[], value=None)
64
-
65
- # Формируем читаемый список: "Name (Gender)"
66
  filtered = [f"{v['ShortName']} ({v['Gender']})" for v in VOICES_CACHE if v['Locale'] == language]
67
 
68
- # Возвращаем обновленный дропдаун с первым значением по умолчанию
69
- return gr.Dropdown(choices=filtered, value=filtered[0] if filtered else None)
 
 
 
 
 
 
 
 
70
 
71
  async def generate_speech(text, voice_str, rate, pitch):
72
- """
73
- Главная функция генерации (Async).
74
- Gradio 4.x отлично работает с async def.
75
- """
76
- if not text or not text.strip():
77
- raise gr.Warning("Пожалуйста, введите текст.")
78
-
79
- if not voice_str:
80
- raise gr.Warning("Пожалуйста, выберите голос.")
81
 
82
- # Извлекаем "ShortName" из строки вида "ru-RU-DmitryNeural (Male)"
83
  voice_short = voice_str.split(" (")[0]
84
-
85
- # Подготовка параметров
86
  clean_input = clean_text(text)
 
 
87
  rate_str = f"{rate:+d}%"
88
  pitch_str = f"{pitch:+d}Hz"
89
 
90
- # Уникальное имя файла (решает проблемы с кэшем браузера и доступом)
91
- filename = f"audio_{uuid.uuid4().hex}.mp3"
92
- file_path = os.path.join(TEMP_DIR, filename)
93
 
94
- print(f"🔄 Генерация: {voice_short} | Скорость: {rate_str}")
95
 
96
  try:
97
- # Создаем объект Communicate (версия 6.1.12)
98
  communicate = edge_tts.Communicate(clean_input, voice_short, rate=rate_str, pitch=pitch_str)
99
-
100
- # Сохраняем файл
101
- await communicate.save(file_path)
102
-
103
- # Проверяем результат
104
- if os.path.exists(file_path) and os.path.getsize(file_path) > 0:
105
- return file_path
106
- else:
107
- raise Exception("Файл создан, но он пуст.")
108
-
109
  except Exception as e:
110
- error_message = str(e)
111
- print(f" Ошибка: {error_message}")
112
-
113
- if "403" in error_message:
114
- raise gr.Error(
115
- "Ошибка 403: Microsoft заблокировал доступ для вашей версии библиотеки. "
116
- "Это не ошибка кода, это ограничение API для версии 6.1.12."
117
- )
118
- else:
119
- raise gr.Error(f"Ошибка генерации: {error_message}")
120
 
121
- # --- ИНТЕРФЕЙС GRADIO (BLOCKS) ---
122
 
123
- # Предзагрузка голосов перед запуском интерфейса
124
- asyncio.run(get_voices_async())
125
 
126
- # CSS для красоты
127
  css = """
 
128
  .container { max-width: 900px; margin: auto; }
129
- footer { display: none !important; }
 
130
  """
131
 
132
- # Тема интерфейса
133
  theme = gr.themes.Soft(
134
- primary_hue="blue",
 
135
  neutral_hue="slate",
136
- radius_size="md"
137
  )
138
 
139
- with gr.Blocks(theme=theme, css=css, title="Edge TTS Pro") as demo:
 
 
140
 
141
  with gr.Row():
142
- gr.Markdown(
143
- """
144
- # 🎧 Edge TTS Generator
145
- ### Качественный синтез речи (версия 6.1.12)
146
- """
147
- )
148
-
149
- with gr.Row():
150
- # Левая колонка: Настройки
151
- with gr.Column(scale=1, min_width=300):
152
- gr.Markdown("### ⚙️ Настройки голоса")
153
 
154
- # Выбор языка
155
  lang_dropdown = gr.Dropdown(
156
  choices=LANGUAGES_CACHE,
157
- value="ru-RU" if "ru-RU" in LANGUAGES_CACHE else None,
158
- label="1. Язык",
159
  interactive=True
160
  )
161
 
162
- # Выбор голоса (заполняется динамически)
163
- # Инициализируем начальным списком для выбранного языка
164
- initial_voices = []
165
- if lang_dropdown.value:
166
- initial_voices = [f"{v['ShortName']} ({v['Gender']})" for v in VOICES_CACHE if v['Locale'] == lang_dropdown.value]
167
-
168
  voice_dropdown = gr.Dropdown(
169
- choices=initial_voices,
170
- value=initial_voices[0] if initial_voices else None,
171
- label="2. Голос",
172
  interactive=True
173
  )
174
 
175
- # Слайдеры в аккордеоне для компактности
176
- with gr.Accordion("Дополнительно", open=True):
177
- rate_slider = gr.Slider(minimum=-50, maximum=50, value=0, step=1, label="Скорость речи (%)")
178
- pitch_slider = gr.Slider(minimum=-20, maximum=20, value=0, step=1, label="Тон голоса (Hz)")
 
179
 
180
- # Правая колонка: Ввод и Результат
181
  with gr.Column(scale=2):
182
- gr.Markdown("### 📝 Текст")
183
  text_input = gr.Textbox(
184
- label="Введите текст для озвучивания",
185
- placeholder="Привет! Как твои дела?",
186
- lines=6,
187
- max_lines=20
188
  )
189
 
190
- generate_btn = gr.Button("🚀 Создать аудио", variant="primary", size="lg")
191
-
192
- gr.Markdown("### 🔊 Результат")
193
- audio_output = gr.Audio(
194
- label="Сгенерированный файл",
195
- type="filepath",
196
- interactive=False,
197
- autoplay=True
198
- )
199
 
200
- # --- ЛОГИКА СОБЫТИЙ ---
201
-
202
- # 1. При смене языка обновляем список голосов
203
- lang_dropdown.change(
204
- fn=filter_voices,
205
- inputs=[lang_dropdown],
206
- outputs=[voice_dropdown]
207
- )
208
-
209
- # 2. При нажатии кнопки генерируем (Gradio 4.x сам обработает async)
210
- generate_btn.click(
211
- fn=generate_speech,
212
- inputs=[text_input, voice_dropdown, rate_slider, pitch_slider],
213
- outputs=[audio_output]
214
- )
215
-
216
- # Запуск приложения
217
  if __name__ == "__main__":
218
  demo.launch()
 
1
  import gradio as gr
2
  import edge_tts
 
 
 
3
  import asyncio
4
+ import tempfile
5
  import re
6
  import emoji
7
+ import os
8
+ import uuid
9
 
10
+ # Проверка версии при запуске
11
+ try:
12
+ print(f"ℹ️ Версия edge-tts: {edge_tts.__version__}")
13
+ except:
14
+ pass
15
 
16
+ # Глобальные переменные
17
  VOICES_CACHE = []
18
  LANGUAGES_CACHE = []
19
 
20
+ # --- ФУНКЦИИ ---
21
 
22
  def clean_text(text):
23
+ if not text: return ""
 
 
 
 
 
24
  text = re.sub(r'[*_~><]', '', text)
 
25
  text = emoji.replace_emoji(text, replace='')
 
26
  text = re.sub(r'\s+', ' ', text).strip()
27
  return text
28
 
29
+ async def load_voices_async():
 
 
 
30
  global VOICES_CACHE, LANGUAGES_CACHE
31
  try:
32
  voices = await edge_tts.list_voices()
 
33
  VOICES_CACHE = sorted(voices, key=lambda x: x['Locale'])
34
 
 
35
  seen_langs = set()
 
36
  for v in VOICES_CACHE:
37
  if v['Locale'] not in seen_langs:
38
  seen_langs.add(v['Locale'])
39
  LANGUAGES_CACHE.append(v['Locale'])
 
40
  LANGUAGES_CACHE.sort()
41
+ print(f"✅ Голоса загружены ({len(VOICES_CACHE)} шт).")
42
  except Exception as e:
43
+ print(f"⚠️ Ошибка загрузки списка голосов: {e}")
44
+ # Фолбэк на случай отсутствия интернета при старте
45
+ LANGUAGES_CACHE = ["ru-RU", "en-US"]
46
 
47
  def filter_voices(language):
48
+ if not language: return gr.Dropdown(choices=[])
49
+ # Формируем список
 
 
 
 
 
50
  filtered = [f"{v['ShortName']} ({v['Gender']})" for v in VOICES_CACHE if v['Locale'] == language]
51
 
52
+ # Пытаемся найти Дмитрия для русского языка н идеален для фэнтези)
53
+ default_voice = None
54
+ if filtered:
55
+ default_voice = filtered[0]
56
+ for v in filtered:
57
+ if "Dmitry" in v: # Приоритет Дмитрию
58
+ default_voice = v
59
+ break
60
+
61
+ return gr.Dropdown(choices=filtered, value=default_voice)
62
 
63
  async def generate_speech(text, voice_str, rate, pitch):
64
+ if not text.strip(): raise gr.Warning("Напишите текст вашей истории...")
65
+ if not voice_str: raise gr.Warning("Выберите голос рассказчика.")
 
 
 
 
 
 
 
66
 
 
67
  voice_short = voice_str.split(" (")[0]
 
 
68
  clean_input = clean_text(text)
69
+
70
+ # Формируем параметры
71
  rate_str = f"{rate:+d}%"
72
  pitch_str = f"{pitch:+d}Hz"
73
 
74
+ filename = f"fantasy_story_{uuid.uuid4().hex}.mp3"
75
+ output_path = os.path.join(tempfile.gettempdir(), filename)
 
76
 
77
+ print(f"📖 Читает: {voice_short} | Скорость: {rate_str} | Тон: {pitch_str}")
78
 
79
  try:
 
80
  communicate = edge_tts.Communicate(clean_input, voice_short, rate=rate_str, pitch=pitch_str)
81
+ await communicate.save(output_path)
82
+ return output_path
 
 
 
 
 
 
 
 
83
  except Exception as e:
84
+ err = str(e)
85
+ if "403" in err:
86
+ raise gr.Error("⚠️ СРОЧНО ОБНОВИТЕ БИБЛИОТЕКУ! В терминале: pip install --upgrade edge-tts")
87
+ raise gr.Error(f"Ошибка магии: {err}")
 
 
 
 
 
 
88
 
89
+ # --- ИНТЕРФЕЙС (Fantasy Style) ---
90
 
91
+ asyncio.run(load_voices_async())
 
92
 
 
93
  css = """
94
+ body { background-color: #1a1b26; }
95
  .container { max-width: 900px; margin: auto; }
96
+ .gradio-container { font-family: 'Georgia', serif; }
97
+ h1 { font-family: 'Georgia', serif; color: #d4af37; text-align: center; }
98
  """
99
 
 
100
  theme = gr.themes.Soft(
101
+ primary_hue="amber", # Золотой оттенок для фэнтези
102
+ secondary_hue="slate",
103
  neutral_hue="slate",
 
104
  )
105
 
106
+ with gr.Blocks(theme=theme, css=css, title="Fantasy Storyteller") as demo:
107
+
108
+ gr.Markdown("# 📜 Летописец: Фэнтези Озвучка")
109
 
110
  with gr.Row():
111
+ with gr.Column(scale=1):
112
+ gr.Markdown("### 🧙‍♂️ Настройки барда")
 
 
 
 
 
 
 
 
 
113
 
114
+ # По умолчанию выбираем Русский
115
  lang_dropdown = gr.Dropdown(
116
  choices=LANGUAGES_CACHE,
117
+ value="ru-RU",
118
+ label="Язык сказания",
119
  interactive=True
120
  )
121
 
122
+ # Ищем Дмитрия в кэше для старта
123
+ start_voices = [f"{v['ShortName']} ({v['Gender']})" for v in VOICES_CACHE if v['Locale'] == "ru-RU"]
124
+ dmitry = next((v for v in start_voices if "Dmitry" in v), start_voices[0] if start_voices else None)
125
+
 
 
126
  voice_dropdown = gr.Dropdown(
127
+ choices=start_voices,
128
+ value=dmitry,
129
+ label="Голос",
130
  interactive=True
131
  )
132
 
133
+ with gr.Group():
134
+ gr.Markdown("*Для эпичности рекомендую скорость -10% и тон -5Hz*")
135
+ # Дефолтные значения специально под Фэнтези
136
+ rate_slider = gr.Slider(-50, 50, value=-10, step=1, label="Скорость чтения (%)")
137
+ pitch_slider = gr.Slider(-20, 20, value=-5, step=1, label="Глубина голоса (Hz)")
138
 
 
139
  with gr.Column(scale=2):
 
140
  text_input = gr.Textbox(
141
+ label="Текст легенды",
142
+ placeholder="В далекой темной пещере, где спал древний дракон...",
143
+ lines=8,
144
+ value="Давным-давно, в далекой стране, где горы пронзали небеса, жил древний дракон."
145
  )
146
 
147
+ btn = gr.Button(" Сотворить голос", variant="primary", size="lg")
148
+ audio_out = gr.Audio(label="Голос рассказчика", type="filepath", autoplay=True)
149
+
150
+ # Логика
151
+ lang_dropdown.change(filter_voices, inputs=lang_dropdown, outputs=voice_dropdown)
152
+ btn.click(generate_speech, inputs=[text_input, voice_dropdown, rate_slider, pitch_slider], outputs=audio_out)
 
 
 
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  if __name__ == "__main__":
155
  demo.launch()