import gradio as gr import tempfile import os import json import shutil import base64 try: from moviepy.editor import VideoFileClip MOVIEPY_AVAILABLE = True except ImportError: print("MoviePy не установлен, используем альтернативный метод") MOVIEPY_AVAILABLE = False # Глобальная переменная для хранения созданных уроков current_lesson_dir = None def add_segment(lecture_video, correct_video, wrong_video, option1_text, option2_text, correct_option, segments_state): """Добавляет сегмент: видео лекции + видео реакций + текст вариантов""" if lecture_video is None or correct_video is None or wrong_video is None: return segments_state, "⚠️ Загрузите все три видео (лекция, правильная реакция, неправильная реакция)", None, None, None if not option1_text or not option2_text: return segments_state, "⚠️ Введите оба варианта ответа", None, None, None if correct_option is None: return segments_state, "⚠️ Выберите правильный вариант", None, None, None # Получаем длительность лекции try: if MOVIEPY_AVAILABLE: clip = VideoFileClip(lecture_video) duration = clip.duration clip.close() else: # Альтернативный метод через subprocess import subprocess result = subprocess.run( ['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', lecture_video], stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) duration = float(result.stdout) except Exception as e: print(f"Ошибка получения длительности: {e}") duration = 10 # Значение по умолчанию # Сохраняем оригинальные пути new_segment = { 'lecture_path': lecture_video, 'correct_path': correct_video, 'wrong_path': wrong_video, 'options': [option1_text, option2_text], 'correct_answer': 0 if correct_option == "Вариант 1" else 1, # Переименовали ключ 'duration': duration } segments_state.append(new_segment) info = f"✅ **Всего сегментов: {len(segments_state)}**\n\n" for i, seg in enumerate(segments_state, 1): info += f"**Сегмент {i}:** ({seg['duration']:.1f} сек)\n" info += f"- 🎯 Вариант 1: {seg['options'][0]}\n" info += f"- 🎯 Вариант 2: {seg['options'][1]}\n" info += f"- ✅ Правильный: Вариант {seg['correct_answer'] + 1}\n\n" # Очищаем поля ввода return segments_state, info, None, None, None def create_lesson(segments_state): """Подготавливает урок - копирует файлы и создает метаданные""" global current_lesson_dir if not segments_state: return "⚠️ Нет добавленных сегментов", None # Создаем временную директорию для урока lesson_dir = tempfile.mkdtemp(prefix="lesson_") current_lesson_dir = lesson_dir # Сохраняем глобально timestamps = [] for i, seg in enumerate(segments_state): # Копируем видео в директорию урока lecture_new = os.path.join(lesson_dir, f"lecture_{i}.mp4") correct_new = os.path.join(lesson_dir, f"correct_{i}.mp4") wrong_new = os.path.join(lesson_dir, f"wrong_{i}.mp4") # Копируем файлы shutil.copy2(seg['lecture_path'], lecture_new) shutil.copy2(seg['correct_path'], correct_new) shutil.copy2(seg['wrong_path'], wrong_new) timestamps.append({ 'index': i, 'lecture_file': f"lecture_{i}.mp4", # Переименовали ключи для ясности 'correct_file': f"correct_{i}.mp4", 'wrong_file': f"wrong_{i}.mp4", 'duration': seg['duration'], 'options': seg['options'], 'correct_answer': seg['correct_answer'] # Здесь храним индекс правильного ответа }) # Сохраняем метаданные metadata = { 'timestamps': timestamps, 'total_segments': len(segments_state), 'lesson_dir': lesson_dir # Сохраняем путь к директории } meta_path = os.path.join(lesson_dir, 'metadata.json') with open(meta_path, 'w', encoding='utf-8') as f: json.dump(metadata, f, ensure_ascii=False, indent=2) total_duration = sum(seg['duration'] for seg in segments_state) return (f"✅ Урок создан!\n- Всего сегментов: {len(segments_state)}\n- Примерная длительность: {total_duration:.1f} сек\n- Путь: {lesson_dir}", meta_path) def generate_player_html(meta_path): """Генерирует HTML плеер с интерактивными кнопками""" global current_lesson_dir if not meta_path or not os.path.exists(meta_path): return "

⚠️ Сначала создайте урок!

" with open(meta_path, 'r', encoding='utf-8') as f: metadata = json.load(f) # Используем сохраненную директорию lesson_dir = metadata.get('lesson_dir', current_lesson_dir) if not lesson_dir or not os.path.exists(lesson_dir): return "

