AlserFurma commited on
Commit
f1b66ce
·
verified ·
1 Parent(s): ff0ae3b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +34 -54
app.py CHANGED
@@ -16,7 +16,6 @@ from pydub import AudioSegment
16
  # Параметры
17
  # =========================
18
  TALKING_HEAD_SPACE = "Skywork/skyreels-a1-talking-head"
19
-
20
  device = "cuda" if torch.cuda.is_available() else "cpu"
21
  print(f"Using device: {device}")
22
 
@@ -43,21 +42,18 @@ try:
43
  )
44
 
45
  print("✅ Все модели успешно загружены!")
46
-
47
  except Exception as e:
48
  raise RuntimeError(f"❌ Ошибка загрузки моделей: {str(e)}")
49
 
50
-
51
  # =========================
52
  # Вспомогательные функции
53
  # =========================
54
  def generate_quiz(text: str):
55
- """
56
- Генерирует один вопрос и два варианта ответа на основе текста.
57
  Алгоритмы:
58
- 1. Базовый: случайное предложение и первые слова.
59
- 2. Пропуск ключевого слова.
60
- 3. Вопрос о числе/дате.
61
  """
62
  try:
63
  sentences = [s.strip() for s in text.replace("!", ".").replace("?", ".").split(".") if s.strip()]
@@ -65,9 +61,9 @@ def generate_quiz(text: str):
65
  raise ValueError("Текст слишком короткий")
66
 
67
  algo = random.choice([1, 2, 3])
 
68
  # ------------------------
69
- if algo == 1:
70
- # Базовый алгоритм
71
  question_sentence = random.choice(sentences)
72
  words = question_sentence.split()
73
  if len(words) <= 3:
@@ -76,14 +72,12 @@ def generate_quiz(text: str):
76
  else:
77
  question = "Что сказано в тексте?"
78
  correct_answer = " ".join(words[:6]) + ("..." if len(words) > 6 else "")
79
-
80
  wrong_sentence = random.choice([s for s in sentences if s != question_sentence] or ["Другая информация"])
81
  wrong_words = wrong_sentence.split()
82
  wrong_answer = " ".join(wrong_words[:6]) + ("..." if len(wrong_words) > 6 else "")
83
-
84
  # ------------------------
85
- elif algo == 2:
86
- # Пропуск ключевого слова
87
  question_sentence = random.choice(sentences)
88
  words = question_sentence.split()
89
  if len(words) > 2:
@@ -94,10 +88,9 @@ def generate_quiz(text: str):
94
  else:
95
  # fallback
96
  return generate_quiz(text)
97
-
98
  # ------------------------
99
- elif algo == 3:
100
- # Вопрос о числе или дате
101
  import re
102
  question_sentence = random.choice(sentences)
103
  numbers = re.findall(r'\d+', question_sentence)
@@ -109,26 +102,26 @@ def generate_quiz(text: str):
109
  else:
110
  # fallback к базовому
111
  return generate_quiz(text)
112
-
113
  options = [correct_answer, wrong_answer]
114
  random.shuffle(options)
115
  return question, options, correct_answer
116
-
117
  except Exception as e:
118
  raise ValueError(f"Ошибка генерации вопроса: {str(e)}")
119
 
120
-
121
-
122
  def synthesize_audio(text_ru: str):
123
  """Переводит русскую строку на казахский, синтезирует аудио и возвращает путь к файлу .wav"""
124
  translation = translator(text_ru, src_lang="rus_Cyrl", tgt_lang="kaz_Cyrl")
125
  text_kk = translation[0]["translation_text"]
126
 
127
  inputs = tts_tokenizer(text_kk, return_tensors="pt").to(device)
 
128
  with torch.no_grad():
129
- output = tts_model(**inputs)
130
 
131
  waveform = output.waveform.squeeze().cpu().numpy()
 
 
132
  audio = (waveform * 32767).astype('int16')
133
  sampling_rate = getattr(tts_model.config, 'sampling_rate', 22050)
