AlserFurma commited on
Commit
c5f3eef
·
verified ·
1 Parent(s): c320841

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -84
app.py CHANGED
@@ -9,6 +9,8 @@ import scipy.io.wavfile as wavfile
9
  import traceback
10
  import random
11
  import time
 
 
12
 
13
  # =========================
14
  # Параметры
@@ -52,7 +54,6 @@ except Exception as e:
52
  def generate_quiz(text: str):
53
  """
54
  Генерирует один вопрос и два варианта ответа (correct, wrong) на русском языке.
55
- Парсит текст вместо строгого JSON.
56
  """
57
  prompt = (
58
  "Сгенерируй один учебный вопрос по этому тексту и два варианта ответа (правильный и неправильный). "
@@ -60,18 +61,15 @@ def generate_quiz(text: str):
60
  )
61
  try:
62
  out = qa_model(prompt, max_length=256)[0]["generated_text"]
63
- # Пробуем извлечь вопрос и два ответа через простые маркеры
64
  import re
65
  match = re.search(
66
  r"Вопрос\s*[:\-]\s*(.*?)\s*Ответ1\s*[:\-]\s*(.*?)\s*Ответ2\s*[:\-]\s*(.*)", out, re.DOTALL | re.IGNORECASE
67
  )
68
  if not match:
69
- # fallback: разделяем по строкам
70
  lines = [l.strip() for l in out.splitlines() if l.strip()]
71
  if len(lines) >= 3:
72
  question, correct, wrong = lines[:3]
73
  else:
74
- # Если всё равно не получилось — берём первые 3 фразы
75
  parts = out.split(".")
76
  question = parts[0] if len(parts) > 0 else "Вопрос"
77
  correct = parts[1] if len(parts) > 1 else "Вариант 1"
@@ -86,7 +84,7 @@ def generate_quiz(text: str):
86
  random.shuffle(options)
87
  return question, options, correct
88
  except Exception as e:
89
- raise ValueError(f"Ошибка генерации вопроса:\n{str(e)}\nМодель вернула: {out}")
90
 
91
 
92
  def synthesize_audio(text_ru: str):
@@ -108,6 +106,23 @@ def synthesize_audio(text_ru: str):
108
  return tmpf.name
109
 
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  def make_talking_head(image_path: str, audio_path: str, max_retries=3):
112
  """Вызывает SkyReels/Talking Head space и возвращает путь или URL видео."""
113
  for attempt in range(max_retries):
@@ -121,20 +136,16 @@ def make_talking_head(image_path: str, audio_path: str, max_retries=3):
121
  api_name="/process_image_audio"
122
  )
123
 
124
- # Отладочный вывод
125
  print(f"Result type: {type(result)}")
126
  print(f"Result content: {result}")
127
 
128
- # Обработка различных форматов результата
129
  if isinstance(result, tuple):
130
- # Если результат - кортеж, берем первый элемент
131
  video_path = result[0]
132
  if isinstance(video_path, dict) and "video" in video_path:
133
  return video_path["video"]
134
  elif isinstance(video_path, str):
135
  return video_path
136
  else:
137
- # Если первый элемент не подходит, пробуем найти путь к видео в кортеже
138
  for item in result:
139
  if isinstance(item, str) and (item.endswith('.mp4') or item.endswith('.webm') or os.path.exists(str(item))):
140
  return item
@@ -158,9 +169,9 @@ def make_talking_head(image_path: str, audio_path: str, max_retries=3):
158
  # Основные обработчики для Gradio
159
  # =========================
160
  def start_lesson(image: Image.Image, text: str, state):
161
- """Шаг 1: лектор читает текст лекции."""
162
  if image is None or not text.strip() or len(text) > 500:
163
- return None, "Пожалуйста, загрузите фото и введите текст лекции (до 500 символов)", gr.update(visible=False), state
164
 
165
  try:
166
  # Сохраняем изображение
@@ -171,72 +182,68 @@ def start_lesson(image: Image.Image, text: str, state):
171
  tmpimg.close()
172
  image_path = tmpimg.name
173
 
174
- # Генерируем вопрос заранее (но не озвучиваем)
175
  question, options, correct = generate_quiz(text)
176
 