⚠️ Файлы урока не найдены. Создайте урок заново.

" # Конвертируем все видео в base64 video_data = {} for segment in metadata['timestamps']: idx = segment['index'] # Используем правильные ключи для имен файлов lecture_path = os.path.join(lesson_dir, segment['lecture_file']) correct_path = os.path.join(lesson_dir, segment['correct_file']) wrong_path = os.path.join(lesson_dir, segment['wrong_file']) try: with open(lecture_path, 'rb') as f: video_data[f'lecture_{idx}'] = base64.b64encode(f.read()).decode() with open(correct_path, 'rb') as f: video_data[f'correct_{idx}'] = base64.b64encode(f.read()).decode() with open(wrong_path, 'rb') as f: video_data[f'wrong_{idx}'] = base64.b64encode(f.read()).decode() except Exception as e: print(f"Ошибка чтения файла: {e}") return f"

⚠️ Ошибка загрузки видео: {str(e)}

" # Создаем структуру данных для JavaScript segments_json = json.dumps([{ 'index': seg['index'], 'duration': seg['duration'], 'options': seg['options'], 'correct': seg['correct_answer'] # Используем правильный ключ } for seg in metadata['timestamps']], ensure_ascii=False) # Создаем словарь с видео для JavaScript videos_json = json.dumps(video_data) html = f"""
Сегмент 1 из {metadata['total_segments']}
Загрузка...
Выберите правильный ответ:
⏱ Времени осталось: 7 сек
0:00
""" return html # Создаем интерфейс with gr.Blocks(title="Интерактивный Урок") as demo: gr.Markdown(""" # 🎓 Интерактивный Урок (Coursera-style) ### Инструкция: 1. **Загрузите 3 видео для каждого сегмента:** - 🎥 Видео лекции (в конце которого задается вопрос) - ✅ Видео реакции на правильный ответ - ❌ Видео реакции на неправильный ответ 2. **Введите варианты ответов** и выберите правильный 3. Нажмите **"Добавить Сегмент"** (повторите для каждого фрагмента) 4. Когда все сегменты добавлены, нажмите **"Создать Урок"** 5. Нажмите **"Запустить Плеер"** для прохождения урока """) # Состояния segments_state = gr.State([]) meta_state = gr.State(None) with gr.Tab("📝 Создание Урока"): gr.Markdown("### Добавление сегмента") with gr.Row(): with gr.Column(scale=1): lecture_input = gr.Video(label="🎥 Видео Лекции") correct_input = gr.Video(label="✅ Реакция: Правильный ответ") wrong_input = gr.Video(label="❌ Реакция: Неправильный ответ") with gr.Column(scale=1): option1_input = gr.Textbox( label="Вариант 1", placeholder="Например: Париж" ) option2_input = gr.Textbox( label="Вариант 2", placeholder="Например: Лондон" ) correct_radio = gr.Radio( ["Вариант 1", "Вариант 2"], label="✅ Правильный вариант", value="Вариант 1" ) add_btn = gr.Button("➕ Добавить Сегмент", variant="primary") segments_info = gr.Markdown("*Нет добавленных сегментов*") add_btn.click( fn=add_segment, inputs=[lecture_input, correct_input, wrong_input, option1_input, option2_input, correct_radio, segments_state], outputs=[segments_state, segments_info, lecture_input, correct_input, wrong_input] ) gr.Markdown("---") create_btn = gr.Button("🎬 Создать Урок", variant="primary") create_status = gr.Markdown("") create_btn.click( fn=create_lesson, inputs=[segments_state], outputs=[create_status, meta_state] ) with gr.Tab("▶️ Прохождение Урока"): gr.Markdown(""" ### Как проходить урок: - 🎬 Видео проигрывается автоматически - ⏱ **За 7 секунд** до конца появятся кнопки с вариантами ответа - 🎯 Выберите правильный ответ до окончания времени - ✅ После ответа покажется видео реакции лектора - ➡️ Затем автоматически начнется следующий сегмент - 🏆 Пройдите все сегменты до конца! """) start_btn = gr.Button("🚀 Запустить Плеер", variant="primary") player_html = gr.HTML() start_btn.click( fn=generate_player_html, inputs=[meta_state], outputs=[player_html] ) if __name__ == '__main__': demo.launch()