Spaces:
Sleeping
Sleeping
File size: 12,071 Bytes
35f8b0a 356823a 35f8b0a 356823a 4689e9f 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a a12e311 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a a12e311 35f8b0a a12e311 35f8b0a a12e311 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 7ea35cf 35f8b0a 4689e9f 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a 356823a 35f8b0a d7b52f6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 | import gradio as gr
import subprocess
import os
import re
import shutil
from pathlib import Path
import tempfile
# Try to import google.generativeai
try:
import google.generativeai as genai
GENAI_AVAILABLE = True
except ImportError:
GENAI_AVAILABLE = False
print("")
# Get API key from environment
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
def fix_all_known_errors(code):
"""Fix ALL known Manim errors"""
# 1. Fix colors to base 10 only
color_map = {
'ORANGE_A': 'ORANGE', 'ORANGE_B': 'ORANGE', 'ORANGE_C': 'ORANGE', 'ORANGE_D': 'ORANGE', 'ORANGE_E': 'ORANGE',
'GRAY_A': 'WHITE', 'GRAY_B': 'WHITE', 'GRAY_C': 'WHITE', 'GRAY_D': 'WHITE', 'GRAY_E': 'WHITE',
'GREY': 'WHITE', 'GRAY': 'WHITE', 'GREY_A': 'WHITE', 'GREY_B': 'WHITE',
'PURPLE_A': 'PURPLE', 'PURPLE_B': 'PURPLE', 'PURPLE_C': 'PURPLE', 'PURPLE_D': 'PURPLE', 'PURPLE_E': 'PURPLE',
'RED_A': 'RED', 'RED_B': 'RED', 'RED_C': 'RED', 'RED_D': 'RED', 'RED_E': 'RED',
'BLUE_A': 'BLUE', 'BLUE_B': 'BLUE', 'BLUE_C': 'BLUE', 'BLUE_D': 'BLUE', 'BLUE_E': 'BLUE',
'GREEN_A': 'GREEN', 'GREEN_B': 'GREEN', 'GREEN_C': 'GREEN', 'GREEN_D': 'GREEN', 'GREEN_E': 'GREEN',
'YELLOW_A': 'YELLOW', 'YELLOW_B': 'YELLOW', 'YELLOW_C': 'YELLOW', 'YELLOW_D': 'YELLOW', 'YELLOW_E': 'YELLOW',
'BROWN': 'ORANGE', 'GOLD': 'YELLOW', 'SILVER': 'WHITE', 'BRONZE': 'ORANGE',
'MAROON': 'RED', 'NAVY': 'BLUE', 'CYAN': 'TEAL', 'MAGENTA': 'PINK', 'LIME': 'GREEN',
}
for old, new in color_map.items():
code = re.sub(rf'\b{old}\b', new, code)
# 2. Fix invalid objects
code = re.sub(r'Checkmark\([^)]*\)', 'Circle(radius=0.3, color=GREEN, fill_opacity=1)', code)
code = re.sub(r'CheckMark\([^)]*\)', 'Circle(radius=0.3, color=GREEN, fill_opacity=1)', code)
code = re.sub(r'XMark\([^)]*\)', 'Cross(color=RED).scale(0.5)', code)
code = re.sub(r'SVGMobject\([^)]+\)', 'Circle(radius=0.8, color=PINK, fill_opacity=0.8)', code)
code = re.sub(r'ImageMobject\([^)]+\)', 'Square(side_length=2, color=BLUE, fill_opacity=0.5)', code)
# 3. Fix DecimalNumber methods
code = re.sub(r'\.add_prefix\([^)]+\)', '', code)
code = re.sub(r'\.add_suffix\([^)]+\)', '', code)
# 4. Fix axis labels - replace with Text
code = re.sub(r'(\w+)\s*=\s*axes\.get_x_axis_label\([^)]+\)',
r'\1 = Text("X", font_size=24).next_to(axes.x_axis, DOWN, buff=0.3)', code)
code = re.sub(r'(\w+)\s*=\s*axes\.get_y_axis_label\([^)]+\)',
r'\1 = Text("Y", font_size=24).next_to(axes.y_axis, LEFT, buff=0.3).rotate(90*DEGREES)', code)
code = re.sub(r'\.get_x_axis_label\([^)]+\)', '', code)
code = re.sub(r'\.get_y_axis_label\([^)]+\)', '', code)
# 5. Limit font sizes
code = re.sub(r'font_size=(\d+)', lambda m: f'font_size={min(int(m.group(1)), 36)}', code)
# 6. Fix shifts to safe values
code = re.sub(r'\.shift\(UP\s*\*\s*\d+\.?\d*\)', '.shift(UP*1.5)', code)
code = re.sub(r'\.shift\(DOWN\s*\*\s*\d+\.?\d*\)', '.shift(DOWN*1.5)', code)
code = re.sub(r'\.shift\(LEFT\s*\*\s*\d+\.?\d*\)', '.shift(LEFT*3)', code)
code = re.sub(r'\.shift\(RIGHT\s*\*\s*\d+\.?\d*\)', '.shift(RIGHT*3)', code)
# 7. Force buff on all to_edge
code = re.sub(r'\.to_edge\((UP|DOWN|LEFT|RIGHT)\)(?!\s*,)', r'.to_edge(\1, buff=1)', code)
# 8. Replace Tex with MathTex
code = re.sub(r'\bTex\(', 'MathTex(', code)
return code
def generate_code_with_gemini(prompt):
"""Generate Manim code using Gemini API"""
if not GENAI_AVAILABLE:
return None, "β google-generativeai package not installed"
if not GEMINI_API_KEY:
return None, "β GEMINI_API_KEY not set in environment variables"
try:
genai.configure(api_key=GEMINI_API_KEY)
models = [m.name for m in genai.list_models() if 'generateContent' in m.supported_generation_methods]
model_name = next((m for m in models if 'flash' in m.lower()), models[0])
model = genai.GenerativeModel(model_name)
full_prompt = f"""Generate Manim code. CRITICAL RULES:
1. COLORS - ONLY these 10: BLUE, RED, GREEN, YELLOW, ORANGE, PURPLE, PINK, TEAL, WHITE, BLACK
NO variants (_A, _B, _C, _D, _E)
2. TEXT OVERLAP PREVENTION (MOST IMPORTANT):
- ALWAYS FadeOut text before showing new text
- NEVER reuse the same position without FadeOut first
- Pattern for EVERY text:
```python
title = Text("Title", font_size=36).to_edge(UP, buff=1)
self.play(Write(title))
self.wait(1.5)
self.play(FadeOut(title)) # β MANDATORY!
# Now safe to reuse UP position
subtitle = Text("Next", font_size=32).to_edge(UP, buff=1)
self.play(Write(subtitle))
self.wait(1.5)
self.play(FadeOut(subtitle)) # β MANDATORY!
```
3. SAFE BOUNDARIES (CRITICAL):
- Font size: MAX 36 (never larger!)
- Shifts: UP*1.5, DOWN*1.5, LEFT*3, RIGHT*3 (MAX!)
- Always use buff=1 with to_edge()
- Safe zone: X[-4, 4], Y[-2, 2]
4. NEVER use:
- get_x_axis_label(), get_y_axis_label()
- Checkmark, SVGMobject, ImageMobject
- .add_prefix(), .add_suffix()
5. For axis labels:
x_label = Text("Time", font_size=24).next_to(axes.x_axis, DOWN, buff=0.5)
y_label = Text("Value", font_size=24).next_to(axes.y_axis, LEFT, buff=0.5)
6. STRUCTURE - Follow this pattern:
```python
from manim import *
class MyScene(Scene):
def construct(self):
self.camera.background_color = WHITE
# Section 1
text1 = Text("First", font_size=36).to_edge(UP, buff=1)
self.play(Write(text1))
self.wait(1.5)
self.play(FadeOut(text1)) # β Clean up!
# Section 2
text2 = Text("Second", font_size=32).to_edge(UP, buff=1)
self.play(Write(text2))
self.wait(1.5)
self.play(FadeOut(text2)) # β Clean up!
```
User wants: {prompt}
Generate complete code following ALL rules above. EVERY text MUST have FadeOut before next text!"""
response = model.generate_content(full_prompt)
code = response.text
return code, None
except Exception as e:
return None, f"β Gemini API error: {str(e)}"
def render_video(prompt, quality="low"):
"""Main function to generate and render Manim video"""
if not prompt or not prompt.strip():
yield "β Please enter a prompt!", None, None
return
try:
# Create temp directory
temp_dir = Path(tempfile.mkdtemp(prefix="manim_"))
yield "π€ Generating code with Gemini AI...", None, None
# Generate code
code, error = generate_code_with_gemini(prompt)
if error:
yield error, None, None
return
if not code:
yield "β Failed to generate code", None, None
return
# Extract Python code if wrapped in markdown
if "```python" in code:
code = code.split("```python")[1].split("```")[0]
elif "```" in code:
code = code.split("```")[1].split("```")[0]
code = code.strip()
# Apply fixes
code = fix_all_known_errors(code)
# Find Scene class
match = re.search(r'class\s+(\w+)\s*\(Scene\)', code)
if not match:
yield "β No Scene class found in generated code!", code, None
return
class_name = match.group(1)
# Save code to file
code_file = temp_dir / "animation.py"
with open(code_file, 'w', encoding='utf-8') as f:
f.write(code)
yield f"β Code generated (Scene: {class_name})\nπ¬ Rendering video (this may take 1-2 minutes)...", code, None
# Render video with absolute paths
quality_map = {'low': '-ql', 'medium': '-qm', 'high': '-qh'}
quality_flag = quality_map.get(quality, '-ql')
abs_code_file = str(code_file.absolute())
media_dir = str((temp_dir / "media").absolute())
command = f"manim {abs_code_file} {class_name} {quality_flag} --disable_caching --media_dir {media_dir}"
process = subprocess.Popen(
command, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True
)
output_lines = []
for line in process.stdout:
output_lines.append(line)
process.wait()
if process.returncode == 0:
# Find the generated video
media_path = temp_dir / "media"
video_files = list(media_path.rglob("*.mp4"))
if video_files:
video_path = str(video_files[0])
yield f"β
Video rendered successfully! π", code, video_path
else:
yield "β Video file not found after rendering", code, None
else:
error_msg = ''.join(output_lines[-30:]) # Last 30 lines
yield f"β Rendering failed:\n\n{error_msg}", code, None
# Cleanup
try:
shutil.rmtree(temp_dir)
except:
pass
except Exception as e:
import traceback
error_details = traceback.format_exc()
yield f"β Error: {str(e)}\n\nDetails:\n{error_details}", None, None
# Gradio Interface
def create_interface():
with gr.Blocks(title="Manim Video Generator") as demo:
gr.Markdown("""
# π¬ Manim Video Generator
Create mathematical animations using AI! Describe what you want to see animated.
**Examples:**
- "Explain Pythagorean theorem with animation"
- "Show a sine wave transforming into a cosine wave"
- "Animate a circle morphing into a square"
- "Show the concept of derivatives with a tangent line"
""")
with gr.Row():
with gr.Column():
prompt_input = gr.Textbox(
label="What animation do you want?",
placeholder="e.g., Show a circle morphing into a square",
lines=4
)
quality_input = gr.Radio(
choices=["low", "medium", "high"],
value="low",
label="Quality (low is faster, recommended)"
)
generate_btn = gr.Button("π¬ Generate Video", variant="primary", size="lg")
with gr.Column():
status_output = gr.Textbox(label="Status", lines=4)
video_output = gr.Video(label="Generated Animation")
code_output = gr.Code(label="Generated Manim Code", language="python", lines=15)
gr.Markdown("""
### π‘ Tips:
- Be specific in your description
- Start with simple prompts to test
- Low quality renders faster (30 seconds to 1 minute)
- Medium/High quality may take 2-3 minutes
- Complex animations may timeout on free tier
""")
generate_btn.click(
fn=render_video,
inputs=[prompt_input, quality_input],
outputs=[status_output, code_output, video_output]
)
# Examples
gr.Examples(
examples=[
["Show a blue circle morphing into a red square", "low"],
["Animate the Pythagorean theorem: aΒ² + bΒ² = cΒ²", "low"],
["Show a sine wave moving across the screen", "low"],
["Create an animation showing 3 dots forming a triangle", "low"],
["Show a number counting from 0 to 10", "low"],
],
inputs=[prompt_input, quality_input]
)
return demo
if __name__ == "__main__":
demo = create_interface()
demo.launch() |