AlserFurma commited on
Commit
6270461
·
verified ·
1 Parent(s): 64354df

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -90
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # Полная обновлённая версия app.py с двухшаговым интерактивным уроком (короткая реакция на казахском)
2
 
3
  import gradio as gr
4
  import os
@@ -34,7 +34,7 @@ try:
34
  device=0 if device == "cuda" else -1
35
  )
36
 
37
- # Модель для генерации вопросов (text2text)
38
  qa_model = pipeline(
39
  "text2text-generation",
40
  model="google/flan-t5-small",
@@ -55,7 +55,8 @@ def generate_quiz(text: str):
55
  """Генерирует один вопрос и два варианта (correct, wrong) на русском языке."""
56
  prompt = (
57
  "Сгенерируй один учебный вопрос по этому тексту и дай 1 правильный и 1 неправильный вариант ответа. "
58
- "Формат вывода (разделять переносами строки): QUESTION: ... CORRECT: ... WRONG: ... TEXT: " + text
 
59
  )
60
  try:
61
  out = qa_model(prompt, max_length=200)[0]["generated_text"]
@@ -65,8 +66,9 @@ def generate_quiz(text: str):
65
  question = ""
66
  correct = ""
67
  wrong = ""
68
- for line in out.split('
69
- '):
 
70
  if line.upper().startswith("QUESTION:"):
71
  question = line.split(':', 1)[1].strip()
72
  elif line.upper().startswith("CORRECT:"):
@@ -75,7 +77,6 @@ def generate_quiz(text: str):
75
  wrong = line.split(':', 1)[1].strip()
76
 
77
  if not (question and correct and wrong):
78
- # Попытка более простого разбора
79
  parts = out.split('CORRECT:')
80
  if len(parts) > 1:
81
  qpart = parts[0]
@@ -87,7 +88,7 @@ def generate_quiz(text: str):
87
  wrong = wrong.strip()
88
 
89
  if not (question and correct and wrong):
90
- raise ValueError('Модель не смогла корректно сгенерировать вопрос/варианты')
91
 
92
  options = [correct, wrong]
93
  random.shuffle(options)
@@ -95,8 +96,7 @@ def generate_quiz(text: str):
95
 
96
 
97
  def synthesize_audio(text_ru: str):
98
- """Переводит русскую строку на казахский, синтезирует аудио и возвращает путь к файлу .wav"""
99
- # Переводим на казахский
100
  translation = translator(text_ru, src_lang="rus_Cyrl", tgt_lang="kaz_Cyrl")
101
  text_kk = translation[0]["translation_text"]
102
 
@@ -111,14 +111,13 @@ def synthesize_audio(text_ru: str):
111
  audio = (waveform * 32767).astype('int16')
112
  sampling_rate = getattr(tts_model.config, 'sampling_rate', 22050)
113
 
114
- tmpf = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
115
  wavfile.write(tmpf.name, sampling_rate, audio)
116
  tmpf.close()
117
  return tmpf.name
118
 
119
 
120
  def make_talking_head(image_path: str, audio_path: str):
121
- """Вызывает SkyReels/Talking Head space и возвращает путь или объект с видео."""
122
  client = Client(TALKING_HEAD_SPACE)
123
  try:
124
  result = client.predict(
@@ -129,80 +128,64 @@ def make_talking_head(image_path: str, audio_path: str):
129
  api_name="/process_image_audio"
130
  )
131
  except Exception as e:
132
- raise RuntimeError(f"Ошибка вызова Talking Head API: {e}")
133
 
134
  video_path = None
 
135
  if isinstance(result, tuple) and len(result) > 0:
136
  video_data = result[0]
137
- elif isinstance(result, dict):
138
- video_data = result
139
  else:
140
  video_data = result
141
 
142
  if isinstance(video_data, dict):
143
- video_path = video_data.get('video') or video_data.get('path') or video_data.get('file')
144
  elif isinstance(video_data, str):
145
  video_path = video_data
146
 
147
  if not video_path:
148
- raise ValueError('API не вернул путь к видео')
149
 
150
  return video_path
151
 
152
 
153
  # =========================
154
- # Основные обработчики для Gradio
155
  # =========================
156
 
157
  def start_lesson(image: Image.Image, text: str, state):
158
- """Шаг 1: генерируем видео-лекцию с вопросом и вариантами ответа.
159
- Возвращаем видео, текст вопроса, два варианта и сохраняем правильный ответ + путь к изображению в state."""
160
- if image is None:
161
- return None, "", [], [], state
162
- if not text or not text.strip():
163
- return None, "", [], [], state
164
- if len(text) > 500:
165
  return None, "", [], [], state
166
 
167
  try:
168
- # 1) Генерация вопроса
169
  question, options, correct = generate_quiz(text)
170
 
171
- # 2) Подготовить текст, который лектор произнесёт (вопрос + варианты)
172
  quiz_ru = f"Вопрос: {question} Варианты: 1) {options[0]} 2) {options[1]}"
173
 
174
- # 3) Синтез аудио для вопроса (на казахском внутри функции synthesize_audio)
175
  audio_path = synthesize_audio(quiz_ru)
176
 
177
- # 4) Сохранение фото во временный файл (чтобы передать в Talking Head API)
178
- tmpimg = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
179
- if image.mode != 'RGB':
180
- image = image.convert('RGB')
181
  image.save(tmpimg.name)
182
  tmpimg.close()
183
  image_path = tmpimg.name
184
 
185
- # 5) Генерация видео через Talking Head
186
  video_path = make_talking_head(image_path, audio_path)
187
 
188
- # 6) Сохраняем в state необходимые значения (image_path и correct ответ)
189
  state_data = {
190
- 'image_path': image_path,
191
- 'correct': correct,
192
- 'options': options
193
  }
194
 
195
- # 7) Сообщение состояния: вернём question и варианты в RU для отображения
196
- question_display = question
197
-
198
- # Удалим audio временный файл (видео уже сгенерировано)
199
- try:
200
- if os.path.exists(audio_path):
201
- os.remove(audio_path)
202
- except:
203
- pass
204
 
205
- return video_path, question_display, options, state_data, state_data
206
 
207
  except Exception as e:
208
  traceback.print_exc()
@@ -210,38 +193,26 @@ def start_lesson(image: Image.Image, text: str, state):
210
 
211
 
212
  def answer_selected(selected_option: str, state):
213
- """Шаг 2: пользователь выбирает вариант — генерируем реакцию лектора (короткая на казахском).
214
- state должен содержать image_path и correct ответ."""
215
  if not state:
216
- return None, "Ошибка: отсутствует состояние урока. Сначала нажмите 'Запустить урок'."
217
- try:
218
- correct = state.get('correct')
219
- image_path = state.get('image_path')
220
- options = state.get('options', [])
221
 
222
- if selected_option not in options:
223
- # Иногда selected comes as index or value; try to handle
224
- pass
 
225
 
226
  if selected_option == correct:
227
- reaction_ru = "Молодец!" # короткая реакция на русском — переведём на казахский в synthesize_audio
228
- display_message = "Дұрыс!" # сообщение для интерфейса (можно сразу на казахском)
229
  else:
230
  reaction_ru = f"Неправильно. Правильный ответ: {correct}"
231
  display_message = f"Қате. Дұрыс жауап: {correct}"
232
 
233
- # Синтезируем реакцию (на казахском внутри)
234
  audio_path = synthesize_audio(reaction_ru)
235
-
236
- # Генерируем видео-реакцию с тем же изображением
237
  reaction_video = make_talking_head(image_path, audio_path)
238
 
239
- # Удаляем временный audio
240
- try:
241
- if os.path.exists(audio_path):
242
- os.remove(audio_path)
243
- except:
244
- pass
245
 
246
  return reaction_video, display_message
247
 
@@ -251,50 +222,50 @@ def answer_selected(selected_option: str, state):
251
 
252
 
253
  # =========================
254
- # Gradio UI (двухшаговый)
255
  # =========================
256
 
257
  title = "🎓 Интерактивный бейне-лектор"
258
 
259
  description = (
260
- "Загрузите фото лектора и текст лекции (орыс тілінде, до 500 символов).
261
- "
262
- "Система создаст видео-лектора, задаст вопрос и предложит 2 варианта ответа.
263
- "
264
- "Нажмите на один из вариантов — лектор коротко отреагирует (қазақша)."
265
  )
266
 
267
  with gr.Blocks() as demo:
268
- gr.Markdown(f"# {title}
269
- {description}")
270
 
271
  with gr.Row():
272
  with gr.Column(scale=1):
273
- inp_image = gr.Image(type='pil', label='📸 Фото лектора')
274
- inp_text = gr.Textbox(lines=5, label='📝 Текст лекции (рус.)', placeholder='Введите текст...')
275
  btn_start = gr.Button("Запустить урок")
276
 
277
  with gr.Column(scale=1):
278
- out_video = gr.Video(label='🎬 Видео лектора')
279
- out_question = gr.Markdown(label='Вопрос')
280
- # Кнопки для двух вариантов; изначально пустые
281
  btn_opt1 = gr.Button("Вариант 1")
282
  btn_opt2 = gr.Button("Вариант 2")
283
- out_reaction_video = gr.Video(label='🎥 Реакция лектора')
284
- out_status = gr.Textbox(label='ℹ️ Статус', interactive=False)
285
 
286
- # State для хранения данных между шагами
 
 
287
  lesson_state = gr.State({})
288
 
289
- # Привязки
290
- btn_start.click(fn=start_lesson, inputs=[inp_image, inp_text, lesson_state], outputs=[out_video, out_question, btn_opt1, btn_opt2, lesson_state])
 
 
 
291
 
292
- # Когда пользователь нажимает один из вариантов, вызываем answer_selected
293
- btn_opt1.click(fn=answer_selected, inputs=[btn_opt1, lesson_state], outputs=[out_reaction_video, out_status])
294
- btn_opt2.click(fn=answer_selected, inputs=[btn_opt2, lesson_state], outputs=[out_reaction_video, out_status])
 
295
 
296
- # Небольшая подсказка при запуске
297
  demo.load(lambda: "Готово", outputs=out_status)
298
 
299
- if __name__ == '__main__':
300
  demo.launch()
 
1
+ # Полная исправленная версия app.py
2
 
3
  import gradio as gr
4
  import os
 
34
  device=0 if device == "cuda" else -1
35
  )
36
 
37
+ # Модель генерации вопросов
38
  qa_model = pipeline(
39
  "text2text-generation",
40
  model="google/flan-t5-small",
 
55
  """Генерирует один вопрос и два варианта (correct, wrong) на русском языке."""
56
  prompt = (
57
  "Сгенерируй один учебный вопрос по этому тексту и дай 1 правильный и 1 неправильный вариант ответа. "
58
+ "Формат вывода (разделять переносами строки): "
59
+ "QUESTION: ...\nCORRECT: ...\nWRONG: ...\nTEXT: " + text
60
  )
61
  try:
62
  out = qa_model(prompt, max_length=200)[0]["generated_text"]
 
66
  question = ""
67
  correct = ""
68
  wrong = ""
69
+
70
+ # ИСПРАВЛЕНО: корректное split('\n')
71
+ for line in out.split('\n'):
72
  if line.upper().startswith("QUESTION:"):
73
  question = line.split(':', 1)[1].strip()
74
  elif line.upper().startswith("CORRECT:"):
 
77
  wrong = line.split(':', 1)[1].strip()
78
 
79
  if not (question and correct and wrong):
 
80
  parts = out.split('CORRECT:')
81
  if len(parts) > 1:
82
  qpart = parts[0]
 
88
  wrong = wrong.strip()
89
 
90
  if not (question and correct and wrong):
91
+ raise ValueError("Модель не смогла корректно сгенерировать вопрос/варианты")
92
 
93
  options = [correct, wrong]
94
  random.shuffle(options)
 
96
 
97
 
98
  def synthesize_audio(text_ru: str):
99
+ """Переводит русскую строку на казахский, синтезирует аудио и возвращает путь к файлу."""
 
100
  translation = translator(text_ru, src_lang="rus_Cyrl", tgt_lang="kaz_Cyrl")
101
  text_kk = translation[0]["translation_text"]
102
 
 
111
  audio = (waveform * 32767).astype('int16')
112
  sampling_rate = getattr(tts_model.config, 'sampling_rate', 22050)
113
 
114
+ tmpf = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
115
  wavfile.write(tmpf.name, sampling_rate, audio)
116
  tmpf.close()
117
  return tmpf.name
118
 
119
 
120
  def make_talking_head(image_path: str, audio_path: str):
 
121
  client = Client(TALKING_HEAD_SPACE)
122
  try:
123
  result = client.predict(
 
128
  api_name="/process_image_audio"
129
  )
130
  except Exception as e:
131
+ raise RuntimeError(f"Ошибка Talking Head API: {e}")
132
 
133
  video_path = None
134
+
135
  if isinstance(result, tuple) and len(result) > 0:
136
  video_data = result[0]
 
 
137
  else:
138
  video_data = result
139
 
140
  if isinstance(video_data, dict):
141
+ video_path = video_data.get("video") or video_data.get("path") or video_data.get("file")
142
  elif isinstance(video_data, str):
143
  video_path = video_data
144
 
145
  if not video_path:
146
+ raise ValueError("API не вернул путь к видео")
147
 
148
  return video_path
149
 
150
 
151
  # =========================
152
+ # Логика Gradio
153
  # =========================
154
 
155
  def start_lesson(image: Image.Image, text: str, state):
156
+ if image is None or not text or not text.strip() or len(text) > 500:
 
 
 
 
 
 
157
  return None, "", [], [], state
158
 
159
  try:
160
+ # Генерируем вопрос
161
  question, options, correct = generate_quiz(text)
162
 
 
163
  quiz_ru = f"Вопрос: {question} Варианты: 1) {options[0]} 2) {options[1]}"
164
 
 
165
  audio_path = synthesize_audio(quiz_ru)
166
 
167
+ # Сохраняем изображение
168
+ tmpimg = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
169
+ if image.mode != "RGB":
170
+ image = image.convert("RGB")
171
  image.save(tmpimg.name)
172
  tmpimg.close()
173
  image_path = tmpimg.name
174
 
 
175
  video_path = make_talking_head(image_path, audio_path)
176
 
177
+ # Стейт
178
  state_data = {
179
+ "image_path": image_path,
180
+ "correct": correct,
181
+ "options": options
182
  }
183
 
184
+ # Удаляем аудио
185
+ if os.path.exists(audio_path):
186
+ os.remove(audio_path)
 
 
 
 
 
 
187
 
188
+ return video_path, question, options, state_data, state_data
189
 
190
  except Exception as e:
191
  traceback.print_exc()
 
193
 
194
 
195
  def answer_selected(selected_option: str, state):
 
 
196
  if not state:
197
+ return None, "Ошибка: нет состояния. Нажмите 'Запустить урок'."
 
 
 
 
198
 
199
+ try:
200
+ correct = state.get("correct")
201
+ image_path = state.get("image_path")
202
+ options = state.get("options", [])
203
 
204
  if selected_option == correct:
205
+ reaction_ru = "Молодец!"
206
+ display_message = "Дұрыс!"
207
  else:
208
  reaction_ru = f"Неправильно. Правильный ответ: {correct}"
209
  display_message = f"Қате. Дұрыс жауап: {correct}"
210
 
 
211
  audio_path = synthesize_audio(reaction_ru)
 
 
212
  reaction_video = make_talking_head(image_path, audio_path)
213
 
214
+ if os.path.exists(audio_path):
215
+ os.remove(audio_path)
 
 
 
 
216
 
217
  return reaction_video, display_message
218
 
 
222
 
223
 
224
  # =========================
225
+ # Интерфейс Gradio
226
  # =========================
227
 
228
  title = "🎓 Интерактивный бейне-лектор"
229
 
230
  description = (
231
+ "Загрузите фото лектора и текст лекции (рус., до 500 символов).<br>"
232
+ "Система создаст видео-лектора, задаст вопрос и предложит 2 варианта ответа.<br>"
233
+ "После выбора варианта лектор коротко ответит по-казахски."
 
 
234
  )
235
 
236
  with gr.Blocks() as demo:
237
+ gr.Markdown(f"# {title}<br>{description}")
 
238
 
239
  with gr.Row():
240
  with gr.Column(scale=1):
241
+ inp_image = gr.Image(type='pil', label="📸 Фото лектора")
242
+ inp_text = gr.Textbox(lines=5, label="📝 Текст лекции (рус.)")
243
  btn_start = gr.Button("Запустить урок")
244
 
245
  with gr.Column(scale=1):
246
+ out_video = gr.Video(label="🎬 Видео лектора")
247
+ out_question = gr.Markdown(label="Вопрос")
248
+
249
  btn_opt1 = gr.Button("Вариант 1")
250
  btn_opt2 = gr.Button("Вариант 2")
 
 
251
 
252
+ out_reaction_video = gr.Video(label="🎥 Реакция лектора")
253
+ out_status = gr.Textbox(label="ℹ️ Статус", interactive=False)
254
+
255
  lesson_state = gr.State({})
256
 
257
+ btn_start.click(
258
+ fn=start_lesson,
259
+ inputs=[inp_image, inp_text, lesson_state],
260
+ outputs=[out_video, out_question, btn_opt1, btn_opt2, lesson_state]
261
+ )
262
 
263
+ btn_opt1.click(fn=answer_selected, inputs=[btn_opt1, lesson_state],
264
+ outputs=[out_reaction_video, out_status])
265
+ btn_opt2.click(fn=answer_selected, inputs=[btn_opt2, lesson_state],
266
+ outputs=[out_reaction_video, out_status])
267
 
 
268
  demo.load(lambda: "Готово", outputs=out_status)
269
 
270
+ if __name__ == "__main__":
271
  demo.launch()