AlserFurma commited on
Commit
7802c36
·
verified ·
1 Parent(s): bd33908

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -68
app.py CHANGED
@@ -54,8 +54,12 @@ except Exception as e:
54
  # Вспомогательные функции
55
  # =========================
56
  def generate_quiz(text: str):
57
- """ Генерирует один вопрос и два варианта ответа на основе текста. """
58
- # (Оставляем как есть, без изменений)
 
 
 
 
59
  try:
60
  sentences = [s.strip() for s in text.replace("!", ".").replace("?", ".").split(".") if s.strip()]
61
  if len(sentences) < 1:
@@ -63,6 +67,7 @@ def generate_quiz(text: str):
63
 
64
  algo = random.choice([1, 2, 3])
65
 
 
66
  if algo == 1: # Базовый алгоритм
67
  question_sentence = random.choice(sentences)
68
  words = question_sentence.split()
@@ -76,6 +81,7 @@ def generate_quiz(text: str):
76
  wrong_words = wrong_sentence.split()
77
  wrong_answer = " ".join(wrong_words[:6]) + ("..." if len(wrong_words) > 6 else "")
78
 
 
79
  elif algo == 2: # Пропуск ключевого слова
80
  question_sentence = random.choice(sentences)
81
  words = question_sentence.split()
@@ -85,8 +91,10 @@ def generate_quiz(text: str):
85
  correct_answer = key_word
86
  wrong_answer = random.choice([w for w in words if w != key_word] or ["другое"])
87
  else:
 
88
  return generate_quiz(text)
89
 
 
90
  elif algo == 3: # Вопрос о числе или дате
91
  import re
92
  question_sentence = random.choice(sentences)
@@ -97,6 +105,7 @@ def generate_quiz(text: str):
97
  correct_answer = number
98
  wrong_answer = str(int(number)+random.randint(1,5))
99
  else:
 
100
  return generate_quiz(text)
101
 
102
  options = [correct_answer, wrong_answer]
@@ -107,7 +116,6 @@ def generate_quiz(text: str):
107
 
108
  def synthesize_audio(text_ru: str):
109
  """Переводит русскую строку на казахский, синтезирует аудио и возвращает путь к файлу .wav"""
110
- # (Оставляем как есть, с нормализацией)
111
  translation = translator(text_ru, src_lang="rus_Cyrl", tgt_lang="kaz_Cyrl")
112
  text_kk = translation[0]["translation_text"]
113
 
@@ -129,14 +137,13 @@ def synthesize_audio(text_ru: str):
129
 
130
  def concatenate_audio_files(audio_files):
131
  """Объединяет несколько аудио файлов в один с паузами между ними"""
132
- # (Оставляем как есть)
133
  combined = AudioSegment.empty()
134
  pause = AudioSegment.silent(duration=1000) # 1 секунда паузы
135
 
136
  for i, audio_file in enumerate(audio_files):
137
  audio = AudioSegment.from_wav(audio_file)
138
  combined += audio
139
- if i < len(audio_files) - 1:
140
  combined += pause
141
 
142
  output_file = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
@@ -146,7 +153,6 @@ def concatenate_audio_files(audio_files):
146
 
147
  def make_talking_head(image_path: str, audio_path: str, max_retries=3):
148
  """Вызывает SkyReels/Talking Head space и возвращает путь или URL видео."""
149
- # (Оставляем как есть)
150
  for attempt in range(max_retries):
151
  try:
152
  client = Client(TALKING_HEAD_SPACE)
@@ -188,7 +194,7 @@ def make_talking_head(image_path: str, audio_path: str, max_retries=3):
188
  # Основные обработчики для Gradio
189
  # =========================
190
  def start_lesson(image: Image.Image, text: str, state):
191
- """Генерирует все сегменты видео заранее и начинает интерактивную лекцию"""
192
  if image is None or not text.strip() or len(text) > 500:
193
  return None, "Пожалуйста, загрузите фото и введите текст лекции (до 500 символов)", gr.update(visible=False), gr.update(visible=False), state
194
 
@@ -204,58 +210,51 @@ def start_lesson(image: Image.Image, text: str, state):
204
  # Генерируем вопрос
