AlserFurma commited on
Commit
670074b
·
verified ·
1 Parent(s): 94b627a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +238 -252
app.py CHANGED
@@ -4,96 +4,78 @@ from PIL import Image
4
  import tempfile
5
  from gradio_client import Client, handle_file
6
  import torch
7
- from transformers import VitsModel, AutoTokenizer, pipeline, T5Tokenizer, T5ForConditionalGeneration
8
  import scipy.io.wavfile as wavfile
9
  import traceback
10
  import base64
11
- import nltk
12
  import random
13
 
14
- # Инициализация NLTK
15
- try:
16
- nltk.data.find('tokenizers/punkt')
17
- except LookupError:
18
- nltk.download('punkt', quiet=True)
19
- from nltk.tokenize import sent_tokenize
20
 
21
- # Глобальные переменные для моделей
22
- device = "cuda" if torch.cuda.is_available() else "cpu"
23
- print(f"Using device: {device}")
24
 
 
25
  tts_model = None
26
  tts_tokenizer = None
27
- translator = None
28
- qg_tokenizer = None
29
- qg_model = None
30
- qa_pipeline = None
31
 
32
  TALKING_HEAD_SPACE = "Skywork/skyreels-a1-talking-head"
33
 
34
- def load_models():
35
- """Ленивая загрузка моделей при первом использовании"""
36
- global tts_model, tts_tokenizer, translator, qg_tokenizer, qg_model, qa_pipeline
37
 
38
- try:
39
- if tts_model is None:
40
- print("Загрузка TTS модели...")
41
- tts_model = VitsModel.from_pretrained("facebook/mms-tts-kaz").to(device)
42
- tts_tokenizer = AutoTokenizer.from_pretrained("facebook/mms-tts-kaz")
43
- print("✓ TTS модель загружена")
44
-
45
- if translator is None:
46
- print("Загрузка модели перевода (это может занять минуту)...")
47
- # Используем более легкую модель
48
- translator = pipeline(
49
- "translation",
50
- model="Helsinki-NLP/opus-mt-ru-en", # Только ru->en
51
- device=device
52
- )
53
- print("✓ Модель перевода загружена")
54
-
55
- return True
56
- except Exception as e:
57
- print(f"Ошибка загрузки базовых моделей: {str(e)}")
58
- traceback.print_exc()
59
- return False
60
 
61
- def load_qa_models():
62
- """Загрузка моделей для интерактивного урока"""
63
- global qg_tokenizer, qg_model, qa_pipeline
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
- try:
66
- if qg_tokenizer is None:
67
- print("Загрузка QG модели...")
68
- qg_tokenizer = T5Tokenizer.from_pretrained("iarfmoose/t5-base-question-generator", legacy=False)
69
- qg_model = T5ForConditionalGeneration.from_pretrained("iarfmoose/t5-base-question-generator").to(device)
70
- print("✓ QG модель загружена")
71
-
72
- if qa_pipeline is None:
73
- print("Загрузка QA модели...")
74
- qa_pipeline = pipeline("question-answering", model="deepset/roberta-base-squad2", device=device)
75
- print("✓ QA модель загружена")
76
-
77
- return True
78
- except Exception as e:
79
- print(f"Ошибка загрузки QA моделей: {str(e)}")
80
- traceback.print_exc()
81
- return False
82
-
83
- def translate_ru_to_kk(text):
84
- """Упрощенный перевод через английский"""
85
- try:
86
- # ru -> en
87
- en_result = translator(text, max_length=512)[0]['translation_text']
88
-
89
- # Простая транслитерация для казахского (заглушка)
90
- # В реальности нужна модель en->kk, но для демо используем транслит
91
- kk_text = en_result # Временно оставляем на английском
92
-
93
- return kk_text
94
- except Exception as e:
95
- print(f"Ошибка перевода: {e}")
96
- return text # Возвращаем исходный текст
97
 
98
  def inference(image: Image.Image, text: str):
99
  error_msg = ""
@@ -102,37 +84,45 @@ def inference(image: Image.Image, text: str):
102
  img_path = None