177
- # Лектор читает текст лекции
178
- audio_path = synthesize_audio(text)
179
- video_path = make_talking_head(image_path, audio_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
  # Сохраняем состояние
182
  state_data = {
183
  'image_path': image_path,
184
  'correct': correct,
185
  'options': options,
186
- 'question': question,
187
- 'text': text
188
  }
189
 
190
- # Удаляем временный аудио файл
191
- try:
192
- os.remove(audio_path)
193
- except:
 
 
 
 
 
194
  pass
195
 
196
- return video_path, " Лекция прочитана. Нажмите 'Задать вопрос' для проверки знаний.", gr.update(visible=True), state_data
197
-
198
- except Exception as e:
199
- traceback.print_exc()
200
- return None, f"❌ Ошибка: {e}", gr.update(visible=False), state
201
-
202
-
203
- def ask_question(state):
204
- """Шаг 2: лектор задает вопрос с вариантами ответа."""
205
- if not state:
206
- return None, "❌ Ошибка: сначала запустите урок", gr.update(visible=False), gr.update(visible=False)
207
-
208
- try:
209
- image_path = state.get('image_path')
210
- question = state.get('question')
211
- options = state.get('options', [])
212
-
213
- # Формируем текст вопроса с вариантами
214
- quiz_text = f"{question}. Первый вариант: {options[0]}. Второй вариант: {options[1]}"
215
 
216
- # Генерируем аудио и видео с вопросом
217
- audio_path = synthesize_audio(quiz_text)
218
- video_path = make_talking_head(image_path, audio_path)
219
-
220
- # Удаляем временный аудио файл
221
- try:
222
- os.remove(audio_path)
223
- except:
224
- pass
225
-
226
  return (
227
  video_path,
228
- f"**Вопрос:** {question}",
229
- gr.update(value=options[0], visible=True),
230
- gr.update(value=options[1], visible=True)
 
231
  )
232
 
233
  except Exception as e:
234
  traceback.print_exc()
235
- return None, f"❌ Ошибка: {e}", gr.update(visible=False), gr.update(visible=False)
236
 
237
 
238
  def answer_selected(selected_option: str, state):
239
- """Шаг 3: пользователь выбирает вариант генерируем реакцию лектора."""
240
  if not state:
241
  return None, "❌ Ошибка: отсутствует состояние урока"
242
 
@@ -245,13 +252,16 @@ def answer_selected(selected_option: str, state):
245
  image_path = state.get('image_path')
246
 
247
  if selected_option == correct:
248
- reaction_ru = "Правильно! Молодец!"
249
- display_message = "✅ Дұрыс! Жарайсың!"
250
  else:
251
- reaction_ru = f"Неправильно. Правильный ответ: {correct}"
252
- display_message = f"❌ Қате. Дұрыс жауап: {correct}"
253
 
 
254
  audio_path = synthesize_audio(reaction_ru)
 
 
255
  reaction_video = make_talking_head(image_path, audio_path)
256
 
257
  try:
@@ -273,9 +283,8 @@ title = "🎓 Интерактивный бейне-лектор"
273
  description = (
274
  "**Как работает:**\n"
275
  "1. Загрузите фото лектора и введите текст лекции (русский, до 500 символов)\n"
276
- "2. Нажмите 'Запустить урок' — лектор прочитает текст\n"
277
- "3. Нажмите 'Задать вопрос' — лектор задаст вопрос с двумя вариантами ответа\n"
278
- "4. Выберите правильный ответ — лектор отреагирует на қазақша"
279
  )
280
 
281
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
@@ -290,57 +299,46 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
290
  placeholder='Введите текст лекции...',
291
  info="Максимум 500 символов"
292
  )
293
- btn_start = gr.Button("🚀 Запустить урок", variant="primary")
294
- btn_question = gr.Button("❓ Задать вопрос", visible=False, variant="secondary")
295
 
296
  with gr.Column(scale=1):
297
  out_video = gr.Video(label='🎬 Видео лектора')
298
- out_status = gr.Markdown("ℹ️ Загрузите фото и текст, затем нажмите 'Запустить урок'")
299
 
300
  with gr.Row():
301
- btn_opt1 = gr.Button("Вариант 1", visible=False, size="lg")
302
- btn_opt2 = gr.Button("Вариант 2", visible=False, size="lg")
303
 
304
- out_reaction_video = gr.Video(label='🎥 Реакция лектора', visible=False)
305
  out_result = gr.Markdown("")
306
 
307
  lesson_state = gr.State({})
308
 
309
- # Шаг 1: Запуск урока (чтение текста)
310
  btn_start.click(
311
  fn=start_lesson,
312
  inputs=[inp_image, inp_text, lesson_state],
313
- outputs=[out_video, out_status, btn_question, lesson_state]
314
- )
315
-
316
- # Шаг 2: Задать вопрос
317
- btn_question.click(
318
- fn=ask_question,
319
- inputs=[lesson_state],
320
- outputs=[out_video, out_status, btn_opt1, btn_opt2]
321
  )
322
 
323
- # Шаг 3: Обработка ответов
324
  def handle_answer_1(state):
325
  option = state.get('options', [''])[0] if state else ''
326
- video, msg = answer_selected(option, state)
327
- return video, msg, gr.update(visible=True)
328
 
