Spaces:
Sleeping
Sleeping
| 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 | |
| ) |