103
 
104
  try:
105
- # Загрузка моделей при первом запуске
106
- if not load_models():
107
- raise RuntimeError("Не удалось загрузить модели")
108
 
109
  # Валидация
110
  if image is None:
111
  raise ValueError("Загрузите изображение лектора!")
112
 
113
  if not text or not text.strip():
114
- raise ValueError("Введите текст лекции на русском!")
115
 
116
  if len(text) > 500:
117
- raise ValueError("Текст слишком длинный! Используйте до 500 символов.")
118
 
119
- print(f"Входной текст (ru): '{text[:50]}...'")
120
 
121
- # Перевод на казахский
122
- translated_text = translate_ru_to_kk(text)
123
  print(f"Переведенный текст: '{translated_text[:50]}...'")
124
 
125
- # Генерация аудио
126
- torch.manual_seed(42)
127
- inputs = tts_tokenizer(translated_text, return_tensors="pt").to(device)
128
-
129
  with torch.no_grad():
 
 
 
 
 
 
130
  output = tts_model(**inputs)
131
  waveform = output.waveform.squeeze().cpu().numpy()
 
 
 
132
 
133
  if waveform.size == 0:
134
  raise ValueError("TTS сгенерировал пустое аудио!")
135
 
 
136
  audio = (waveform * 32767).astype("int16")
137
  sampling_rate = tts_model.config.sampling_rate
138
 
@@ -140,26 +130,36 @@ def inference(image: Image.Image, text: str):
140
  wavfile.write(audio_file.name, sampling_rate, audio)
141
  audio_path = audio_file.name
142
 
143
- print(f"Аудио сохранено: {audio_path}")
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
- # Сохранение изображения
146
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_file:
147
- if image.mode != 'RGB':
148
- image = image.convert('RGB')
149
- image.save(img_file.name, format='PNG')
150
  img_path = img_file.name
151
 
152
- print(f"Изображение сохранено: {img_path}")
153
 
154
- # Вызов talking-head API
155
  print(f"Подключение к {TALKING_HEAD_SPACE}...")
156
- client = Client(TALKING_HEAD_SPACE)
157
 
158
  result = client.predict(
159
  image_path=handle_file(img_path),
160
  audio_path=handle_file(audio_path),
161
- guidance_scale=3.0,
162
- steps=10,
163
  api_name="/process_image_audio"
164
  )
165
 
@@ -180,15 +180,16 @@ def inference(image: Image.Image, text: str):
180
  if not video_path or not os.path.exists(video_path):
181
  raise ValueError("Видео не сгенерировано!")
182
 
183
- print(f"Видео сгенерировано: {video_path}")
184
  error_msg = "✅ Бейне сәтті жасалды!"
185
 
186
  except Exception as e:
187
- error_msg = f"❌ Ошибка: {str(e)}"
188
  print(f"ОШИБКА: {error_msg}")
189
  traceback.print_exc()
190
 
191
  finally:
 
192
  for path in [audio_path, img_path]:
193
  if path and os.path.exists(path):
194
  try:
@@ -199,204 +200,185 @@ def inference(image: Image.Image, text: str):
199
  return video_path, error_msg
200
 
201
  def generate_interactive_lesson(text, video_path):
 
202
  try:
203
- # Загрузка QA моделей
204
- if not load_qa_models():
205
- return "<p style='color: red;'>❌ Не удалось загрузить модели для генерации вопросов</p>"
206
-
207
  if not video_path or not os.path.exists(video_path):
208
- return "<p style='color: red;'>❌ Сначала сгенерируйте видео!</p>"
209
 
210
- # Перевод на английский
211
- english_text = translator(text, max_length=512)[0]['translation_text']
212
- print(f"English text: {english_text[:100]}...")
213
-
214
- # Генерация вопросов
215
- sentences = sent_tokenize(english_text)[:3]
216
  questions = []
217
 
218
  for i, sent in enumerate(sentences):
 
219
  if len(sent) < 10:
220
  continue
221
 