205
  question, options, correct = generate_quiz(text)
206
 
207
- # Создаем аудио для всех частей заранее
208
- audio_files = [] # Для лекции + вопрос + варианты
209
- reaction_audios = {} # Для реакций
210
 
211
- # 1. Аудио лекции
212
- audio_lecture = synthesize_audio(text)
213
- audio_files.append(audio_lecture)
214
 
215
- # 2. Аудио вопроса
216
  question_text = f"А теперь вопрос: {question}"
217
- audio_question = synthesize_audio(question_text)
218
- audio_files.append(audio_question)
219
 
220
- # 3. Аудио вариантов
221
  options_text = f"Первый вариант: {options[0]}. Второй вариант: {options[1]}"
222
- audio_options = synthesize_audio(options_text)
223
- audio_files.append(audio_options)
224
 
225
- # Объединяем аудио для основного видео (лекция + вопрос + варианты)
226
- combined_audio_main = concatenate_audio_files(audio_files)
227
 
228
- # Генерируем основное видео
229
- video_main = make_talking_head(image_path, combined_audio_main)
230
 
231
- # Генерируем реакции заранее
232
- reaction_correct_ru = "Правильно! Отлично справились!"
233
- audio_correct = synthesize_audio(reaction_correct_ru)
234
- video_correct = make_talking_head(image_path, audio_correct)
235
-
236
- reaction_wrong_ru = f"К сожалению неправильно. Правильный ответ был: {correct}"
237
- audio_wrong = synthesize_audio(reaction_wrong_ru)
238
- video_wrong = make_talking_head(image_path, audio_wrong)
239
-
240
- # Сохраняем состояние (пути к видео, для последовательного показа)
241
  state_data = {
242
  'image_path': image_path,
243
  'correct': correct,
244
  'options': options,
245
- 'question': question,
246
- 'video_main': video_main, # Первое видео: лекция + вопрос + варианты
247
- 'video_correct': video_correct,
248
- 'video_wrong': video_wrong,
249
- 'audio_files': audio_files + [audio_correct, audio_wrong, combined_audio_main], # Для cleanup
250
- 'step': 'main' # Текущий шаг лекции (для multi-step)
251
  }
252
 
253
- # Удаляем временные аудио (кроме тех, что в state для позднего cleanup)
254
- # (Очистку перенесём в конец сессии, если нужно)
 
 
 
 
 
 
 
 
255
 