134
 
@@ -137,24 +130,22 @@ def synthesize_audio(text_ru: str):
137
  tmpf.close()
138
  return tmpf.name
139
 
140
-
141
  def concatenate_audio_files(audio_files):
142
  """Объединяет несколько аудио файлов в один с паузами между ними"""
143
  combined = AudioSegment.empty()
144
  pause = AudioSegment.silent(duration=1000) # 1 секунда паузы
145
-
146
  for i, audio_file in enumerate(audio_files):
147
  audio = AudioSegment.from_wav(audio_file)
148
  combined += audio
149
  if i < len(audio_files) - 1: # Не добавляем пау��у после последнего файла
150
  combined += pause
151
-
152
  output_file = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
153
  combined.export(output_file.name, format='wav')
154
  output_file.close()
155
  return output_file.name
156
 
157
-
158
  def make_talking_head(image_path: str, audio_path: str, max_retries=3):
159
  """Вызывает SkyReels/Talking Head space и возвращает путь или URL видео."""
160
  for attempt in range(max_retries):
@@ -167,7 +158,6 @@ def make_talking_head(image_path: str, audio_path: str, max_retries=3):
167
  steps=10,
168
  api_name="/process_image_audio"
169
  )
170
-
171
  print(f"Result type: {type(result)}")
172
  print(f"Result content: {result}")
173
 
@@ -188,7 +178,6 @@ def make_talking_head(image_path: str, audio_path: str, max_retries=3):
188
  return result
189
  else:
190
  raise ValueError(f"Unexpected talking head result: {type(result)}, value: {result}")
191
-
192
  except Exception as e:
193
  if attempt < max_retries - 1:
194
  print(f"Попытка {attempt + 1} не удалась: {e}. Повторяю через 2 секунды...")
@@ -196,7 +185,6 @@ def make_talking_head(image_path: str, audio_path: str, max_retries=3):
196
  else:
197
  raise Exception(f"Ошибка после {max_retries} попыток: {str(e)}")
198
 
199
-
200
  # =========================
201
  # Основные обработчики для Gradio
202
  # =========================
@@ -219,24 +207,24 @@ def start_lesson(image: Image.Image, text: str, state):
219
 
220
  # Создаем три аудио файла
221
  audio_files = []
222
-
223
  # 1. Текст лекции
224
  audio1 = synthesize_audio(text)
225
  audio_files.append(audio1)
226
-
227
  # 2. Вопрос
228
  question_text = f"А теперь вопрос: {question}"
229
  audio2 = synthesize_audio(question_text)
230
  audio_files.append(audio2)
231
-
232
  # 3. Варианты ответа
233
  options_text = f"Первый вариант: {options[0]}. Второй вариант: {options[1]}"
234
  audio3 = synthesize_audio(options_text)
235
  audio_files.append(audio3)
236
-
237
  # Объединяем все аудио в одно
238
  combined_audio = concatenate_audio_files(audio_files)
239
-
240
  # Создаем одно видео с полным содержанием
241
  video_path = make_talking_head(image_path, combined_audio)
242
 
@@ -250,9 +238,9 @@ def start_lesson(image: Image.Image, text: str, state):
250
 
251
  # Удаляем временные аудио файлы
252
  for audio_file in audio_files:
253
- try:
254
  os.remove(audio_file)
255
- except:
256
  pass
257
  try:
258
  os.remove(combined_audio)
@@ -260,25 +248,22 @@ def start_lesson(image: Image.Image, text: str, state):
260
  pass
261
 
262
  question_display = f"**Вопрос:** {question}"
263
-
264
  return (
265
- video_path,
266
- question_display,
267
  gr.update(value=options[0], visible=True),
268
  gr.update(value=options[1], visible=True),
269
  state_data
270
  )
271
-
272
  except Exception as e:
273
  traceback.print_exc()
274
  return None, f"❌ Ошибка: {e}", gr.update(visible=False), gr.update(visible=False), state
275
 
