VSPAN commited on
Commit
192a34f
·
verified ·
1 Parent(s): 8d7d379

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -74
app.py CHANGED
@@ -2,24 +2,23 @@ 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='')
@@ -27,129 +26,151 @@ def clean_text(text):
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()
 
2
  import edge_tts
3
  import asyncio
4
  import tempfile
 
 
5
  import os
6
  import uuid
7
+ import re
8
+ import emoji
9
+ from pydub import AudioSegment # Используем новую библиотеку pydub
10
 
11
+ # --- КОНСТАНТЫ И НАСТРОЙКИ ---
12
+ TEMP_DIR = tempfile.gettempdir()
 
 
 
13
 
14
+ # Глобальные кэши
15
  VOICES_CACHE = []
16
  LANGUAGES_CACHE = []
17
 
18
+ # --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ---
19
 
20
  def clean_text(text):
21
+ """Очистка текста от мусора."""
22
  if not text: return ""
23
  text = re.sub(r'[*_~><]', '', text)
24
  text = emoji.replace_emoji(text, replace='')
 
26
  return text
27
 
28
  async def load_voices_async():
29
+ """Загрузка списка голосов при старте."""
30
  global VOICES_CACHE, LANGUAGES_CACHE
31
  try:
32
+ print("🔮 Призыв духов голосов (загрузка списка)...")
33
  voices = await edge_tts.list_voices()
34
  VOICES_CACHE = sorted(voices, key=lambda x: x['Locale'])
35
 
36
+ seen = set()
37
+ LANGUAGES_CACHE = []
38
  for v in VOICES_CACHE:
39
+ if v['Locale'] not in seen:
40
+ seen.add(v['Locale'])
41
  LANGUAGES_CACHE.append(v['Locale'])
42
  LANGUAGES_CACHE.sort()
43
+ print(f"✅ Успешно! Доступно {len(VOICES_CACHE)} голосов.")
44
  except Exception as e:
45
+ print(f" Ошибка загрузки голосов: {e}")
 
46
  LANGUAGES_CACHE = ["ru-RU", "en-US"]
47
 
48
  def filter_voices(language):
49
+ """Фильтр голосов по языку."""
50
  if not language: return gr.Dropdown(choices=[])
51
+
52
  filtered = [f"{v['ShortName']} ({v['Gender']})" for v in VOICES_CACHE if v['Locale'] == language]
53
 
54
+ # Авто-выбор лучшего голоса для фэнтези (Дмитрий для RU, Гай для EN)
55
+ default_val = filtered[0] if filtered else None
56
+ for v in filtered:
57
+ if "Dmitry" in v or "Guy" in v:
58
+ default_val = v
59
+ break
60
+
61
+ return gr.Dropdown(choices=filtered, value=default_val)
 
 
62
 
63
  async def generate_speech(text, voice_str, rate, pitch):
64
+ """Генерация речи с использованием edge-tts и обработка через pydub."""
65
+ if not text.strip(): raise gr.Warning("Свиток пуст! Напишите текст.")
66
+ if not voice_str: raise gr.Warning("Выберите рассказчика.")
67
 
68
  voice_short = voice_str.split(" (")[0]
69
  clean_input = clean_text(text)
70
 
71
+ # Параметры для API
72
  rate_str = f"{rate:+d}%"
73
  pitch_str = f"{pitch:+d}Hz"
74
 
75
+ # Имя временного файла
76
+ filename = f"fantasy_{uuid.uuid4().hex}.mp3"
77
+ file_path = os.path.join(TEMP_DIR, filename)
78
 
79
+ print(f"🎙️ Озвучка: {voice_short} | Скорость: {rate_str} | Тон: {pitch_str}")
80
 
81
  try:
82
+ # 1. Генерация через Edge TTS
83
  communicate = edge_tts.Communicate(clean_input, voice_short, rate=rate_str, pitch=pitch_str)