256
- question_display = f"**Вопрос:** {question} (После просмотра лекции выберите ответ)"
257
  return (
258
- state_data['video_main'], # Показываем основное видео сначала
259
  question_display,
260
  gr.update(value=options[0], visible=True),
261
  gr.update(value=options[1], visible=True),
@@ -266,35 +265,31 @@ def start_lesson(image: Image.Image, text: str, state):
266
  return None, f"❌ Ошибка: {e}", gr.update(visible=False), gr.update(visible=False), state
267
 
268
  def answer_selected(selected_option: str, state):
269
- """Показывает предгенерированную реакцию и завершает шаг лекции"""
270
  if not state:
271
  return None, "❌ Ошибка: отсутствует состояние урока"
272
 
273
  try:
274
  correct = state.get('correct')
 
275
 
276
  if selected_option == correct:
277
- reaction_video = state['video_correct']
278
- display_message = "✅ **Дұрыс! Жарайсың!** \nЛекция завершена. Можно начать новую."
279
  else:
280
- reaction_video = state['video_wrong']
281
- display_message = f"❌ **Қате!** Дұрыс жауап: **{correct}** \nЛекция завершена. Можно начать новую."
282
 
283
- # Cleanup: Удаляем все временные файлы после показа
284
- for audio in state.get('audio_files', []):
285
- try:
286
- os.remove(audio)
287
- except:
288
- pass
289
- for video in [state.get('video_main'), state.get('video_correct'), state.get('video_wrong')]:
290
- if video and os.path.exists(video):
291
- try:
292
- os.remove(video)
293
- except:
294
- pass
295
 
296
- # Обновляем state на 'completed' для предотвращения повторений
297
- state['step'] = 'completed'
 
 
 
 
 
298
 
299
  return reaction_video, display_message
300
  except Exception as e:
@@ -308,8 +303,8 @@ title = "🎓 Интерактивті Бейне Мұғалім TiлГен"
308
  description = (
309
  "**Қалай жұмыс істейді:**\n"
310
  "1. Мұғалімнің суретін жүктеп, дәріс мәтінін енгізіңіз (орыс, 500 таңбаға дейін)\n"
311
- "2. 'Сабақты бастау' түймесін басыңыз генерируется вся лекция заранее, показывается видео с текстом, вопросом и вариантами\n"
312
- "3. Выберите ответ покажется реакция (предгенерированная)"
313
  )
314
 
315
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
@@ -345,14 +340,10 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
345
 
346
  # Обработка ответов
347
  def handle_answer_1(state):
348
- if state.get('step') != 'main':
349
- return None, "Лекция уже завершена"
350
  option = state.get('options', [''])[0] if state else ''
351
  return answer_selected(option, state)
352
 
353
  def handle_answer_2(state):
354
- if state.get('step') != 'main':
355
- return None, "Лекция уже завершена"
356
  option = state.get('options', [''])[1] if state and len(state.get('options', [])) > 1 else ''
357
  return answer_selected(option, state)
358
 
 
54
  # Вспомогательные функции
55
  # =========================
56
  def generate_quiz(text: str):
57
+ """ Генерирует один вопрос и два варианта ответа на основе текста.
58
+ Алгоритмы:
59
+ 1. Базовый: случайное предложение и первые слова.
60
+ 2. Пропуск ключевого слова.
61
+ 3. Вопрос о числе/дате.
62
+ """
63
  try:
64
  sentences = [s.strip() for s in text.replace("!", ".").replace("?", ".").split(".") if s.strip()]
65
  if len(sentences) < 1:
 
67
 
68
  algo = random.choice([1, 2, 3])
69
 
70
+ # ------------------------
71
  if algo == 1: # Базовый алгоритм
72
  question_sentence = random.choice(sentences)
73
  words = question_sentence.split()
 
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
  question_sentence = random.choice(sentences)
87
  words = question_sentence.split()
 
91
  correct_answer = key_word
92
  wrong_answer = random.choice([w for w in words if w != key_word] or ["другое"])
93
  else:
94
+ # fallback
95
  return generate_quiz(text)
96
 
97
+ # ------------------------
98
  elif algo == 3: # Вопрос о числе или дате
99
  import re
100
  question_sentence = random.choice(sentences)
 
105
  correct_answer = number
106
  wrong_answer = str(int(number)+random.randint(1,5))
107
  else:
108
+ # fallback к базовому
109
  return generate_quiz(text)
110
 
111
  options = [correct_answer, wrong_answer]
 
116
 
117
  def synthesize_audio(text_ru: str):
118
  """Переводит русскую строку на казахский, синтезирует аудио и возвращает путь к файлу .wav"""
 
119
  translation = translator(text_ru, src_lang="rus_Cyrl", tgt_lang="kaz_Cyrl")
120
  text_kk = translation[0]["translation_text"]
121
 
 
137
 
138
  def concatenate_audio_files(audio_files):
139
  """Объединяет несколько аудио файлов в один с паузами между ними"""
 
140
  combined = AudioSegment.empty()
141
  pause = AudioSegment.silent(duration=1000) # 1 секунда паузы
142
 
143
  for i, audio_file in enumerate(audio_files):
144
  audio = AudioSegment.from_wav(audio_file)
145
  combined += audio
146
+ if i < len(audio_files) - 1: # Не добавляем паузу после последнего файла
147
  combined += pause
148
 
149
  output_file = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
 
153
 
154
  def make_talking_head(image_path: str, audio_path: str, max_retries=3):
155
  """Вызывает SkyReels/Talking Head space и возвращает путь или URL видео."""
 
156
  for attempt in range(max_retries):
157
  try:
158
  client = Client(TALKING_HEAD_SPACE)
 
194
  # Основные обработчики для Gradio
195
  # =========================
196
  def start_lesson(image: Image.Image, text: str, state):
197
+ """Создает одно видео: текст лекции + вопрос с вариантами ответа"""
198
  if image is None or not text.strip() or len(text) > 500:
199
  return None, "Пожалуйста, загрузите фото и введите текст лекции (до 500 символов)", gr.update(visible=False), gr.update(visible=False), state
200
 
 
210
  # Генерируем вопрос
211
  question, options, correct = generate_quiz(text)
212
 
213
+ # Создаем три аудио файла
214
+ audio_files = []
 
215
 
216
+ # 1. Текст лекции
217
+ audio1 = synthesize_audio(text)
218
+ audio_files.append(audio1)
219
 
220
+ # 2. Вопрос
221
  question_text = f"А теперь вопрос: {question}"
222
+ audio2 = synthesize_audio(question_text)
223
+ audio_files.append(audio2)
224
 
225
+ # 3. Варианты ответа
226
  options_text = f"Первый вариант: {options[0]}. Второй вариант: {options[1]}"
227
+ audio3 = synthesize_audio(options_text)
228
+ audio_files.append(audio3)
229
 
230
+ # Объединяем все аудио в одно
231
+ combined_audio = concatenate_audio_files(audio_files)
232
 
233
+ # Создаем одно видео с полным содержанием
234
+ video_path = make_talking_head(image_path, combined_audio)
235
 
236
+ # Сохраняем состояние
 
 
 
 
 
 
 
 
 
237
  state_data = {
238
  'image_path': image_path,
239
  'correct': correct,
240
  'options': options,
241
+ 'question': question
 
 
 
 
 
242
  }
243
 
244
+ # Удаляем временные аудио файлы
245
+ for audio_file in audio_files:
246
+ try:
247
+ os.remove(audio_file)
248
+ except:
249
+ pass
250
+ try:
251
+ os.remove(combined_audio)
252
+ except:
253
+ pass
254
 
255
+ question_display = f"**Вопрос:** {question}"
256
  return (
257
+ video_path,
258
  question_display,
259
  gr.update(value=options[0], visible=True),
260
  gr.update(value=options[1], visible=True),
 
265
  return None, f"❌ Ошибка: {e}", gr.update(visible=False), gr.update(visible=False), state
266
 
267
  def answer_selected(selected_option: str, state):
268
+ """Генерирует реакцию лектора и показывает в том же окне"""
269
  if not state:
270
  return None, "❌ Ошибка: отсутствует состояние урока"
271
 
272
  try:
273
  correct = state.get('correct')
274
+ image_path = state.get('image_path')
275
 
276
  if selected_option == correct:
277
+ reaction_ru = "Правильно! Отлично справились!"
278
+ display_message = "✅ **Дұрыс! Жарайсың!**"
279
  else:
280
+ reaction_ru = f"К сожалению неправильно. Правильный ответ был: {correct}"
281
+ display_message = f"❌ **Қате!** Дұрыс жауап: **{correct}**"
282
 
283
+ # Создаем аудио с реакцией
284
+ audio_path = synthesize_audio(reaction_ru)
 
 
 
 
 
 
 
 
 
 
285
 
286
+ # Создаем видео с реакцией
287
+ reaction_video = make_talking_head(image_path, audio_path)
288
+
289
+ try:
290
+ os.remove(audio_path)
291
+ except:
292
+ pass
293
 
294
  return reaction_video, display_message
295
  except Exception as e:
 
303
  description = (
304
  "**Қалай жұмыс істейді:**\n"
305
  "1. Мұғалімнің суретін жүктеп, дәріс мәтінін енгізіңіз (орыс, 500 таңбаға дейін)\n"
306
+ "2. 'Сабақты бастау' түймесін басыңыз-мұғалім мәтінді оқып, сұрақ қояды\n"
307
+ "3. Дұрыс жауапты таңдаңыз-мұғалім сіздің жауабыңызға жауап береді"
308
  )
309
 
310
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
 
340
 
341
  # Обработка ответов
342
  def handle_answer_1(state):
 
 
343
  option = state.get('options', [''])[0] if state else ''
344
  return answer_selected(option, state)
345
 
346
  def handle_answer_2(state):
 
 
347
  option = state.get('options', [''])[1] if state and len(state.get('options', [])) > 1 else ''
348
  return answer_selected(option, state)
349