AlserFurma commited on
Commit
b9b0a3a
·
verified ·
1 Parent(s): 8e44e35

Update app.py

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