Spaces:
Runtime error
Runtime error
| """ | |
| LVM Video Engine - Gradio Web Interface | |
| AI-driven educational video generation - Streamlined with Smart Logging | |
| """ | |
| import gradio as gr | |
| import os | |
| import tempfile | |
| import shutil | |
| from pathlib import Path | |
| from src.core.video_engine import LVMVideoEngine | |
| from src.utils.smart_logger import logger | |
| import time | |
| from datetime import datetime | |
| # Load environment variables from .env file if it exists | |
| try: | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| except ImportError: | |
| env_file = Path(".env") | |
| if env_file.exists(): | |
| with open(env_file) as f: | |
| for line in f: | |
| if line.strip() and not line.startswith('#'): | |
| key, value = line.strip().split('=', 1) | |
| os.environ[key] = value | |
| class GradioVideoEngine: | |
| def __init__(self): | |
| self.engine = None | |
| self.current_project_dir = None | |
| self.log_messages = [] | |
| def log_message(self, message): | |
| """Add timestamped log message for UI display""" | |
| timestamp = datetime.now().strftime("%H:%M:%S") | |
| log_entry = f"[{timestamp}] {message}" | |
| self.log_messages.append(log_entry) | |
| return "\n".join(self.log_messages[-30:]) # Keep last 30 messages | |
| def initialize_engine(self, api_key): | |
| """Initialize the video engine with API key""" | |
| self.log_message("π§ Starting engine initialization...") | |
| if not api_key: | |
| error_msg = "β Please provide a valid Gemini API key" | |
| self.log_message(error_msg) | |
| return error_msg | |
| os.environ["GEMINI_API_KEY"] = api_key | |
| self.current_project_dir = tempfile.mkdtemp(prefix="lvm_project_") | |
| try: | |
| self.engine = LVMVideoEngine(self.current_project_dir) | |
| if self.engine.gemini_client: | |
| success_msg = "β Engine initialized successfully!" | |
| self.log_message(success_msg) | |
| return success_msg | |
| else: | |
| error_msg = "β Failed to initialize Gemini AI client" | |
| self.log_message(error_msg) | |
| return error_msg | |
| except Exception as e: | |
| error_msg = f"β Error initializing engine: {str(e)[:50]}..." | |
| self.log_message(error_msg) | |
| return error_msg | |
| def generate_video_plan(self, prompt, api_key): | |
| """Generate a video plan from user prompt""" | |
| self.log_message("π Starting video plan generation...") | |
| if not prompt: | |
| error_msg = "β Please provide a video topic/prompt" | |
| self.log_message(error_msg) | |
| return error_msg, "", self.log_message("") | |
| # Initialize engine if needed | |
| init_result = self.initialize_engine(api_key) | |
| if "β" in init_result: | |
| return init_result, "", self.log_message("") | |
| try: | |
| self.engine.project_config["prompt"] = prompt | |
| start_time = time.time() | |
| self.engine.step_1_plan_video() | |
| elapsed = time.time() - start_time | |
| self.log_message(f"β±οΈ Plan generation completed in {elapsed:.2f}s") | |
| plan = self.engine.project_config.get("video_plan", {}) | |
| if plan: | |
| scenes_count = len(plan.get('scenes', [])) | |
| self.log_message(f"β Plan created with {scenes_count} scenes") | |
| plan_text = f"**Title:** {plan.get('video_title', 'Untitled')}\n\n" | |
| plan_text += f"**Description:** {plan.get('video_description', 'No description')}\n\n" | |
| plan_text += "**Scenes:**\n" | |
| for i, scene in enumerate(plan.get('scenes', []), 1): | |
| plan_text += f"{i}. **{scene.get('scene_name', 'Unnamed Scene')}**\n" | |
| plan_text += f" - {scene.get('description', 'No description')}\n" | |
| narration_preview = scene.get('narration', 'No narration')[:80] + "..." if len(scene.get('narration', '')) > 80 else scene.get('narration', 'No narration') | |
| plan_text += f" - *Narration: {narration_preview}*\n\n" | |
| return "β Video plan generated successfully!", plan_text, self.log_message("") | |
| else: | |
| error_msg = "β Failed to generate video plan" | |
| self.log_message(error_msg) | |
| return error_msg, "", self.log_message("") | |
| except Exception as e: | |
| error_msg = f"β Error generating plan: {str(e)[:50]}..." | |
| self.log_message(error_msg) | |
| return error_msg, "", self.log_message("") | |
| def generate_full_video(self, prompt, api_key, progress=gr.Progress()): | |
| """Generate complete video from prompt""" | |
| self.log_message("π¬ Starting full video generation...") | |
| if not prompt: | |
| error_msg = "β Please provide a video topic/prompt" | |
| self.log_message(error_msg) | |
| return error_msg, None, self.log_message("") | |
| # Initialize engine | |
| init_result = self.initialize_engine(api_key) | |
| if "β" in init_result: | |
| return init_result, None, self.log_message("") | |
| try: | |
| self.engine.project_config["prompt"] = prompt | |
| total_start_time = time.time() | |
| # Step 1: Plan video | |
| self.log_message("π Step 1/5: Planning video...") | |
| progress(0.2, desc="Planning video...") | |
| self.engine.step_1_plan_video() | |
| if not self.engine.project_config.get("video_plan"): | |
| error_msg = "β Failed to generate video plan" | |
| self.log_message(error_msg) | |
| return error_msg, None, self.log_message("") | |
| scenes_count = len(self.engine.project_config["video_plan"].get("scenes", [])) | |
| self.log_message(f"β Plan created with {scenes_count} scenes") | |
| # Step 2: Generate scene code | |
| self.log_message("π€ Step 2/5: Generating animation code...") | |
| progress(0.4, desc="Generating animation code...") | |
| self.engine.step_2_generate_scene_code() | |
| self.log_message("β Animation code generated") | |
| # Step 3: Generate audio | |
| self.log_message("π΅ Step 3/5: Generating audio narration...") | |
| progress(0.6, desc="Generating audio narration...") | |
| self.engine.step_3_generate_audio() | |
| self.log_message("β Audio generation completed") | |
| # Step 4: Render scenes | |
| self.log_message("π¨ Step 4/5: Rendering video scenes...") | |
| self.log_message("βΉοΈ Note: Each scene gets max 2 attempts to avoid excessive costs") | |
| progress(0.8, desc="Rendering video scenes...") | |
| self.engine.step_4_render_scenes() | |
| self.log_message("β Scene rendering completed") | |
| # Step 5: Assemble final video | |
| self.log_message("ποΈ Step 5/5: Assembling final video...") | |
| progress(0.9, desc="Assembling final video...") | |
| self.engine.step_5_assemble_final_video() | |
| total_elapsed = time.time() - total_start_time | |
| self.log_message(f"π Total generation time: {total_elapsed:.1f}s") | |
| # Find the final video file | |
| output_dir = Path(self.current_project_dir) / "output" | |
| video_files = list(output_dir.glob("*.mp4")) | |
| if video_files: | |
| final_video = str(video_files[0]) | |
| file_size = Path(final_video).stat().st_size / (1024*1024) | |
| self.log_message(f"π₯ Video created: {file_size:.1f}MB") | |
| return "β Video generated successfully!", final_video, self.log_message("") | |
| else: | |
| error_msg = "β Video generation completed but no output file found" | |
| self.log_message(error_msg) | |
| return error_msg, None, self.log_message("") | |
| except Exception as e: | |
| error_msg = f"β Error generating video: {str(e)[:50]}..." | |
| self.log_message(error_msg) | |
| return error_msg, None, self.log_message("") | |
| finally: | |
| progress(1.0, desc="Complete!") | |
| def cleanup(self): | |
| """Clean up temporary files""" | |
| if self.current_project_dir and Path(self.current_project_dir).exists(): | |
| try: | |
| shutil.rmtree(self.current_project_dir, ignore_errors=True) | |
| self.log_message("π§Ή Temporary files cleaned up") | |
| except Exception: | |
| pass | |
| EXAMPLE_PROMPTS = [ | |
| { | |
| "title": "Pythagorean Theorem", | |
| "prompt": "Explain the Pythagorean theorem with a visual proof using a right triangle and squares on each side", | |
| "description": "Mathematical proof with geometric visualization" | |
| }, | |
| { | |
| "title": "Photosynthesis Process", | |
| "prompt": "Show how photosynthesis works in plants, explaining the conversion of sunlight, water and CO2 into glucose", | |
| "description": "Biological process explanation with simple diagrams" | |
| }, | |
| { | |
| "title": "Compound Interest", | |
| "prompt": "Demonstrate the concept of compound interest with a visual graph showing money growth over time", | |
| "description": "Financial concept with animated graphs" | |
| }, | |
| { | |
| "title": "Neural Network Basics", | |
| "prompt": "Illustrate how a simple neural network learns, showing input nodes, hidden layers, and output", | |
| "description": "AI/ML concept with network visualization" | |
| }, | |
| { | |
| "title": "Laws of Motion", | |
| "prompt": "Explain Newton's three laws of motion with simple examples using moving objects and forces", | |
| "description": "Physics concepts with motion animations" | |
| }, | |
| { | |
| "title": "Cell Division", | |
| "prompt": "Show the process of mitosis, illustrating how one cell divides into two identical cells", | |
| "description": "Biology process with step-by-step visuals" | |
| }, | |
| { | |
| "title": "Quadratic Equations", | |
| "prompt": "Explain how to solve quadratic equations using the quadratic formula with a visual graph", | |
| "description": "Mathematical concept with formula derivation" | |
| }, | |
| { | |
| "title": "Water Cycle", | |
| "prompt": "Demonstrate the water cycle showing evaporation, condensation, precipitation and collection", | |
| "description": "Environmental science with cyclical process" | |
| } | |
| ] | |
| # Initialize the Gradio engine | |
| gradio_engine = GradioVideoEngine() | |
| def load_example_prompt(example_choice): | |
| """Load selected example prompt""" | |
| if example_choice == "Select an example...": | |
| return "" | |
| for example in EXAMPLE_PROMPTS: | |
| if f"{example['title']} - {example['description']}" == example_choice: | |
| return example['prompt'] | |
| return "" | |
| def create_interface(): | |
| with gr.Blocks(title="LVM Video Engine - Director AI", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown(""" | |
| # π¬ LVM Video Engine - Director AI Enhanced | |
| Create educational videos with AI-powered Manim animations! Simply describe what you want to teach, | |
| and the AI will generate a complete animated video with narration. | |
| **Quick Start:** Choose from our example prompts below or write your own custom prompt. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| api_key_input = gr.Textbox( | |
| label="π Gemini API Key", | |
| placeholder="Enter your Google Gemini API key", | |
| type="password", | |
| info="Get your API key from https://aistudio.google.com/" | |
| ) | |
| # Example prompts selector | |
| example_choices = ["Select an example..."] + [f"{ex['title']} - {ex['description']}" for ex in EXAMPLE_PROMPTS] | |
| example_dropdown = gr.Dropdown( | |
| label="π Example Prompts", | |
| choices=example_choices, | |
| value="Select an example...", | |
| info="Choose a pre-made example or write your own prompt below" | |
| ) | |
| prompt_input = gr.Textbox( | |
| label="π Video Topic/Prompt", | |
| placeholder="Describe what you want to create (e.g., 'Explain quadratic equations with examples')", | |
| lines=3 | |
| ) | |
| with gr.Row(): | |
| plan_btn = gr.Button("π Generate Plan Only", variant="secondary") | |
| generate_btn = gr.Button("π¬ Generate Complete Video", variant="primary") | |
| with gr.Column(scale=3): | |
| status_output = gr.Textbox( | |
| label="π Status", | |
| lines=2, | |
| interactive=False | |
| ) | |
| plan_output = gr.Markdown( | |
| label="π Video Plan", | |
| value="Video plan will appear here...", | |
| visible=True | |
| ) | |
| video_output = gr.Video( | |
| label="π₯ Generated Video", | |
| visible=True | |
| ) | |
| with gr.Accordion("π Process Log", open=False): | |
| console_output = gr.Textbox( | |
| label="Generation Log", | |
| lines=8, | |
| max_lines=15, | |
| interactive=False | |
| ) | |
| gr.Markdown(""" | |
| ### π‘ Tips for Better Results: | |
| - Be specific about the concept you want to explain | |
| - Mention if you want mathematical proofs, step-by-step explanations, or visual demonstrations | |
| - The AI works best with educational and mathematical content | |
| - Videos typically take 2-5 minutes to generate depending on complexity | |
| ### π οΈ Technical Notes: | |
| - Uses Manim for professional mathematical animations | |
| - AI-powered scene planning and code generation | |
| - Automatic error detection and fixing | |
| - Text-to-speech narration generation | |
| """) | |
| # Event handlers | |
| example_dropdown.change( | |
| fn=load_example_prompt, | |
| inputs=[example_dropdown], | |
| outputs=[prompt_input] | |
| ) | |
| plan_btn.click( | |
| fn=gradio_engine.generate_video_plan, | |
| inputs=[prompt_input, api_key_input], | |
| outputs=[status_output, plan_output, console_output] | |
| ) | |
| generate_btn.click( | |
| fn=gradio_engine.generate_full_video, | |
| inputs=[prompt_input, api_key_input], | |
| outputs=[status_output, video_output, console_output] | |
| ) | |
| # Cleanup on close | |
| demo.load(lambda: gradio_engine.cleanup()) | |
| return demo | |
| if __name__ == "__main__": | |
| demo = create_interface() | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| show_error=False | |
| ) | |