Spaces:
Configuration error
Configuration error
File size: 10,141 Bytes
a5e880f bd8d357 a5e880f ee6d55e a5e880f ee6d55e a5e880f ee6d55e a5e880f ee6d55e a5e880f 082c2a7 a5e880f bd8d357 a5e880f bd8d357 a5e880f 83cbaf8 a5e880f bd8d357 a5e880f bd8d357 a5e880f | 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 |
import os
import json
import re
from google import genai
from google.genai import types
COMPILER_SYSTEM_PROMPT = """
You are a deterministic Manim script generator for a text-to-educational-animation engine.
Your job:
Convert the user prompt into a single clean Manim Community Edition Python script.
The script will be executed automatically by a backend system, so the structure must be strict.
ββββββββββββββββββββββββββββ
ABSOLUTE REQUIREMENTS
ββββββββββββββββββββββββββββ
Always output ONLY Python code.
No markdown
No explanations
No comments
No triple quotes
NEVER include self.add_sound() calls
NEVER include audio or sound methods
Exactly 1 Scene class:
class GenScene(Scene):
Never use any other scene name.
Imports:
from manim import *
No LaTeX
Never use:
MathTex
Tex
Matrix
TexTemplate
Use only:
Text("expression or label")
ββββββββββββββββββββββββββββ
ASCII RULES (CRITICAL)
ββββββββββββββββββββββββββββ
1. NEVER output Unicode mathematical characters or subscript/superscript glyphs.
β Do not use: Β² β β Ξ± Ξ² Ξ³ ΞΈ Ο Ο β β Γ Γ·
βοΈ Instead use ONLY plain ASCII text:
"x^2" instead of "xΒ²"
"x_1" instead of "xβ"
"pi" instead of "Ο"
"velocity" instead of "βv"
2. The script must be compatible with Windows console and UTF-8 only.
- No special glyphs, emoji, arrows, smart quotes, curly quotes, or accents.
ββββββββββββββββββββββββββββ
VISUAL RULES (CRITICAL)
ββββββββββββββββββββββββββββ
1. NEVER allow visuals to go outside the video frame.
- Keep all objects centered or inside safe boundaries.
- Do not let squares, shapes, or arrows clip off-screen.
- Standard frame is [-7, 7] horizontally and [-4, 4] vertically. Keep well within this.
2. MANDATORY SPACING - No overlapping elements.
- Use FadeOut() to CLEAR previous content before showing new content
- Minimum buffer between objects: buff=0.5 (NEVER less than 0.4)
- Use screen zones:
* TOP zone (y=3 to y=2): For titles/headers
* MIDDLE zone (y=1 to y=-1): For main content
* BOTTOM zone (y=-2 to y=-3): For explanations/labels
- ALWAYS clear screen between major steps using self.play(FadeOut(VGroup(*self.mobjects)))
- Position text with .to_edge(UP/DOWN) or .shift(UP*2 / DOWN*2) to avoid center crowding
3. Visual accuracy FIRST.
- Show geometry clearly.
- Avoid rotating or stretching objects unnecessarily.
- Avoid random effects.
- Use scale(0.7) for text if needed to prevent overflow
ββββββββββββββββββββββββββββ
ANIMATION RULES
ββββββββββββββββββββββββββββ
1. Timing for audio synchronization:
- Each narration step gets approximately 3-4 seconds of animation
- Use run_time=1.0 for Write() and Create()
- Use run_time=0.8 for FadeIn/FadeOut
- Add self.wait(1.5) between major steps for narration
- TOTAL scene duration should be 12-20 seconds
2. Only use these animations:
Create
FadeIn
FadeOut
Write
Transform
MoveTo
Scale
Rotate
3. No 3D, no camera zoom, no cinematic effects, no physics.
4. STRUCTURE each step clearly:
- Clear previous content with FadeOut
- Show new title/concept
- Display visual elements one by one
- Add wait time for narration
- Transition to next step
ββββββββββββββββββββββββββββ
STRUCTURE & PACING
ββββββββββββββββββββββββββββ
1. Follow step-by-step logic:
- Introduce main idea
- Draw objects (one-by-one, not overlapping)
- Highlight key components
- Explain or show the formula visually
- Conclude cleanly
2. Keep total runtime 12β18 seconds.
- Use self.wait(1) or self.wait(2) to pace the video.
ββββββββββββββββββββββββββββ
OUTPUT FORMAT EXAMPLE
ββββββββββββββββββββββββββββ
from manim import *
class GenScene(Scene):
def construct(self):
# 1. Introduce
title = Text("Concept Name").scale(0.8).to_edge(UP)
self.play(Write(title), run_time=1)
self.wait(0.5)
# 2. Draw Objects
box = Square(side_length=2, color=BLUE)
self.play(Create(box), run_time=1)
self.wait(0.5)
# 3. Label (No overlap)
label = Text("Side = 2").next_to(box, DOWN, buff=0.5)
self.play(Write(label), run_time=1)
self.wait(1)
# 4. Conclude
self.play(FadeOut(box), FadeOut(label), run_time=1)
self.wait(1)
ββββββββββββββββββββββββββββ
FINAL OUTPUT RULE
ββββββββββββββββββββββββββββ
β‘οΈ Return ONLY Python code.
β‘οΈ No formatting, no text, no explanations.
β‘οΈ Only 1 Scene class named GenScene.
"""
async def generate_manim_code(outline: dict, step_audio_paths=None):
outline_str = json.dumps(outline, indent=2)
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
raise ValueError("GEMINI_API_KEY not found in environment variables.")
client = genai.Client(api_key=api_key)
prompt = f"{COMPILER_SYSTEM_PROMPT}\n\nINPUT OUTLINE:\n{outline_str}\n\nPYTHON CODE:"
try:
response = client.models.generate_content(
model='gemini-2.5-flash',
contents=prompt
)
code = response.text.strip()
# Cleanup markdown if present
if code.startswith("```python"):
code = code[9:]
elif code.startswith("```"):
code = code[3:]
if code.endswith("```"):
code = code[:-3]
# --- POST-PROCESSING SANITIZATION ---
if "MathTex" in code or "Tex(" in code:
print("WARNING: Model used LaTeX despite instructions. Sanitizing code...")
code = code.replace("MathTex", "Text")
code = code.replace("Tex(", "Text(")
replacements = {
r"^\\circ": " degrees", r"\\circ": " degrees", "Β°": " degrees",
r"\\theta": "theta", "ΞΈ": "theta",
r"\\pi": "pi", "Ο": "pi",
r"\\alpha": "alpha", "Ξ±": "alpha",
r"\\beta": "beta", "Ξ²": "beta",
r"\\gamma": "gamma", "Ξ³": "gamma",
r"\\sigma": "sigma", "Ο": "sigma",
r"\\Delta": "Delta", "Ξ": "Delta",
r"\\times": "x", "Γ": "x",
r"\\cdot": "*", "Β·": "*",
r"\\div": "/", "Γ·": "/",
r"\\pm": "+/-", "Β±": "+/-",
r"\\approx": "~", "β": "~",
r"\\neq": "!=", "β ": "!=",
r"\\le": "<=", "β€": "<=",
r"\\ge": ">=", "β₯": ">=",
r"\\infty": "infinity", "β": "infinity",
r"\\Rightarrow": "->", "β": "->",
r"\\rightarrow": "->", "β": "->",
r"\\leftarrow": "<-", "β": "<-",
"Β²": "^2", "Β³": "^3", "β": "_1", "β": "_2", "β": "_x",
r"\\\\": "\n", # Double backslash to newline
"β": "-", # En dash to hyphen
"β": "-", # Em dash to hyphen
"β": "'", # Smart quotes
"β": '"',
"β": '"',
}
for pattern, replacement in replacements.items():
code = code.replace(pattern, replacement)
# Remove any model-generated self.add_sound calls and self.wait calls
lines = code.split('\n')
code = '\n'.join([line for line in lines if "self.add_sound" not in line and not line.strip().startswith("self.wait(")])
# Fix .center usage (replace .center with .get_center() only when used as an argument)
code = re.sub(r'([\w\)\]]+)\.center(\s*\))', r'\1.get_center()\2', code)
code = re.sub(r'class\s+\w+\(Scene\):', 'class GenScene(Scene):', code)
# Fix VGroup(*self.mobjects) -> Group(*self.mobjects)
# VGroup can only contain VMobjects, but self.mobjects may contain Groups
code = re.sub(r'VGroup\(\*self\.mobjects\)', r'Group(*self.mobjects)', code)
# DISABLE audio insertion - it causes syntax errors
# Audio feature is disabled to prevent malformed code
# Validate Python syntax
try:
compile(code, '<string>', 'exec')
print("β Generated code validated successfully")
except SyntaxError as se:
print(f"β Syntax error in generated code: {se}")
print(f" Line {se.lineno}: {se.text}")
# Try to fix by removing problematic lines
lines = code.split('\n')
fixed_lines = []
error_line = se.lineno - 1 if se.lineno else -1
for i, line in enumerate(lines):
skip = False
if i == error_line or "self.add_sound" in line or (line.strip().startswith("self.wait(") and "self.play" not in lines[i-1] if i > 0 else False):
print(f" β Removing problematic line {i+1}: {line.strip()}")
skip = True
if not skip:
fixed_lines.append(line)
code = '\n'.join(fixed_lines)
# Try compiling again
try:
compile(code, '<string>', 'exec')
print("β Fixed syntax errors")
except SyntaxError as se2:
print(f"β Still has syntax errors: {se2}")
return code
except Exception as e:
print(f"Error generating code with Gemini: {e}")
raise e
|