Chand11 commited on
Commit
356823a
Β·
verified Β·
1 Parent(s): a12e311

Update app.py

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