276
-
277
  def answer_selected(selected_option: str, state):
278
  """Генерирует реакцию лектора и показывает в том же окне"""
279
  if not state:
280
  return None, "❌ Ошибка: отсутствует состояние урока"
281
-
282
  try:
283
  correct = state.get('correct')
284
  image_path = state.get('image_path')
@@ -292,22 +277,20 @@ def answer_selected(selected_option: str, state):
292
 
293
  # Создаем аудио с реакцией
294
  audio_path = synthesize_audio(reaction_ru)
295
-
296
  # Создаем видео с реакцией
297
  reaction_video = make_talking_head(image_path, audio_path)
298
 
299
- try:
300
  os.remove(audio_path)
301
- except:
302
  pass
303
 
304
  return reaction_video, display_message
305
-
306
  except Exception as e:
307
  traceback.print_exc()
308
  return None, f"❌ Ошибка: {e}"
309
 
310
-
311
  # =========================
312
  # Gradio UI
313
  # =========================
@@ -326,8 +309,8 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
326
  with gr.Column(scale=1):
327
  inp_image = gr.Image(type='pil', label='📸 Мұғалімнің суреті')
328
  inp_text = gr.Textbox(
329
- lines=5,
330
- label='📝 Дәріс мәтіні (орыс.)',
331
  placeholder='Дәріс мәт��нін енгізіңіз...',
332
  info="Ең көбі 500 таңба"
333
  )
@@ -336,11 +319,9 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
336
  with gr.Column(scale=1):
337
  out_video = gr.Video(label='🎬 Мұғалімнің видеосы')
338
  out_question = gr.Markdown("")
339
-
340
  with gr.Row():
341
  btn_opt1 = gr.Button("Вариант 1", visible=False, size="lg", variant="secondary")
342
  btn_opt2 = gr.Button("Вариант 2", visible=False, size="lg", variant="secondary")
343
-
344
  out_result = gr.Markdown("")
345
 
346
  lesson_state = gr.State({})
@@ -356,17 +337,16 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
356
  def handle_answer_1(state):
357
  option = state.get('options', [''])[0] if state else ''
358
  return answer_selected(option, state)
359
-
360
  def handle_answer_2(state):
361
  option = state.get('options', [''])[1] if state and len(state.get('options', [])) > 1 else ''
362
  return answer_selected(option, state)
363
-
364
  btn_opt1.click(
365
  fn=handle_answer_1,
366
  inputs=[lesson_state],
367
  outputs=[out_video, out_result]
368
  )
369
-
370
  btn_opt2.click(
371
  fn=handle_answer_2,
372
  inputs=[lesson_state],
 
16
  # Параметры
17
  # =========================
18
  TALKING_HEAD_SPACE = "Skywork/skyreels-a1-talking-head"
 
19
  device = "cuda" if torch.cuda.is_available() else "cpu"
20
  print(f"Using device: {device}")
21
 
 
42
  )
43
 
44
  print("✅ Все модели успешно загружены!")
 
45
  except Exception as e:
46
  raise RuntimeError(f"❌ Ошибка загрузки моделей: {str(e)}")
47
 
 
48
  # =========================
49
  # Вспомогательные функции
50
  # =========================
51
  def generate_quiz(text: str):
52
+ """ Генерирует один вопрос и два варианта ответа на основе текста.
 
53
  Алгоритмы:
54
+ 1. Базовый: случайное предложение и первые слова.
55
+ 2. Пропуск ключевого слова.
56
+ 3. Вопрос о числе/дате.
57
  """
58
  try:
59
  sentences = [s.strip() for s in text.replace("!", ".").replace("?", ".").split(".") if s.strip()]
 
61
  raise ValueError("Текст слишком короткий")
62
 
63
  algo = random.choice([1, 2, 3])
64
+
65
  # ------------------------
66
+ if algo == 1: # Базовый алгоритм
 
67
  question_sentence = random.choice(sentences)
68
  words = question_sentence.split()
69
  if len(words) <= 3:
 
