Spaces:
No application file
No application file
| import gradio as gr | |
| import google.generativeai as genai | |
| import os | |
| import re | |
| import subprocess | |
| import tempfile | |
| import shutil | |
| from pathlib import Path | |
| import sys | |
| # Check if API key is available | |
| api_key = os.getenv("GEMINI_API_KEY") | |
| if not api_key: | |
| print("β οΈ Warning: GEMINI_API_KEY not found in environment variables") | |
| print("Please set your Gemini API key in the Hugging Face Spaces secrets") | |
| # Configure Gemini API | |
| genai.configure(api_key=api_key) | |
| model = genai.GenerativeModel( | |
| model_name="gemini-1.5-flash", # Using more stable model for deployment | |
| generation_config={ | |
| "temperature": 0.3, | |
| "top_p": 0.95, | |
| "top_k": 40, | |
| "max_output_tokens": 8000, # Reduced for better performance | |
| } | |
| ) | |
| def generate_video(topic, duration_minutes=1): | |
| if not api_key: | |
| return None, "β Error: GEMINI_API_KEY not configured. Please set it in Hugging Face Spaces secrets." | |
| try: | |
| # Step 1: Generate plan for Manim animation | |
| planning_prompt = f""" | |
| You are a professional with deep expertise in Manim CE 0.18.0. | |
| Your task is to design a presentation-style video using Manim for the topic: "{topic}" | |
| Guidelines: | |
| - The video should consist of **slides of text** with **smooth transitions** between them. | |
| - Structure the scene logically with 3-5 main points maximum. | |
| - Target duration: {duration_minutes} minute(s). | |
| - Use **simple text objects only**, no equations, graphs, or complex animations. | |
| - Keep text concise and readable. | |
| - Include details like: | |
| - Text to be shown (keep it short and clear) | |
| - When to fade in/out | |
| - Slide duration (2-4 seconds per slide) | |
| - Transitions (FadeIn, FadeOut, Transform) | |
| - Do not output any code here β only the **presentation plan**, step by step. | |
| Output Format: | |
| Scene 1: | |
| - Text: "Title: {topic}" | |
| - Action: FadeIn | |
| - Duration: 3 seconds | |
| Scene 2: | |
| - Text: "Key Point 1: [brief description]" | |
| - Action: Transform from previous | |
| - Duration: 4 seconds | |
| ... | |
| """ | |
| plan_response = model.generate_content(planning_prompt) | |
| plan = plan_response.text.strip() | |
| # Step 2: Generate Manim code based on the plan | |
| code_prompt = f""" | |
| Using this animation plan: | |
| {plan} | |
| Write a complete Manim CE 0.18.0 compatible Python file with a class called `VideoScene` that inherits from Scene. | |
| Critical Requirements: | |
| - Import all necessary modules: from manim import * | |
| - Use ONLY basic text objects and simple transitions | |
| - Background should be WHITE: self.camera.background_color = WHITE | |
| - Text should be BLACK for contrast | |
| - Keep text size reasonable (font_size=24 to 36) | |
| - Use FadeIn, FadeOut, and Transform only | |
| - Add self.clear() between slides to avoid overlapping text | |
| - Include proper self.wait() durations | |
| - Make sure the class is properly indented and structured | |
| - NO complex animations, NO equations, NO images | |
| - Keep it simple and clean | |
| Example structure: | |
| ```python | |
| from manim import * | |
| class VideoScene(Scene): | |
| def construct(self): | |
| self.camera.background_color = WHITE | |
| # Title slide | |
| title = Text("Your Title", font_size=36, color=BLACK) | |
| self.play(FadeIn(title)) | |
| self.wait(3) | |
| self.play(FadeOut(title)) | |
| self.clear() | |
| # Next slide... | |
| ``` | |
| Output: Just the complete Python code, no explanations. | |
| """ | |
| final_code = model.generate_content(code_prompt) | |
| raw_code = final_code.text.strip() | |
| # Clean the code | |
| cleaned_code = re.sub(r"^(```|''')python\s*", "", raw_code) | |
| cleaned_code = re.sub(r"(```|''')\s*$", "", cleaned_code) | |
| # Create temporary directory for this generation | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| temp_path = Path(temp_dir) | |
| # Save the generated code | |
| manim_file = temp_path / "generated_video.py" | |
| with open(manim_file, "w", encoding="utf-8") as f: | |
| f.write(cleaned_code) | |
| # Run manim to generate video with error handling | |
| try: | |
| # Use low quality for faster generation in cloud environment | |
| result = subprocess.run([ | |
| "manim", "-pql", "--fps", "15", str(manim_file), "VideoScene" | |
| ], | |
| cwd=temp_dir, | |
| capture_output=True, | |
| text=True, | |
| check=True, | |
| timeout=120 # 2 minute timeout | |
| ) | |
| # Find the generated video file | |
| media_dir = temp_path / "media" | |
| video_files = list(media_dir.rglob("*.mp4")) | |
| if video_files: | |
| video_file = video_files[0] | |
| # Copy to a location accessible by Gradio | |
| output_file = f"video_{abs(hash(topic)) % 10000}.mp4" | |
| shutil.copy2(video_file, output_file) | |
| success_msg = f"β Video generated successfully!\n\n**Plan:**\n{plan}\n\n**Generated Code:**\n```python\n{cleaned_code}\n```" | |
| return output_file, success_msg | |
| else: | |
| error_msg = f"β No video file found after generation.\n\n**Plan:**\n{plan}\n\n**Generated Code:**\n```python\n{cleaned_code}\n```\n\n**Manim Output:**\n{result.stdout}" | |
| return None, error_msg | |
| except subprocess.TimeoutExpired: | |
| return None, f"β Video generation timed out. Try a shorter duration or simpler topic.\n\n**Plan:**\n{plan}" | |
| except subprocess.CalledProcessError as e: | |
| error_msg = f"β Manim generation failed:\n{e.stderr}\n\n**Plan:**\n{plan}\n\n**Generated Code:**\n```python\n{cleaned_code}\n```" | |
| return None, error_msg | |
| except Exception as e: | |
| return None, f"β Unexpected error: {str(e)}" | |
| # Create Gradio interface | |
| with gr.Blocks(title="AI Video Generator", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown(""" | |
| # π¬ AI Video Generator | |
| Generate educational videos using AI and Manim animations. Simply enter a topic and get a professional presentation-style video! | |
| **Features:** | |
| - π€ AI-powered content generation using Gemini | |
| - π¨ Professional Manim animations | |
| - β±οΈ Customizable duration (1-3 minutes) | |
| - π± Clean presentation style | |
| **Note:** Video generation may take 1-3 minutes depending on complexity. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| topic_input = gr.Textbox( | |
| label="π Video Topic", | |
| placeholder="Enter an educational topic (e.g., 'Introduction to Python', 'Basic Math Concepts')", | |
| value="Introduction to Python Programming", | |
| lines=2 | |
| ) | |
| duration_input = gr.Slider( | |
| minimum=1, | |
| maximum=3, | |
| value=1, | |
| step=1, | |
| label="β±οΈ Duration (minutes)", | |
| info="Shorter durations generate faster" | |
| ) | |
| generate_btn = gr.Button("π Generate Video", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| gr.Markdown(""" | |
| ### π‘ Tips for Best Results: | |
| - Use clear, educational topics | |
| - Avoid overly complex subjects | |
| - Shorter durations work better | |
| - Be patient - generation takes time | |
| ### π― Great Topic Examples: | |
| - "Introduction to Python" | |
| - "Basic Data Structures" | |
| - "How Photosynthesis Works" | |
| - "Understanding Fractions" | |
| - "Solar System Overview" | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| video_output = gr.Video( | |
| label="π₯ Generated Video", | |
| height=400 | |
| ) | |
| with gr.Column(): | |
| log_output = gr.Textbox( | |
| label="π Generation Details", | |
| lines=15, | |
| max_lines=25, | |
| show_copy_button=True, | |
| placeholder="Generation logs and AI-created content will appear here..." | |
| ) | |
| # Progress indicator | |
| with gr.Row(): | |
| status_text = gr.Textbox( | |
| label="π Status", | |
| value="Ready to generate video", | |
| interactive=False | |
| ) | |
| def update_status(message): | |
| return message | |
| def generate_with_status(topic, duration): | |
| yield None, "π Starting video generation...", "π Initializing AI content creation..." | |
| try: | |
| result_video, result_log = generate_video(topic, duration) | |
| if result_video: | |
| yield result_video, result_log, "β Video generated successfully!" | |
| else: | |
| yield None, result_log, "β Video generation failed" | |
| except Exception as e: | |
| yield None, f"β Error: {str(e)}", "β Generation failed with error" | |
| generate_btn.click( | |
| fn=generate_with_status, | |
| inputs=[topic_input, duration_input], | |
| outputs=[video_output, log_output, status_text], | |
| show_progress=True | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch( | |
| share=False, | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| show_error=True | |
| ) | |