84
+ await communicate.save(file_path)
85
+
86
+ # 2. Проверка файла (Pydub)
87
+ # Мы просто проверяем, что файл валиден и читается библиотекой pydub
88
+ if os.path.exists(file_path) and os.path.getsize(file_path) > 0:
89
+ try:
90
+ audio = AudioSegment.from_mp3(file_path)
91
+ print(f"✅ Аудио готово. Длительность: {len(audio) / 1000} сек.")
92
+ except Exception as e:
93
+ # Если у пользователя нет ffmpeg, pydub может ругаться, но файл всё равно рабочий
94
+ print(f"⚠️ Pydub warning (не критично): {e}")
95
+
96
+ return file_path
97
+ else:
98
+ raise Exception("Файл не создан.")
99
+
100
  except Exception as e:
101
+ error_msg = str(e)
102
+ if "403" in error_msg:
103
+ raise gr.Error("Ошибка 403! Ваша библиотека устарела. Выполните: pip install --upgrade edge-tts")
104
+ raise gr.Error(f"Ошибка магии: {error_msg}")
105
 
106
+ # --- ЗАПУСК ИНТЕРФЕЙСА ---
107
 
108
+ # Загружаем голоса перед стартом
109
  asyncio.run(load_voices_async())
110
 
111
+ # Стилизация
112
  css = """
113
+ body { background-color: #0f172a; }
114
  .container { max-width: 900px; margin: auto; }
115
+ h1 { color: #fbbf24; text-align: center; font-family: serif; }
116
+ .gradio-container { font-family: 'Merriweather', 'Georgia', serif; }
117
  """
118
 
119
  theme = gr.themes.Soft(
120
+ primary_hue="amber",
121
  secondary_hue="slate",
122
+ neutral_hue="slate"
123
  )
124
 
125
+ with gr.Blocks(theme=theme, css=css, title="Fantasy TTS v2.0") as demo:
126
 
127
+ gr.Markdown("# 🐉 Легендарный Рассказчик")
128
 
129
  with gr.Row():
130
+ # Колонка настроек
131
  with gr.Column(scale=1):
132
+ gr.Markdown("### 🧙‍♂️ Настройки Голоса")
133
 
134
+ lang_dr = gr.Dropdown(
135
+ choices=LANGUAGES_CACHE,
136
+ value="ru-RU",
137
+ label="Язык Летописи",
 
138
  interactive=True
139
  )
140
 
141
+ # Предзагрузка списка для русского языка
142
+ init_voices = [f"{v['ShortName']} ({v['Gender']})" for v in VOICES_CACHE if v['Locale'] == "ru-RU"]
143
+ # Ищем Дмитрия
144
+ dmitry = next((v for v in init_voices if "Dmitry" in v), init_voices[0] if init_voices else None)
145
+
146
+ voice_dr = gr.Dropdown(
147
+ choices=init_voices,
148
+ value=dmitry,
149
+ label="Рассказчик",
150
  interactive=True
151
  )
152
 
153
+ with gr.Accordion("🎚️ Тонкая настройка магии", open=True):
154
+ rate_sl = gr.Slider(-50, 50, value=-10, step=1, label="Скорость (%) - Рекомендую -10")
155
+ pitch_sl = gr.Slider(-20, 20, value=-5, step=1, label="Высота (Hz) - Рекомендую -5")
 
 
156
 
157
+ # Колонка ввода
158
  with gr.Column(scale=2):
159
+ gr.Markdown("### 📜 Текст Истории")
160
+ input_text = gr.Textbox(
161
+ label="",
162
+ placeholder="В темные времена, когда драконы правили небесами...",
163
+ lines=10,
164
+ value="Давным-давно, в далекой стране, где горы пронзали небеса, жил древний дракон. Его чешуя блестела как золото, а глаза горели огнем вечности."
165
  )
166
 
167
+ btn = gr.Button("✨ Воплотить в звук", variant="primary", size="lg")
168
+ output_audio = gr.Audio(label="Результат", type="filepath", autoplay=True)
169
 
170
  # Логика
171
+ lang_dr.change(filter_voices, inputs=lang_dr, outputs=voice_dr)
172
+ btn.click(generate_speech, inputs=[input_text, voice_dr, rate_sl, pitch_sl], outputs=output_audio)
173
 
174
  if __name__ == "__main__":
175
+ print(f"ℹ️ Запуск с версией edge-tts: {edge_tts.__version__}")
176
  demo.launch()