72
  else:
73
  question = "Что сказано в тексте?"
74
  correct_answer = " ".join(words[:6]) + ("..." if len(words) > 6 else "")
 
75
  wrong_sentence = random.choice([s for s in sentences if s != question_sentence] or ["Другая информация"])
76
  wrong_words = wrong_sentence.split()
77
  wrong_answer = " ".join(wrong_words[:6]) + ("..." if len(wrong_words) > 6 else "")
78
+
79
  # ------------------------
80
+ elif algo == 2: # Пропуск ключевого слова
 
81
  question_sentence = random.choice(sentences)
82
  words = question_sentence.split()
83
  if len(words) > 2:
 
88
  else:
89
  # fallback
90
  return generate_quiz(text)
91
+
92
  # ------------------------
93
+ elif algo == 3: # Вопрос о числе или дате
 
94
  import re
95
  question_sentence = random.choice(sentences)
96
  numbers = re.findall(r'\d+', question_sentence)
 
102
  else:
103
  # fallback к базовому
104
  return generate_quiz(text)
105
+
106
  options = [correct_answer, wrong_answer]
107
  random.shuffle(options)
108
  return question, options, correct_answer
 
109
  except Exception as e:
110
  raise ValueError(f"Ошибка генерации вопроса: {str(e)}")
111
 
 
 
112
  def synthesize_audio(text_ru: str):
113
  """Переводит русскую строку на казахский, синтезирует аудио и возвращает путь к файлу .wav"""
114
  translation = translator(text_ru, src_lang="rus_Cyrl", tgt_lang="kaz_Cyrl")
115
  text_kk = translation[0]["translation_text"]
116
 
117
  inputs = tts_tokenizer(text_kk, return_tensors="pt").to(device)
118
+
119
  with torch.no_grad():
120
+ output = tts_model(**inputs, noise_scale=0.7, noise_scale_w=0.9, length_scale=1.2)
121
 
122
  waveform = output.waveform.squeeze().cpu().numpy()
123
+ waveform /= np.max(np.abs(waveform)) + 1e-8 # Нормализация для лучшего качества
124
+
125
  audio = (waveform * 32767).astype('int16')
126
  sampling_rate = getattr(tts_model.config, 'sampling_rate', 22050)
127
 
 
130
  tmpf.close()
131
  return tmpf.name
132
 
 
133
  def concatenate_audio_files(audio_files):
134
  """Объединяет несколько аудио файлов в один с паузами между ними"""
135
  combined = AudioSegment.empty()
136
  pause = AudioSegment.silent(duration=1000) # 1 секунда паузы
137
+
138
  for i, audio_file in enumerate(audio_files):
139
  audio = AudioSegment.from_wav(audio_file)
140
  combined += audio
141
  if i < len(audio_files) - 1: # Не добавляем пау��у после последнего файла
142
  combined += pause
143
+
144
  output_file = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
145
  combined.export(output_file.name, format='wav')
146
  output_file.close()
147
  return output_file.name
148
 
 
149
  def make_talking_head(image_path: str, audio_path: str, max_retries=3):
150
  """Вызывает SkyReels/Talking Head space и возвращает путь или URL видео."""
151
  for attempt in range(max_retries):
 
158
  steps=10,
159
  api_name="/process_image_audio"
160
  )
 
161
  print(f"Result type: {type(result)}")
162
  print(f"Result content: {result}")
163
 
 
178
  return result
179
  else:
180
  raise ValueError(f"Unexpected talking head result: {type(result)}, value: {result}")
 
181
  except Exception as e:
182
  if attempt < max_retries - 1:
183
  print(f"Попытка {attempt + 1} не удалась: {e}. Повторяю через 2 секунды...")
 
185
  else:
186
  raise Exception(f"Ошибка после {max_retries} попыток: {str(e)}")
187
 
 
188
  # =========================
189
  # Основные обработчики для Gradio
190
  # =========================
 
207
 
208
  # Создаем три аудио файла
