Chand11 commited on
Commit
35f8b0a
Β·
verified Β·
1 Parent(s): 4b0f123

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +246 -0
app.py ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import subprocess
3
+ 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
+
45
+ class MyScene(Scene):
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
119
+ temp_dir = Path("temp_manim")
120
+ temp_dir.mkdir(exist_ok=True)
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:
129
+ code = code.split("```python")[1].split("```")[0]
130
+ elif "```" in code:
131
+ code = code.split("```")[1].split("```")[0]
132
+ code = code.strip()
133
+
134
+ # Apply fixes
135
+ code = fix_all_known_errors(code)
136
+
137
+ # Find Scene class
138
+ match = re.search(r'class\s+(\w+)\s*\(Scene\)', code)
139
+ if not match:
140
+ yield "❌ No Scene class found in generated code!", code, None
141
+ return
142
+
143
+ class_name = match.group(1)
144
+
145
+ # Save code
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
153
+ quality_map = {'low': '-ql', 'medium': '-qm', 'high': '-qh'}
154
+ quality_flag = quality_map.get(quality, '-ql')
155
+
156
+ command = f"manim {code_file} {class_name} {quality_flag} --disable_caching --media_dir {temp_dir}/media"
157
+
158
+ process = subprocess.Popen(
159
+ command, shell=True,
160
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
161
+ text=True, cwd=temp_dir
162
+ )
163
+
164
+ output_lines = []
165
+ for line in process.stdout:
166
+ output_lines.append(line)
167
+
168
+ process.wait()
169
+
170
+ if process.returncode == 0:
171
+ # Find the generated video
172
+ video_files = list((temp_dir / "media" / "videos" / "animation").rglob("*.mp4"))
173
+
174
+ if video_files:
175
+ video_path = str(video_files[0])
176
+ yield f"βœ… Video rendered successfully!", code, video_path
177
+ else:
178
+ yield "❌ Video file not found after rendering", code, None
179
+ else:
180
+ error_msg = ''.join(output_lines[-20:]) # Last 20 lines
181
+ yield f"❌ Rendering failed:\n{error_msg}", code, None
182
+
183
+ except Exception as e:
184
+ yield f"❌ Error: {str(e)}", None, None
185
+
186
+ # Gradio Interface
187
+ def create_interface():
188
+ with gr.Blocks(title="Manim Video Generator", theme=gr.themes.Soft()) as demo:
189
+ gr.Markdown("""
190
+ # 🎬 Manim Video Generator
191
+ Create mathematical animations using AI! Describe what you want to see animated.
192
+
193
+ **Examples:**
194
+ - "Explain Pythagorean theorem with animation"
195
+ - "Show a sine wave transforming into a cosine wave"
196
+ - "Animate the concept of derivatives"
197
+ """)
198
+
199
+ with gr.Row():
200
+ with gr.Column():
201
+ prompt_input = gr.Textbox(
202
+ label="What animation do you want?",
203
+ placeholder="e.g., Show a circle morphing into a square",
204
+ lines=3
205
+ )
206
+ quality_input = gr.Radio(
207
+ choices=["low", "medium", "high"],
208
+ value="low",
209
+ label="Quality (low is faster)"
210
+ )
211
+ generate_btn = gr.Button("🎬 Generate Video", variant="primary")
212
+
213
+ with gr.Column():
214
+ status_output = gr.Textbox(label="Status", lines=3)
215
+ video_output = gr.Video(label="Generated Animation")
216
+
217
+ code_output = gr.Code(label="Generated Manim Code", language="python")
218
+
219
+ gr.Markdown("""
220
+ ### πŸ’‘ Tips:
221
+ - Start with simple prompts
222
+ - Low quality renders faster (recommended for testing)
223
+ - Complex animations may timeout on free tier
224
+ """)
225
+
226
+ generate_btn.click(
227
+ fn=render_video,
228
+ inputs=[prompt_input, quality_input],
229
+ outputs=[status_output, code_output, video_output]
230
+ )
231
+
232
+ # Examples
233
+ gr.Examples(
234
+ examples=[
235
+ ["Show a circle morphing into a square", "low"],
236
+ ["Animate the number pi appearing", "low"],
237
+ ["Show three dots moving in a triangle pattern", "low"],
238
+ ],
239
+ inputs=[prompt_input, quality_input]
240
+ )
241
+
242
+ return demo
243
+
244
+ if __name__ == "__main__":
245
+ demo = create_interface()
246
+ demo.launch()