AlserFurma commited on
Commit
4adabcc
·
verified ·
1 Parent(s): c06b1cb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +74 -372
app.py CHANGED
@@ -2,391 +2,93 @@ 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
- try:
387
- iface.launch(
388
- server_name="0.0.0.0",
389
- server_port=7860
390
- )
391
- except Exception as e:
392
- print(f"Launch error: {str(e)}")
 
2
  import os
3
  from PIL import Image
4
  import tempfile
 
5
  import torch
6
  from transformers import VitsModel, AutoTokenizer
7
  import scipy.io.wavfile as wavfile
8
+ from gradio_client import Client, handle_file
9
  import traceback
10
+
11
+ # Только CPU
12
+ os.environ["CUDA_VISIBLE_DEVICES"] = ""
13
+ torch.set_num_threads(4)
14
+
15
+ TALKING_HEAD = "Skywork/skyreels-a1-talking-head"
16
+ model = None
17
+ tokenizer = None
18
+
19
+ def load_tts():
20
+ global model, tokenizer
21
+ if model is None:
22
+ print("Загружаем TTS (каз)…")
23
+ model = VitsModel.from_pretrained("facebook/mms-tts-kaz")
24
+ tokenizer = AutoTokenizer.from_pretrained("facebook/mms-tts-kaz")
25
+ print("TTS готова")
 
 
 
 
 
 
 
 
 
26
  return True
27
+
28
+ def ru_to_kz_simple(text: str) -> str:
29
+ rep = {
30
+ "привет": "сәлем", "здравствуйте": "сәлеметсіз бе", "спасибо": "рахмет",
31
+ "да": "иә", "нет": "жоқ", "сегодня": "бүгін", "завтра": "ертең",
32
+ "урок": "сабақ", "лекция": "дәріс", "учитель": "мұғалім", "школа": "мектеп"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
+ for ru, kz in rep.items():
35
+ text = text.replace(ru, kz).replace(ru.capitalize(), kz.capitalize())
36
+ return text
37
+
38
+ def create_video(image: Image.Image, text: str):
39
+ if not image or not text.strip():
40
+ return None, "Загрузите фото и введите текст!"
41
+
42
+ load_tts()
43
+ text_kz = ru_to_kz_simple(text.strip())
44
+
 
 
 
 
45
  try:
46
+ # TTS
47
+ inputs = tokenizer(text_kz, return_tensors="pt")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  with torch.no_grad():
49
+ waveform = model(**inputs).waveform.squeeze().cpu().numpy()
50
+
51
+ rate = model.config.sampling_rate
52
+ audio_path = "/tmp/audio.wav"
53
+ wavfile.write(audio_path, rate, (waveform * 32767).astype("int16"))
54
+
55
+ # Изображение
56
+ if image.mode != "RGB":
57
+ image = image.convert("RGB")
58
+ img_path = "/tmp/img.png"
59
+ image.save(img_path)
60
+
61
+ # Talking head
62
+ client = Client(TALKING_HEAD)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  result = client.predict(
64
  image_path=handle_file(img_path),
65
  audio_path=handle_file(audio_path),
66
+ guidance_scale=2.0,
67
+ steps=8,
68
  api_name="/process_image_audio"
69
  )
70
+
71
+ video_path = result[0] if isinstance(result, (list, tuple)) else result
72
+ return video_path, "Бейне дайын!"
73
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  except Exception as e:
75
  traceback.print_exc()
76
+ return None, f"Қате: {e}"
77
+
78
+ # === Интерфейс ===
79
+ with gr.Blocks(title="Бейне-лектор қазақша") as app:
80
+ gr.Markdown("# Бейне-лектор қазақша\nФото + текст → говорящий видео-лектор")
81
+
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  with gr.Row():
83
+ with gr.Column():
84
+ img_in = gr.Image(label="Фото лектора", type="pil")
85
+ txt_in = gr.Textbox(label="Текст лекции (русский)", lines=6, placeholder="Привет! Сегодня мы изучаем математику…")
86
+ btn = gr.Button("Сделать видео", variant="primary")
87
+
88
+ with gr.Column():
89
+ video_out = gr.Video(label="Готовое видео")
90
+ status = gr.Textbox(label="Статус", interactive=False)
91
+
92
+ btn.click(create_video, [img_in, txt_in], [video_out, status])
93
+
94
+ app.launch(server_name="0.0.0.0", server_port=7860)