209
  audio_files = []
210
+
211
  # 1. Текст лекции
212
  audio1 = synthesize_audio(text)
213
  audio_files.append(audio1)
214
+
215
  # 2. Вопрос
216
  question_text = f"А теперь вопрос: {question}"
217
  audio2 = synthesize_audio(question_text)
218
  audio_files.append(audio2)
219
+
220
  # 3. Варианты ответа
221
  options_text = f"Первый вариант: {options[0]}. Второй вариант: {options[1]}"
222
  audio3 = synthesize_audio(options_text)
223
  audio_files.append(audio3)
224
+
225
  # Объединяем все аудио в одно
226
  combined_audio = concatenate_audio_files(audio_files)
227
+
228
  # Создаем одно видео с полным содержанием
229
  video_path = make_talking_head(image_path, combined_audio)
230
 
 
238
 
239
  # Удаляем временные аудио файлы
240
  for audio_file in audio_files:
241
+ try:
242
  os.remove(audio_file)
243
+ except:
244
  pass
245
  try:
246
  os.remove(combined_audio)
 
248
  pass
249
 
250
  question_display = f"**Вопрос:** {question}"
 
251
  return (
252
+ video_path,
253
+ question_display,
254
  gr.update(value=options[0], visible=True),
255
  gr.update(value=options[1], visible=True),
256
  state_data
257
  )
 
258
  except Exception as e:
259
  traceback.print_exc()
260
  return None, f"❌ Ошибка: {e}", gr.update(visible=False), gr.update(visible=False), state
261
 
 
262
  def answer_selected(selected_option: str, state):
263
  """Генерирует реакцию лектора и показывает в том же окне"""
264
  if not state:
265
  return None, "❌ Ошибка: отсутствует состояние урока"
266
+
267
  try:
268
  correct = state.get('correct')
269
  image_path = state.get('image_path')
 
277
 
278
  # Создаем аудио с реакцией
279
  audio_path = synthesize_audio(reaction_ru)
280
+
281
  # Создаем видео с реакцией
282
  reaction_video = make_talking_head(image_path, audio_path)
283
 
284
+ try:
285
  os.remove(audio_path)
286
+ except:
287
  pass
288
 
289
  return reaction_video, display_message
 
290
  except Exception as e:
291
  traceback.print_exc()
292
  return None, f"❌ Ошибка: {e}"
293
 
 
294
  # =========================
295
  # Gradio UI
296
  # =========================
 
309
  with gr.Column(scale=1):
310
  inp_image = gr.Image(type='pil', label='📸 Мұғалімнің суреті')
311
  inp_text = gr.Textbox(
312
+ lines=5,
313
+ label='📝 Дәріс мәтіні (орыс.)',
314
  placeholder='Дәріс мәт��нін енгізіңіз...',
315
  info="Ең көбі 500 таңба"
316
  )
 
319
  with gr.Column(scale=1):
320
  out_video = gr.Video(label='🎬 Мұғалімнің видеосы')
321
  out_question = gr.Markdown("")
 
322
  with gr.Row():
323
  btn_opt1 = gr.Button("Вариант 1", visible=False, size="lg", variant="secondary")
324
  btn_opt2 = gr.Button("Вариант 2", visible=False, size="lg", variant="secondary")
 
325
  out_result = gr.Markdown("")
326
 
327
  lesson_state = gr.State({})
 
337
  def handle_answer_1(state):
338
  option = state.get('options', [''])[0] if state else ''
339
  return answer_selected(option, state)
340
+
341
  def handle_answer_2(state):
342
  option = state.get('options', [''])[1] if state and len(state.get('options', [])) > 1 else ''
343
  return answer_selected(option, state)
344
+
345
  btn_opt1.click(
346
  fn=handle_answer_1,
347
  inputs=[lesson_state],
348
  outputs=[out_video, out_result]
349
  )
 
350
  btn_opt2.click(
351
  fn=handle_answer_2,
352
  inputs=[lesson_state],