222
- try:
223
- # Генерация вопроса
224
- qg_input = f"generate question: {sent}"
225
- input_ids = qg_tokenizer.encode(qg_input, return_tensors="pt", max_length=512, truncation=True).to(device)
226
-
227
- with torch.no_grad():
228
- outputs = qg_model.generate(input_ids, max_length=64, num_beams=4)
229
-
230
- question_en = qg_tokenizer.decode(outputs[0], skip_special_tokens=True)
231
-
232
- if not question_en or len(question_en) < 5:
233
- question_en = f"What is mentioned about {sent.split()[0]}?"
234
-
235
- print(f"Q{i+1}: {question_en}")
236
-
237
- # Извлечение ответа
238
- try:
239
- qa_result = qa_pipeline(question=question_en, context=english_text)
240
- correct_answer_en = qa_result['answer']
241
- except:
242
- correct_answer_en = sent.split()[0:5]
243
- correct_answer_en = " ".join(correct_answer_en)
244
-
245
- # Неправильный ответ
246
- wrong_options = ["Not mentioned", "Unknown", "Incorrect answer"]
247
- wrong_answer_en = random.choice(wrong_options)
248
-
249
- questions.append({
250
- "question": question_en,
251
- "correct": correct_answer_en,
252
- "wrong": wrong_answer_en
253
- })
254
-
255
- except Exception as e:
256
- print(f"Ошибка генерации вопроса {i+1}: {e}")
257
  continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
  if not questions:
260
- return "<p style='color: orange;'>⚠️ Не удалось сгенерировать вопросы. Текст слишком короткий?</p>"
261
-
262
- # Base64 видео
 
 
 
 
 
 
263
  with open(video_path, 'rb') as f:
264
- video_base64 = base64.b64encode(f.read()).decode('utf-8')
 
 
 
 
265
 
266
- # HTML
267
  html = f"""<!DOCTYPE html>
268
- <html>
269
- <head>
270
- <meta charset="UTF-8">
271
- <title>Interactive Lesson</title>
272
- <style>
273
- body {{ font-family: Arial, sans-serif; max-width: 900px; margin: 20px auto; padding: 20px; background: #f0f0f0; }}
274
- h1 {{ color: #2c3e50; text-align: center; }}
275
- video {{ width: 100%; max-width: 700px; display: block; margin: 20px auto; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }}
276
- .lecture-text {{ background: white; padding: 20px; border-radius: 10px; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
277
- .question {{ background: white; padding: 20px; margin: 20px 0; border-radius: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); border-left: 4px solid #3498db; }}
278
- button {{ background: #3498db; color: white; padding: 12px 24px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; margin-top: 10px; }}
279
- button:hover {{ background: #2980b9; }}
280
- .feedback {{ margin-top: 15px; padding: 10px; border-radius: 5px; font-weight: bold; }}
281
- label {{ margin-left: 8px; cursor: pointer; }}
282
- input[type="radio"] {{ cursor: pointer; }}
283
- </style>
284
- </head>
285
- <body>
286
- <h1>📚 Interactive Lesson</h1>
287
- <video controls>
288
- <source src="data:video/mp4;base64,{video_base64}" type="video/mp4">
289
- </video>
290
- <div class="lecture-text">
291
- <h3>📝 Lecture Text:</h3>
292
- <p>{text.replace(chr(10), '<br>')}</p>
293
- </div>
294
- <h2 style="color: #2c3e50; text-align: center;">Test Your Knowledge:</h2>
295
- """
296
 
297
  for i, q in enumerate(questions):
298
- correct_escaped = q['correct'].replace("'", "\\'").replace('"', '\\"')
299
  html += f"""
300
- <div class="question">
301
- <p><strong>Question {i+1}:</strong> {q['question']}</p>
302
- <div style="margin: 15px 0;">
303
- <input type="radio" name="q{i}" value="correct" id="correct{i}">
304
- <label for="correct{i}">{q['correct']}</label><br><br>
305
- <input type="radio" name="q{i}" value="wrong" id="wrong{i}">
306
- <label for="wrong{i}">{q['wrong']}</label>
307
- </div>
308
- <button onclick="checkAnswer({i}, '{correct_escaped}')">Check Answer</button>
309
- <div class="feedback" id="feedback{i}"></div>
310
- </div>
311
- """
312
 
