import gradio as gr import subprocess import tempfile import os import shutil from pathlib import Path import time # Default example code DEFAULT_CODE = '''from manim import * class SimpleAnimation(Scene): def construct(self): # Create a simple text animation title = Text("Welcome to Manimator!", font_size=48, color=BLUE) subtitle = Text("Mathematical Animation Renderer", font_size=24) subtitle.next_to(title, DOWN) # Create geometric shapes circle = Circle(radius=1, color=RED) square = Square(side_length=2, color=GREEN) triangle = Triangle(color=YELLOW) # Position shapes circle.shift(LEFT * 3) square.shift(RIGHT * 3) triangle.shift(DOWN * 2) # Animate! self.play(Write(title), run_time=2) self.wait(0.5) self.play(Write(subtitle), run_time=1.5) self.wait(1) # Show shapes self.play( Create(circle), Create(square), Create(triangle), run_time=2 ) self.wait(1) # Transform shapes self.play( circle.animate.set_color(PURPLE), square.animate.rotate(PI/4), triangle.animate.scale(1.5), run_time=2 ) self.wait(1) # Group and move everything = Group(title, subtitle, circle, square, triangle) self.play(everything.animate.shift(UP * 0.5), run_time=1.5) self.wait(2) ''' EXAMPLE_CODES = { "Simple Animation": DEFAULT_CODE, "Moving Shapes": '''from manim import * class MovingShapes(Scene): def construct(self): # Create shapes circle = Circle(color=BLUE) square = Square(color=RED).shift(RIGHT * 2) # Animations self.play(Create(circle)) self.play(circle.animate.shift(RIGHT * 2)) self.play(Transform(circle, square)) self.play(square.animate.scale(2).rotate(PI/4)) self.wait() ''', "Text Animation": '''from manim import * class TextAnimation(Scene): def construct(self): # Create text text1 = Text("This is Manim", font_size=72) text2 = Text("Create Amazing Animations!", font_size=48, color=YELLOW) text2.next_to(text1, DOWN) # Animate self.play(Write(text1)) self.wait() self.play(text1.animate.scale(0.7).shift(UP)) self.play(FadeIn(text2, shift=UP)) self.wait(2) ''', "Graph Example": '''from manim import * class GraphExample(Scene): def construct(self): # Create axes axes = Axes( x_range=[-3, 3, 1], y_range=[-3, 3, 1], axis_config={"color": BLUE} ) # Create graph graph = axes.plot(lambda x: x**2, color=RED) graph_label = axes.get_graph_label(graph, label='f(x) = x^2') # Animate self.play(Create(axes)) self.play(Create(graph), Write(graph_label)) self.wait(2) ''', "Dot Movement": '''from manim import * class DotMovement(Scene): def construct(self): # Create dots dot = Dot(color=RED) # Create path path = VMobject() path.set_points_as_corners([ LEFT * 2, UP * 2, RIGHT * 2, DOWN * 2, LEFT * 2 ]) path.set_color(BLUE) # Animate self.play(Create(path)) self.play(MoveAlongPath(dot, path), run_time=4) self.wait() ''', "Math Formulas (Text)": '''from manim import * class MathFormulas(Scene): def construct(self): # Math using Text() - works without LaTeX! title = Text("Famous Formulas", font_size=48, color=BLUE) title.to_edge(UP) # Einstein's equation einstein = Text("E = mc²", font_size=60, color=YELLOW) # Pythagorean theorem pythagoras = Text("a² + b² = c²", font_size=60, color=GREEN) pythagoras.next_to(einstein, DOWN, buff=1) # Euler's identity euler = Text("e^(iπ) + 1 = 0", font_size=60, color=RED) euler.next_to(pythagoras, DOWN, buff=1) # Animate self.play(Write(title)) self.wait(0.5) self.play(Write(einstein), run_time=2) self.wait(1) self.play(Write(pythagoras), run_time=2) self.wait(1) self.play(Write(euler), run_time=2) self.wait(2) ''', "3D Shapes": '''from manim import * class ThreeDShapes(Scene): def construct(self): # Note: 3D requires ThreeDScene for rotation # This shows basic 3D objects in 2D view title = Text("3D Objects", font_size=48) title.to_edge(UP) # Create 3D shapes (shown in 2D projection) sphere = Sphere(radius=1, color=BLUE).shift(LEFT * 3) cube = Cube(side_length=2, color=RED) torus = Torus(color=GREEN).shift(RIGHT * 3).scale(0.7) # Animate self.play(Write(title)) self.wait(0.5) self.play(Create(sphere), Create(cube), Create(torus), run_time=3) self.wait(2) ''', } def extract_scene_class(code): """Extract the Scene class name from code""" lines = code.split('\n') for line in lines: line = line.strip() if line.startswith('class ') and '(Scene)' in line: class_name = line.split('class ')[1].split('(')[0].strip() return class_name return None def render_manim(code, quality, progress=gr.Progress()): """Render Manim animation from code""" if not code.strip(): return None, "❌ Error: Please enter some code!" # Create temp directory temp_dir = tempfile.mkdtemp(prefix="gradio_manim_") try: progress(0.1, desc="Saving code...") # Save code to file temp_file = os.path.join(temp_dir, "animation.py") with open(temp_file, 'w', encoding='utf-8') as f: f.write(code) # Extract scene class scene_class = extract_scene_class(code) if not scene_class: return None, "❌ Error: No Scene class found in code!\n\nMake sure your code has a class that inherits from Scene, like:\nclass MyAnimation(Scene):" progress(0.2, desc=f"Found scene: {scene_class}") # Prepare command quality_map = { "Low (Fast)": "l", "Medium": "m", "High": "h", "Production": "p" } quality_flag = quality_map.get(quality, "l") output_dir = os.path.join(temp_dir, "media") # Use Python -m manim for cross-platform compatibility import sys cmd = [ sys.executable, "-m", "manim", "render", temp_file, scene_class, "-q", quality_flag, "--media_dir", output_dir, "--disable_caching" ] progress(0.3, desc="Starting render...") print(f"Running command: {' '.join(cmd)}") # Run manim result = subprocess.run( cmd, capture_output=True, text=True, timeout=300, # 5 minute timeout cwd=temp_dir # Set working directory ) print(f"Cmd run complete. Return code: {result.returncode}") progress(0.8, desc="Processing output...") # Combine stdout and stderr for output output_text = result.stdout + "\n" + result.stderr if result.returncode != 0: error_msg = f"❌ Rendering failed!\n\n{output_text}" return None, error_msg progress(0.9, desc="Finding video file...") # Find the generated video video_file = None videos_dir = os.path.join(output_dir, "videos", "animation") if os.path.exists(videos_dir): for root, dirs, files in os.walk(videos_dir): for file in files: if file.endswith('.mp4'): video_file = os.path.join(root, file) break if video_file: break if not video_file or not os.path.exists(video_file): # Check for static image images_dir = os.path.join(output_dir, "images", "animation") if os.path.exists(images_dir): for root, dirs, files in os.walk(images_dir): for file in files: if file.endswith(('.png', '.jpg')): return None, f"⚠️ Generated static image instead of video.\n\nYour scene has no animations!\nAdd self.play(...) commands to create animated videos.\n\nOutput:\n{output_text}" return None, f"❌ Could not find output file.\n\n{output_text}" progress(1.0, desc="Done!") # Success message file_size = os.path.getsize(video_file) / (1024 * 1024) # MB success_msg = f"✅ Animation rendered successfully!\n\n📊 Details:\n• Scene: {scene_class}\n• Quality: {quality}\n• File size: {file_size:.2f} MB" return video_file, success_msg except subprocess.TimeoutExpired: return None, "❌ Error: Rendering took too long (>5 minutes). Try reducing animation length or quality." except Exception as e: return None, f"❌ Error: {str(e)}" finally: # Cleanup is handled by Gradio, but we keep temp dir for video access pass def load_example(example_name): """Load example code""" return EXAMPLE_CODES.get(example_name, DEFAULT_CODE) def clear_all(): """Clear all fields""" return "", None, "Ready to render!" # Create Gradio interface with gr.Blocks(title="Manim Animation Editor", theme=gr.themes.Soft()) as demo: gr.Markdown(""" # 🎬 Manim Animation Editor Create mathematical animations using Manim! Write your code, click render, and watch your animation come to life. **📚 Quick Tips:** - Use `self.play(...)` to create animations (required for videos!) - Use `self.wait()` to pause - Use `Text()` for formulas (works everywhere!) - ⚠️ `MathTex()` requires LaTeX (may not work on hosted versions) - Check the examples below to get started """) with gr.Row(): with gr.Column(scale=2): # Code editor code_input = gr.Code( value=DEFAULT_CODE, language="python", label="📝 Manim Code", lines=25 ) with gr.Row(): # Quality selector quality_input = gr.Radio( choices=["Low (Fast)", "Medium", "High", "Production"], value="Low (Fast)", label="🎥 Quality", info="Higher quality = longer render time" ) with gr.Row(): # Action buttons render_btn = gr.Button("▶️ Render Animation", variant="primary", size="lg") clear_btn = gr.Button("🗑️ Clear", size="lg") # Examples gr.Markdown("### 📚 Example Animations") example_dropdown = gr.Dropdown( choices=list(EXAMPLE_CODES.keys()), label="Load Example", value="Simple Animation" ) load_example_btn = gr.Button("Load Example Code") with gr.Column(scale=2): # Video output video_output = gr.Video( label="🎬 Animation Output", height=400 ) # Status/output text output_text = gr.Textbox( label="📋 Status & Console Output", value="Ready to render! Write your code and click 'Render Animation'.", lines=15, max_lines=15 ) # Info section with gr.Accordion("ℹ️ Help & Documentation", open=False): gr.Markdown(""" ### Getting Started **Basic Structure:** ```python from manim import * class MyAnimation(Scene): def construct(self): # Create objects text = Text("Hello!") # Animate them self.play(Write(text)) self.wait() ``` **Common Commands:** - `Write(obj)` - Write text or formula - `Create(obj)` - Draw shapes - `FadeIn(obj)` - Fade in object - `Transform(obj1, obj2)` - Transform one object to another - `obj.animate.move_to(...)` - Move object - `self.wait(seconds)` - Pause for seconds **Common Objects:** - `Text("...")` - Text - `Circle()` - Circle - `Square()` - Square - `Line(start, end)` - Line - `Dot()` - Dot/Point **Colors:** - `RED`, `BLUE`, `GREEN`, `YELLOW`, `PURPLE`, `ORANGE`, `WHITE`, `BLACK` **Positions:** - `UP`, `DOWN`, `LEFT`, `RIGHT` - `ORIGIN` - Center (0, 0, 0) ### Requirements - Manim Community must be installed: `pip install manim` - For math formulas (MathTex), LaTeX must be installed ### Troubleshooting - **"No Scene class found"** - Make sure you have `class YourName(Scene):` - **"Static image generated"** - Add `self.play(...)` commands for animations - **LaTeX errors** - Use `Text()` instead of `MathTex()` or install LaTeX """) # Event handlers render_btn.click( fn=render_manim, inputs=[code_input, quality_input], outputs=[video_output, output_text] ) load_example_btn.click( fn=load_example, inputs=[example_dropdown], outputs=[code_input] ) clear_btn.click( fn=clear_all, outputs=[code_input, video_output, output_text] ) # Footer gr.Markdown(""" --- 💡 **Pro Tip:** Start with examples, then modify them to learn! 📖 [Manim Documentation](https://docs.manim.community/) | 🎓 [Manim Tutorial](https://docs.manim.community/en/stable/tutorials.html) """) # Launch the app if __name__ == "__main__": print("🚀 Starting Manim Animation Editor...") print("📱 Access from mobile: Use the public URL (with share=True)") print("💻 Local access: http://localhost:7860") demo.launch( # share=False, # Set to True for public URL # debug=True )