329
  def handle_answer_2(state):
330
  option = state.get('options', [''])[1] if state and len(state.get('options', [])) > 1 else ''
331
- video, msg = answer_selected(option, state)
332
- return video, msg, gr.update(visible=True)
333
 
334
  btn_opt1.click(
335
  fn=handle_answer_1,
336
  inputs=[lesson_state],
337
- outputs=[out_reaction_video, out_result, out_reaction_video]
338
  )
339
 
340
  btn_opt2.click(
341
  fn=handle_answer_2,
342
  inputs=[lesson_state],
343
- outputs=[out_reaction_video, out_result, out_reaction_video]
344
  )
345
 
346
  if __name__ == '__main__':
 
9
  import traceback
10
  import random
11
  import time
12
+ import numpy as np
13
+ from pydub import AudioSegment
14
 
15
  # =========================
16
  # Параметры
 
54
  def generate_quiz(text: str):
55
  """
56
  Генерирует один вопрос и два варианта ответа (correct, wrong) на русском языке.
 
57
  """
58
  prompt = (
59
  "Сгенерируй один учебный вопрос по этому тексту и два варианта ответа (правильный и неправильный). "
 
61
  )
62
  try:
63
  out = qa_model(prompt, max_length=256)[0]["generated_text"]
 
64
  import re
65
  match = re.search(
66
  r"Вопрос\s*[:\-]\s*(.*?)\s*Ответ1\s*[:\-]\s*(.*?)\s*Ответ2\s*[:\-]\s*(.*)", out, re.DOTALL | re.IGNORECASE
67
  )
68
  if not match:
 
69
  lines = [l.strip() for l in out.splitlines() if l.strip()]
70
  if len(lines) >= 3:
71
  question, correct, wrong = lines[:3]
72
  else:
 
73
  parts = out.split(".")
74
  question = parts[0] if len(parts) > 0 else "Вопрос"
75
  correct = parts[1] if len(parts) > 1 else "Вариант 1"
 
84
  random.shuffle(options)
85
  return question, options, correct
86
  except Exception as e:
87
+ raise ValueError(f"Ошибка генерации вопроса:\n{str(e)}")
88
 
89
 
90
  def synthesize_audio(text_ru: str):
 
106
  return tmpf.name
107
 
108
 
109
+ def concatenate_audio_files(audio_files):
110
+ """Объединяет несколько аудио файлов в один с паузами между ними"""
111
+ combined = AudioSegment.empty()
112
+ pause = AudioSegment.silent(duration=1000) # 1 секунда паузы
113
+
114
+ for i, audio_file in enumerate(audio_files):
115
+ audio = AudioSegment.from_wav(audio_file)
116
+ combined += audio
117
+ if i < len(audio_files) - 1: # Не добавляем паузу после последнего файла
118
+ combined += pause
119
+
120
+ output_file = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
121
+ combined.export(output_file.name, format='wav')
122
+ output_file.close()
123
+ return output_file.name
124
+
125
+
126
  def make_talking_head(image_path: str, audio_path: str, max_retries=3):
127
  """Вызывает SkyReels/Talking Head space и возвращает путь или URL видео."""
128
  for attempt in range(max_retries):
 
136
  api_name="/process_image_audio"
137
  )
138
 
 
139
  print(f"Result type: {type(result)}")
140
  print(f"Result content: {result}")
141
 
 
142
  if isinstance(result, tuple):
 
143
  video_path = result[0]
144
  if isinstance(video_path, dict) and "video" in video_path:
145
  return video_path["video"]
146
  elif isinstance(video_path, str):
147
  return video_path
148
  else:
 
149
  for item in result:
150
  if isinstance(item, str) and (item.endswith('.mp4') or item.endswith('.webm') or os.path.exists(str(item))):
151
  return item
 
169
  # Основные обработчики для Gradio
170
  # =========================
171
  def start_lesson(image: Image.Image, text: str, state):
172
+ """Создает одно видео: текст лекции + вопрос с вариантами ответа"""
173
  if image is None or not text.strip() or len(text) > 500:
174
+ return None, "Пожалуйста, загрузите фото и введите текст лекции (до 500 символов)", gr.update(visible=False), gr.update(visible=False), state
175
 
176
  try:
177
  # Сохраняем изображение
 
182
  tmpimg.close()
183
  image_path = tmpimg.name
184
 
185
+ # Генерируем вопрос
186
  question, options, correct = generate_quiz(text)
187
 
