AlserFurma commited on
Commit
db9d335
·
verified ·
1 Parent(s): aaf0f51

Create app.py

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