313
  html += """
314
- <script>
315
- function checkAnswer(i, correct) {
316
- var selected = document.querySelector('input[name="q' + i + '"]:checked');
317
- var feedback = document.getElementById('feedback' + i);
318
- if (selected) {
319
- if (selected.value === 'correct') {
320
- feedback.innerHTML = ' Correct!';
321
- feedback.style.background = '#d4edda';
322
- feedback.style.color = '#155724';
323
- } else {
324
- feedback.innerHTML = '❌ Wrong. Correct answer: ' + correct;
325
- feedback.style.background = '#f8d7da';
326
- feedback.style.color = '#721c24';
327
- }
328
- } else {
329
- feedback.innerHTML = '⚠️ Please select an answer!';
330
- feedback.style.background = '#fff3cd';
331
- feedback.style.color = '#856404';
332
- }
333
- }
334
- </script>
335
- </body>
336
- </html>
337
- """
338
-
339
- # Экранирование
340
- escaped_html = html.replace('\\', '\\\\').replace('`', '\\`').replace('${', '\\${')
341
 
342
  return f"""
343
- <div style="text-align: center; padding: 30px; background: white; border-radius: 10px;">
344
- <h3 style="color: #2c3e50; margin-bottom: 20px;">✅ Interactive lesson is ready!</h3>
345
- <button onclick="openLesson()" style="background: #27ae60; color: white; padding: 15px 40px; font-size: 18px; border: none; border-radius: 8px; cursor: pointer; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
346
- 📖 Open Interactive Lesson
347
- </button>
348
- </div>
349
- <script>
350
- function openLesson() {{
351
- var w = window.open('', '_blank');
352
- w.document.write(`{escaped_html}`);
353
- w.document.close();
354
- }}
355
- </script>
356
- """
357
 
358
  except Exception as e:
359
  traceback.print_exc()
360
- return f"<p style='color: red;'>❌ Error: {str(e)}</p>"
361
 
362
  # Интерфейс
363
- with gr.Blocks(theme=gr.themes.Soft(), title="Video Lecturer") as iface:
 
 
 
 
364
  gr.Markdown("""
365
- # 🎓 AI Video Lecturer
366
 
367
- Upload your photo and enter lecture text in Russian. AI will translate to Kazakh/English and create a video lecture!
 
 
 
 
368
 
369
- **Requirements:**
370
- - 📸 Photo: Clear frontal face photo
371
- - 📝 Text: In Russian, max 500 characters
372
  """)
373
 
374
  with gr.Row():
375
- with gr.Column():
376
- image_input = gr.Image(type="pil", label="📸 Lecturer Photo")
377
  text_input = gr.Textbox(
378
  lines=6,
379
- placeholder="Example: Hello! Today we will talk about mathematics...",
380
- label="📝 Lecture Text (Russian, max 500 chars)"
381
  )
382
- generate_video_btn = gr.Button("🎬 Generate Video", variant="primary", size="lg")
383
 
384
- with gr.Column():
385
- video_output = gr.Video(label="🎬 Generated Video")
386
- status = gr.Textbox(label="ℹ️ Status", interactive=False)
387
 
388
- interactive_btn = gr.Button("📚 Create Interactive Lesson", visible=False, variant="secondary", size="lg")
389
  lesson_output = gr.HTML(visible=False)
390
 
391
- def show_interactive_btn(video, status_msg):
392
  return gr.update(visible=bool(video and "✅" in status_msg))
393
 
394
- generate_video_btn.click(
395
  inference,
396
  inputs=[image_input, text_input],
397
  outputs=[video_output, status]
398
  ).then(
399
- show_interactive_btn,
400
  inputs=[video_output, status],
401
  outputs=interactive_btn
402
  )
@@ -411,4 +393,8 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Video Lecturer") as iface:
411
  )
412
 
413
  if __name__ == "__main__":