188
+ # Создаем три аудио файла
189
+ audio_files = []
190
+
191
+ # 1. Текст лекции
192
+ audio1 = synthesize_audio(text)
193
+ audio_files.append(audio1)
194
+
195
+ # 2. Вопрос
196
+ question_text = f"А теперь вопрос: {question}"
197
+ audio2 = synthesize_audio(question_text)
198
+ audio_files.append(audio2)
199
+
200
+ # 3. Варианты ответа
201
+ options_text = f"Первый вариант: {options[0]}. Второй вариант: {options[1]}"
202
+ audio3 = synthesize_audio(options_text)
203
+ audio_files.append(audio3)
204
+
205
+ # Объединяем все аудио в одно
206
+ combined_audio = concatenate_audio_files(audio_files)
207
+
208
+ # Создаем одно видео с полным содержанием
209
+ video_path = make_talking_head(image_path, combined_audio)
210
 
211
  # Сохраняем состояние
212
  state_data = {
213
  'image_path': image_path,
214
  'correct': correct,
215
  'options': options,
216
+ 'question': question
 
217
  }
218
 
219
+ # Удаляем временные аудио файлы
220
+ for audio_file in audio_files:
221
+ try:
222
+ os.remove(audio_file)
223
+ except:
224
+ pass
225
+ try:
226
+ os.remove(combined_audio)
227
+ except:
228
  pass
229
 
230
+ question_display = f"**Вопрос:** {question}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
 
 
 
 
 
 
 
 
 
 
 
232
  return (
233
  video_path,
234
+ question_display,
235
+ gr.update(value=options[0], visible=True),
236
+ gr.update(value=options[1], visible=True),
237
+ state_data
238
  )
239
 
240
  except Exception as e:
241
  traceback.print_exc()
242
+ return None, f"❌ Ошибка: {e}", gr.update(visible=False), gr.update(visible=False), state
243
 
244
 
245
  def answer_selected(selected_option: str, state):
246
+ """Генерирует реакцию лектора и показывает в том же окне"""
247
  if not state:
248
  return None, "❌ Ошибка: отсутствует состояние урока"
249
 
 
252
  image_path = state.get('image_path')
253
 
254
  if selected_option == correct:
255
+ reaction_ru = "Правильно! Отлично справились!"
256
+ display_message = "✅ **Дұрыс! Жарайсың!**"
257
  else:
258
+ reaction_ru = f"К сожалению неправильно. Правильный ответ был: {correct}"
259
+ display_message = f"❌ **Қате!** Дұрыс жауап: **{correct}**"
260
 
261
+ # Создаем аудио с реакцией
262
  audio_path = synthesize_audio(reaction_ru)
263
+
264
+ # Создаем видео с реакцией
265
  reaction_video = make_talking_head(image_path, audio_path)
266
 
267
  try:
 
283
  description = (
284
  "**Как работает:**\n"
285
  "1. Загрузите фото лектора и введите текст лекции (русский, до 500 символов)\n"
286
+ "2. Нажмите 'Запустить урок' — лектор прочитает текст и задаст вопрос\n"
287
+ "3. Выберите правильный ответ — лектор отреагирует в том же окне"
 
288
  )
289
 
290
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
 
299
  placeholder='Введите текст лекции...',
300
  info="Максимум 500 символов"
301
  )
302
+ btn_start = gr.Button("🚀 Запустить урок", variant="primary", size="lg")
 
303
 
304
  with gr.Column(scale=1):
305
  out_video = gr.Video(label='🎬 Видео лектора')
306
+ out_question = gr.Markdown("")
307
 
308
  with gr.Row():
309
+ btn_opt1 = gr.Button("Вариант 1", visible=False, size="lg", variant="secondary")
310
+ btn_opt2 = gr.Button("Вариант 2", visible=False, size="lg", variant="secondary")
311
 
 
312
  out_result = gr.Markdown("")
313
 
314
  lesson_state = gr.State({})
315
 
316
+ # Запуск урока
317
  btn_start.click(
318
  fn=start_lesson,
319
  inputs=[inp_image, inp_text, lesson_state],
320
+ outputs=[out_video, out_question, btn_opt1, btn_opt2, lesson_state]
 
 
 
 
 
 
 
321
  )
322
 
323
+ # Обработка ответов
324
  def handle_answer_1(state):
325
  option = state.get('options', [''])[0] if state else ''
326
+ return answer_selected(option, state)
 
327
 
328
  def handle_answer_2(state):
329
  option = state.get('options', [''])[1] if state and len(state.get('options', [])) > 1 else ''
330
+ return answer_selected(option, state)
 
331
 
332
  btn_opt1.click(
333
  fn=handle_answer_1,
334
  inputs=[lesson_state],
335
+ outputs=[out_video, out_result]
336
  )
337
 
338
  btn_opt2.click(
339
  fn=handle_answer_2,
340
  inputs=[lesson_state],
341
+ outputs=[out_video, out_result]
342
  )
343
 
344
  if __name__ == '__main__':