414
- iface.launch()
 
 
 
 
 
4
  import tempfile
5
  from gradio_client import Client, handle_file
6
  import torch
7
+ from transformers import VitsModel, AutoTokenizer
8
  import scipy.io.wavfile as wavfile
9
  import traceback
10
  import base64
 
11
  import random
12
 
13
+ # Принудительно CPU и минимальное использование памяти
14
+ os.environ['CUDA_VISIBLE_DEVICES'] = ''
15
+ torch.set_num_threads(2) # Ограничение потоков CPU
 
 
 
16
 
17
+ device = "cpu"
18
+ print(f"Using device: {device} (optimized mode)")
 
19
 
20
+ # Глобальные переменные
21
  tts_model = None
22
  tts_tokenizer = None
 
 
 
 
23
 
24
  TALKING_HEAD_SPACE = "Skywork/skyreels-a1-talking-head"
25
 
26
+ def load_tts_model():
27
+ """Загрузка только TTS модели"""
28
+ global tts_model, tts_tokenizer
29
 
30
+ if tts_model is None:
31
+ print("Загрузка TTS модели (казахский)...")
32
+ tts_model = VitsModel.from_pretrained(
33
+ "facebook/mms-tts-kaz",
34
+ torch_dtype=torch.float32,
35
+ low_cpu_mem_usage=True
36
+ )
37
+ tts_model.eval() # Режим инференса
38
+ tts_tokenizer = AutoTokenizer.from_pretrained("facebook/mms-tts-kaz")
39
+ print("✓ TTS модель загружена")
40
+ return True
 
 
 
 
 
 
 
 
 
 
 
41
 
42
+ def simple_translate_to_kazakh(russian_text):
43
+ """
44
+ Упрощенная транслитерация/перевод без тяжелых моделей
45
+ Для реального использования нужна легкая модель или API
46
+ """
47
+ # Простая замена для базовых слов (демо)
48
+ translations = {
49
+ 'привет': 'сәлем',
50
+ 'здравствуйте': 'сәлеметсіздер ме',
51
+ 'спасибо': 'рахмет',
52
+ 'пожалуйста': 'өтінемін',
53
+ 'да': 'иә',
54
+ 'нет': 'жоқ',
55
+ 'сегодня': 'бүгін',
56
+ 'завтра': 'ертең',
57
+ 'математика': 'математика',
58
+ 'физика': 'физика',
59
+ 'урок': 'сабақ',
60
+ 'лекция': 'дәріс',
61
+ 'студент': 'студент',
62
+ 'учитель': 'мұғалім',
63
+ 'школа': 'мектеп',
64
+ 'университет': 'университет',
65
+ 'знание': 'білім',
66
+ 'книга': 'кітап',
67
+ 'вопрос': 'сұрақ',
68
+ 'ответ': 'жауап'
69
+ }
70
 
71
+ text_lower = russian_text.lower()
72
+ result = russian_text
73
+
74
+ for ru, kk in translations.items():
75
+ result = result.replace(ru, kk)
76
+ result = result.replace(ru.capitalize(), kk.capitalize())
77
+
78
+ return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  def inference(image: Image.Image, text: str):
81
  error_msg = ""
 
84
  img_path = None
85
 
86
  try:
87
+ # Загрузка TTS
88
+ if not load_tts_model():
89
+ raise RuntimeError("Не удалось загрузить TTS модель")
90
 
91
  # Валидация
92
  if image is None:
93
  raise ValueError("Загрузите изображение лектора!")
94
 
95
  if not text or not text.strip():
96
+ raise ValueError("Введите текст лекции!")
97
 
98
  if len(text) > 500:
99
+ raise ValueError("Текст слишком длинный! Максимум 500 символов.")
100
 
101
+ print(f"Входной текст: '{text[:50]}...'")
102
 
103
+ # Простой перевод на казахский
104
+ translated_text = simple_translate_to_kazakh(text)
105
  print(f"Переведенный текст: '{translated_text[:50]}...'")
106
 
107
+ # Генерация аудио с оптимизацией памяти
108
+ print("Генерация аудио...")
 
 
109
  with torch.no_grad():
110
+ inputs = tts_tokenizer(translated_text, return_tensors="pt", truncation=True, max_length=512)
111
+
112
+ # Освобождение памяти перед генерацией
113
+ if torch.cuda.is_available():
114
+ torch.cuda.empty_cache()
115
+
116
  output = tts_model(**inputs)
117
  waveform = output.waveform.squeeze().cpu().numpy()
118
+
119
+ # Очистка
120
+ del inputs, output
121
 
122
  if waveform.size == 0:
123
  raise ValueError("TTS сгенерировал пустое аудио!")
124
 
125
+ # Сохранение аудио
126
  audio = (waveform * 32767).astype("int16")
127
  sampling_rate = tts_model.config.sampling_rate
128
 
 
130
  wavfile.write(audio_file.name, sampling_rate, audio)
131
  audio_path = audio_file.name
132
 
133
+ print(f" Аудио: {audio_path} ({len(waveform)/sampling_rate:.1f} сек)")
134
+
135
+ # Оптимизация изображения
136
+ print("Обработка изображения...")
137
+ if image.mode != 'RGB':
138
+ image = image.convert('RGB')
139
+
140
+ # Уменьшаем размер если слишком большое (экономия памяти)
141
+ max_size = 1024
142
+ if max(image.size) > max_size:
143
+ ratio = max_size / max(image.size)
144
+ new_size = tuple(int(dim * ratio) for dim in image.size)
145
+ image = image.resize(new_size, Image.Resampling.LANCZOS)
146
+ print(f"Изображение уменьшено до {new_size}")
147
 
 
148
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_file:
149
+ image.save(img_file.name, format='PNG', optimize=True)
 
 
150
  img_path = img_file.name
151
 
152
+ print(f" Изображение: {img_path}")
153
 
154
+ # Вызов Talking Head API
155
  print(f"Подключение к {TALKING_HEAD_SPACE}...")
156
+ client = Client(TALKING_HEAD_SPACE, verbose=False)
157
 
158
  result = client.predict(
159
  image_path=handle_file(img_path),
160
  audio_path=handle_file(audio_path),
161
+ guidance_scale=2.5, # Снижено для скорости
162
+ steps=8, # Меньше шагов = быстрее
163
  api_name="/process_image_audio"
164
  )
165
 
 
180
  if not video_path or not os.path.exists(video_path):
181
  raise ValueError("Видео не сгенерировано!")
182
 
183
+ print(f" Видео: {video_path}")
184
  error_msg = "✅ Бейне сәтті жасалды!"
185
 
186
  except Exception as e:
187
+ error_msg = f"❌ Қате: {str(e)}"
188
  print(f"ОШИБКА: {error_msg}")
189
  traceback.print_exc()
190
 
191
  finally:
192
+ # Очистка временных файлов
193
  for path in [audio_path, img_path]:
194
  if path and os.path.exists(path):
195
  try:
 
200
  return video_path, error_msg
201
 
202
  def generate_interactive_lesson(text, video_path):
203
+ """Упрощенная версия без тяжелых моделей QA"""
204
  try:
 
 
 
 
205
  if not video_path or not os.path.exists(video_path):
206
+ return "<p style='color: red;'>❌ Алдымен бейнені жасаңыз!</p>"
207
 
208
+ # Простая генерация вопросов без ML моделей
209
+ sentences = text.split('.')[:3] # Первые 3 предложения
 
 
 
 
210
  questions = []
211
 
212
  for i, sent in enumerate(sentences):
213
+ sent = sent.strip()
214
  if len(sent) < 10:
215
  continue
216
 
217
+ # Простые шаблоны вопросов
218
+ words = sent.split()
219
+ if len(words) < 3:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  continue
221
+
222
+ # Генерируем вопрос на основе шаблона
223
+ question_templates = [
224
+ f"Не сказано о {words[0].lower()}?",
225
+ f"Что упоминается в тексте о {words[1].lower() if len(words) > 1 else 'теме'}?",
226
+ f"Какая информация дана о {words[2].lower() if len(words) > 2 else 'содержании'}?"
227
+ ]
228
+
229
+ question = random.choice(question_templates)
230
+
231
+ # Правильный ответ - часть предложения
232
+ correct = ' '.join(words[:min(5, len(words))])
233
+
234
+ # Неправильные ответы
235
+ wrong_options = [
236
+ "Бұл туралы айтылмаған",
237
+ "Мәтінде жоқ",
238
+ "Дұрыс емес жауап"
239
+ ]
240
+ wrong = random.choice(wrong_options)
241
+
242
+ questions.append({
243
+ "question": question,
244
+ "correct": correct,
245
+ "wrong": wrong
246
+ })
247
 
248
  if not questions:
249
+ # Создаем хотя бы один вопрос
250
+ questions.append({
251
+ "question": "Дәрістің негізгі тақырыбы не?",
252
+ "correct": text.split('.')[0][:50] if text else "Білім",
253
+ "wrong": "Спорт туралы"
254
+ })
255
+
256
+ # Base64 видео (оптимизировано)
257
+ print("Кодирование видео в base64...")
258
  with open(video_path, 'rb') as f:
259
+ video_data = f.read()
260
+ # Проверка размера
261
+ if len(video_data) > 50 * 1024 * 1024: # 50MB
262
+ return "<p style='color: orange;'>⚠️ Видео слишком большое для встраивания. Скачайте его отдельно.</p>"
263
+ video_base64 = base64.b64encode(video_data).decode('utf-8')
264
 
265
+ # Минимальный HTML
266
  html = f"""<!DOCTYPE html>
267
+ <html>
268
+ <head>
269
+ <meta charset="UTF-8">
270
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
271
+ <title>Интерактивті сабақ</title>
272
+ <style>
273
+ * {{ margin: 0; padding: 0; box-sizing: border-box; }}
274
+ body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 15px; background: #f5f5f5; }}
275
+ h1 {{ color: #333; text-align: center; margin: 20px 0; font-size: 24px; }}
276
+ video {{ width: 100%; max-width: 600px; display: block; margin: 20px auto; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }}
277
+ .text {{ background: white; padding: 15px; margin: 20px 0; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
278
+ .q {{ background: white; padding: 15px; margin: 15px 0; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
279
+ button {{ background: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px; }}
280
+ button:hover {{ background: #45a049; }}
281
+ .fb {{ margin-top: 10px; padding: 8px; border-radius: 5px; font-weight: bold; }}
282
+ label {{ cursor: pointer; }}
283
+ </style>
284
+ </head>
285
+ <body>
286
+ <h1>📚 Интерактивті сабақ</h1>
287
+ <video controls><source src="data:video/mp4;base64,{video_base64}" type="video/mp4"></video>
288
+ <div class="text"><strong>Дәріс мәтіні:</strong> {text[:500]}</div>
289
+ <h2 style="text-align:center; margin: 20px 0;">Тесттер:</h2>
290
+ """
 
 
 
 
291
 
292
  for i, q in enumerate(questions):
293
+ ca = q['correct'].replace("'", "\\'").replace('"', '&quot;')
294
  html += f"""
295
+ <div class="q">
296
+ <p><strong>Сұрақ {i+1}:</strong> {q['question']}</p>
297
+ <div style="margin: 10px 0;">
298
+ <input type="radio" name="q{i}" value="c" id="c{i}">
299
+ <label for="c{i}">{q['correct']}</label><br>
300
+ <input type="radio" name="q{i}" value="w" id="w{i}">
301
+ <label for="w{i}">{q['wrong']}</label>
302
+ </div>
303
+ <button onclick="check({i},'{ca}')">Тексеру</button>
304
+ <div class="fb" id="fb{i}"></div>
305
+ </div>
306
+ """
307
 
308
  html += """
309
+ <script>
310
+ function check(i, c) {
311
+ var s = document.querySelector('input[name="q'+i+'"]:checked');
312
+ var f = document.getElementById('fb'+i);
313
+ if(!s) { f.innerHTML='⚠️ Жауап таңдаңыз!'; f.style.background='#fff3cd'; f.style.color='#856404'; return; }
314
+ if(s.value==='c') { f.innerHTML='✅ Дұрыс!'; f.style.background='#d4edda'; f.style.color='#155724'; }
315
+ else { f.innerHTML='❌ Қате. Дұрыс: '+c; f.style.background='#f8d7da'; f.style.color='#721c24'; }
316
+ }
317
+ </script>
318
+ </body>
319
+ </html>"""
320
+
321
+ escaped = html.replace('\\', '\\\\').replace('`', '\\`').replace('${', '\\${')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
 
323
  return f"""
324
+ <div style="text-align:center; padding: 20px; background: white; border-radius: 8px;">
325
+ <h3 style="color: #2c3e50;">✅ Интерактивті сабақ дайын!</h3>
326
+ <button onclick="var w=window.open('','_blank');w.document.write(`{escaped}`);w.document.close();"
327
+ style="background: #27ae60; color: white; padding: 15px 30px; font-size: 16px; border: none;
328
+ border-radius: 8px; cursor: pointer; margin-top: 15px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
329
+ 📖 Интерактивті сабақты ашу
330
+ </button>
331
+ </div>
332
+ """
 
 
 
 
 
333
 
334
  except Exception as e:
335
  traceback.print_exc()
336
+ return f"<p style='color: red;'>❌ Қате: {str(e)}</p>"
337
 
338
  # Интерфейс
339
+ with gr.Blocks(theme=gr.themes.Soft(), title="Бейне Оқытушы", css="""
340
+ .gradio-container {max-width: 1200px !important;}
341
+ footer {display: none !important;}
342
+ """) as iface:
343
+
344
  gr.Markdown("""
345
+ # 🎓 Бейне Оқытушы (CPU Оптимизацияланған)
346
 
347
+ **Қалай пайдалану:**
348
+ 1. 📸 Суретіңізді жүктеңіз (бет анық көрінетін)
349
+ 2. 📝 Дәріс мәтінін орыс тілінде енгізіңіз (500 таңбаға дейін)
350
+ 3. 🎬 "Бейнені жасау" батырмасын басыңыз
351
+ 4. 📚 Дайын болғаннан кейін "Интерактивті сабақ" жасай аласыз
352
 
353
+ ⚡ **Ескерту:** CPU режимінде жұмыс істейді, генерация 1-3 минут алуы мүмкін.
 
 
354
  """)
355
 
356
  with gr.Row():
357
+ with gr.Column(scale=1):
358
+ image_input = gr.Image(type="pil", label="📸 Дәріскер суреті")
359
  text_input = gr.Textbox(
360
  lines=6,
361
+ placeholder="Мысалы: Сәлеметсіздер ме! Бүгін біз математика туралы сөйлесеміз...",
362
+ label="📝 Дәріс мәтіні (орыс тілінде)"
363
  )
364
+ generate_btn = gr.Button("🎬 Бейнені жасау", variant="primary", size="lg")
365
 
366
+ with gr.Column(scale=1):
367
+ video_output = gr.Video(label="🎬 Дайын бейне")
368
+ status = gr.Textbox(label="ℹ️ Мәртебе", interactive=False)
369
 
370
+ interactive_btn = gr.Button("📚 Интерактивті сабақ жасау", visible=False, variant="secondary")
371
  lesson_output = gr.HTML(visible=False)
372
 
373
+ def show_lesson_btn(video, status_msg):
374
  return gr.update(visible=bool(video and "✅" in status_msg))
375
 
376
+ generate_btn.click(
377
  inference,
378
  inputs=[image_input, text_input],
379
  outputs=[video_output, status]
380
  ).then(
381
+ show_lesson_btn,
382
  inputs=[video_output, status],
383
  outputs=interactive_btn
384
  )
 
393
  )
394
 
395
  if __name__ == "__main__":
396
+ iface.launch(
397
+ server_name="0.0.0.0",
398
+ server_port=7860,
399
+ share=False
400
+ )