parthraninga commited on
Commit
f23ec20
·
verified ·
1 Parent(s): e89ffe9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +30 -331
app.py CHANGED
@@ -1,357 +1,56 @@
1
- import gradio as gr
2
- import subprocess
3
- import os
4
- import shutil
5
- import tempfile
6
- import re
7
- import ast
8
- import sys
9
- import time
10
- from pathlib import Path
11
-
12
  # --- Configuration ---
13
- BASE_DIR = "./media" # Use relative path in user home directory
14
-
15
- def ensure_permissions():
16
- """Ensure the base directory exists and is writable."""
17
- global BASE_DIR
18
-
19
- try:
20
- # Create directories with proper permissions
21
- os.makedirs(BASE_DIR, exist_ok=True, mode=0o755)
22
-
23
- # Try to create a test file to check permissions
24
- test_file = os.path.join(BASE_DIR, "test_write.txt")
25
- with open(test_file, 'w') as f:
26
- f.write("test")
27
- os.remove(test_file)
28
- print(f"✅ Directory {BASE_DIR} is writable")
29
-
30
- except PermissionError as e:
31
- print(f"❌ Permission denied for {BASE_DIR}: {e}")
32
- # Try alternative directories
33
- for alt_dir in ["./tmp_media", "/tmp/manim_render", "./output"]:
34
- try:
35
- BASE_DIR = alt_dir
36
- os.makedirs(BASE_DIR, exist_ok=True, mode=0o755)
37
- test_file = os.path.join(BASE_DIR, "test_write.txt")
38
- with open(test_file, 'w') as f:
39
- f.write("test")
40
- os.remove(test_file)
41
- print(f"✅ Using alternative directory: {BASE_DIR}")
42
- break
43
- except:
44
- continue
45
- else:
46
- raise gr.Error("Cannot create writable directory for video output")
47
- except Exception as e:
48
- print(f"⚠️ Warning: Issue with directory setup: {e}")
49
- # Last resort: current directory
50
- BASE_DIR = "./media"
51
- os.makedirs(BASE_DIR, exist_ok=True)
52
-
53
- def get_scene_class_name(code):
54
- """Extract the scene class name from the Manim code."""
55
- try:
56
- # Parse the AST to find class definitions
57
- tree = ast.parse(code)
58
- for node in ast.walk(tree):
59
- if isinstance(node, ast.ClassDef):
60
- # Check if the class inherits from Scene
61
- for base in node.bases:
62
- if isinstance(base, ast.Name) and base.id == 'Scene':
63
- return node.name
64
- return None
65
- except:
66
- # Fallback to regex if AST parsing fails
67
- pattern = r'class\s+(\w+)\s*\([^)]*Scene[^)]*\):'
68
- match = re.search(pattern, code)
69
- return match.group(1) if match else None
70
-
71
- def validate_manim_code(code):
72
- """Validate Manim code for common issues."""
73
- if not code.strip():
74
- return False, "Code is empty."
75
-
76
- # Check for Scene class
77
- if not get_scene_class_name(code):
78
- return False, "No Scene class found. Make sure your class inherits from Scene."
79
-
80
- # Check for basic syntax
81
- try:
82
- ast.parse(code)
83
- except SyntaxError as e:
84
- return False, f"Syntax error: {e}"
85
-
86
- # Check for required construct method
87
- if 'def construct(' not in code:
88
- return False, "Scene class must have a construct() method."
89
-
90
- return True, "Code looks good!"
91
 
92
- def check_manim_installation():
93
- """Check if Manim is properly installed."""
94
- try:
95
- result = subprocess.run(
96
- ["manim", "--version"],
97
- capture_output=True,
98
- text=True,
99
- timeout=10
100
- )
101
- if result.returncode == 0:
102
- return True, result.stdout.strip()
103
- else:
104
- return False, "Manim command failed"
105
- except Exception as e:
106
- return False, f"Manim not found: {e}"
107
-
108
- def setup_environment():
109
- """Set up the environment for Manim rendering."""
110
- # Ensure permissions
111
- ensure_permissions()
112
-
113
- # Check Manim installation
114
- manim_ok, manim_info = check_manim_installation()
115
- if not manim_ok:
116
- print(f"⚠️ Manim issue: {manim_info}")
117
- else:
118
- print(f"✅ {manim_info}")
119
-
120
- # Set environment variables for better rendering
121
- os.environ['MANIM_CONFIG_DIR'] = BASE_DIR
122
- os.environ['MANIM_PLUGINS_DIR'] = os.path.join(BASE_DIR, 'plugins')
123
-
124
- return manim_ok
125
-
126
- def render_manim_video(manim_code):
127
  """
128
- Runs the Manim command with dynamic code and returns the path to the rendered video.
 
129
  """
130
- # Ensure we have write permissions
131
- ensure_permissions()
132
-
133
- if not manim_code.strip():
134
- raise gr.Error("Please provide Manim code.")
135
-
136
- # Extract scene class name from the code
137
- scene_name = get_scene_class_name(manim_code)
138
- if not scene_name:
139
- raise gr.Error("Could not find a Scene class in your code. Make sure your class inherits from Scene.")
140
-
141
- # Create a temporary Python file with the Manim code
142
- with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
143
- # Add the necessary import if not present
144
- if 'from manim import *' not in manim_code and 'import manim' not in manim_code:
145
- f.write('from manim import *\n\n')
146
- f.write(manim_code)
147
- script_file = f.name
148
-
149
- try:
150
- # 1. Clean up previous renders
151
- if os.path.exists(BASE_DIR):
152
- shutil.rmtree(BASE_DIR, ignore_errors=True)
153
 
154
- # 2. Set up output paths
155
- script_name = os.path.basename(script_file).replace('.py', '')
156
- output_video_path = f"{BASE_DIR}/videos/{script_name}/480p15/{scene_name}.mp4"
157
- os.makedirs(os.path.dirname(output_video_path), exist_ok=True)
158
 
159
- # 3. Construct the Manim command
160
- command = [
161
- "manim",
162
- "-ql", # Low quality for faster rendering
163
- script_file,
164
- scene_name
165
- ]
166
 
 
167
  # Run the command
168
  process = subprocess.run(
169
  command,
170
  check=True,
171
- capture_output=True,
172
- text=True,
173
- cwd=os.path.dirname(script_file)
174
  )
175
  print("Manim STDOUT:", process.stdout)
176
 
177
- # 4. Find the output video (Manim creates it in media/videos/)
178
- default_output = f"media/videos/{script_name}/480p15/{scene_name}.mp4"
179
-
180
  if os.path.exists(default_output):
181
- # Move to our expected location
182
- shutil.move(default_output, output_video_path)
183
- return output_video_path
184
  else:
185
- # Try to find any mp4 file in the media directory
186
- media_dir = "media"
187
- if os.path.exists(media_dir):
188
- for root, dirs, files in os.walk(media_dir):
189
- for file in files:
190
- if file.endswith('.mp4'):
191
- video_path = os.path.join(root, file)
192
- shutil.move(video_path, output_video_path)
193
- return output_video_path
194
-
195
- raise gr.Error("Video was not created. Check Manim output for errors.")
196
 
197
  except subprocess.CalledProcessError as e:
198
- error_msg = e.stderr if e.stderr else str(e)
199
- print("Manim STDERR:", error_msg)
200
- raise gr.Error(f"Manim failed to render. Error: {error_msg}")
201
  except Exception as e:
202
  raise gr.Error(f"Unexpected error: {str(e)}")
203
- finally:
204
- # Clean up temporary file
205
- try:
206
- os.unlink(script_file)
207
- except:
208
- pass
209
- # Clean up media directory
210
- if os.path.exists("media"):
211
- shutil.rmtree("media", ignore_errors=True)
212
 
213
  # --- Gradio UI ---
214
-
215
- # Default example code (Pythagorean Theorem proof)
216
- DEFAULT_CODE = '''from manim import *
217
-
218
- class PythagoreanTheorem(Scene):
219
- """
220
- A Manim scene that visually proves the Pythagorean Theorem.
221
- """
222
- def construct(self):
223
- # Define side lengths for the right triangle
224
- a = 3.0
225
- b = 4.0
226
-
227
- # Colors for visual clarity
228
- tri_color = BLUE
229
- sq_a_color = RED_C
230
- sq_b_color = YELLOW_C
231
- sq_c_color = GREEN_C
232
-
233
- # Set up the initial layout and title
234
- self.camera.background_color = WHITE
235
- Mobject.set_default(color=BLACK)
236
-
237
- title = Title("Visual Proof of the Pythagorean Theorem", color=BLACK)
238
- equation = MathTex("a^2", "+", "b^2", "=", "c^2", color=BLACK).next_to(title, DOWN, buff=0.5)
239
- self.play(Write(title), Write(equation))
240
- self.wait(1)
241
-
242
- self.play(equation.animate.to_corner(UR))
243
-
244
- p1 = np.array([a, 0, 0])
245
- p2 = np.array([a + b, a, 0])
246
- p3 = np.array([b, a + b, 0])
247
- p4 = np.array([0, b, 0])
248
-
249
- c1 = np.array([0, 0, 0])
250
- c2 = np.array([a + b, 0, 0])
251
- c3 = np.array([a + b, a + b, 0])
252
- c4 = np.array([0, a + b, 0])
253
-
254
- T1 = Polygon(c1, p1, p4, color=tri_color, fill_opacity=0.7)
255
- T2 = Polygon(c2, p2, p1, color=tri_color, fill_opacity=0.7)
256
- T3 = Polygon(c3, p3, p2, color=tri_color, fill_opacity=0.7)
257
- T4 = Polygon(c4, p4, p3, color=tri_color, fill_opacity=0.7)
258
- sq_c = Polygon(p1, p2, p3, p4, color=sq_c_color, fill_opacity=0.7)
259
-
260
- arrangement1 = VGroup(T1, T2, T3, T4, sq_c).center()
261
- self.play(Create(arrangement1))
262
-
263
- c_sq_label = MathTex("c^2", color=BLACK).move_to(sq_c.get_center())
264
- self.play(Write(c_sq_label))
265
- self.wait(2)
266
-
267
- T1_target = Polygon(np.array([b, 0, 0]), np.array([a+b, 0, 0]), np.array([b, b, 0]), color=tri_color, fill_opacity=0.7)
268
- T2_target = Polygon(np.array([a+b, 0, 0]), np.array([a+b, b, 0]), np.array([b, b, 0]), color=tri_color, fill_opacity=0.7)
269
- T3_target = Polygon(np.array([0, b, 0]), np.array([b, b, 0]), np.array([0, a+b, 0]), color=tri_color, fill_opacity=0.7)
270
- T4_target = Polygon(np.array([b, b, 0]), np.array([b, a+b, 0]), np.array([0, a+b, 0]), color=tri_color, fill_opacity=0.7)
271
-
272
- self.play(
273
- Transform(T1, T1_target),
274
- Transform(T2, T2_target),
275
- Transform(T3, T3_target),
276
- Transform(T4, T4_target),
277
- FadeOut(sq_c),
278
- FadeOut(c_sq_label)
279
- )
280
- self.wait(1)
281
-
282
- sq_a = Square(side_length=a, color=sq_a_color, fill_opacity=0.7)
283
- sq_b = Square(side_length=b, color=sq_b_color, fill_opacity=0.7)
284
-
285
- sq_a.align_to(T4_target, DOWN).align_to(T1_target, RIGHT)
286
- sq_b.align_to(T1_target, UP).align_to(T4_target, LEFT)
287
-
288
- a_sq_label = MathTex("a^2", color=BLACK).move_to(sq_a.get_center())
289
- b_sq_label = MathTex("b^2", color=BLACK).move_to(sq_b.get_center())
290
-
291
- self.play(Create(sq_a), Create(sq_b), Write(a_sq_label), Write(b_sq_label))
292
- self.wait(2)
293
- self.play(Indicate(equation))
294
- self.wait(3)
295
- '''
296
-
297
- with gr.Blocks(title="Manim Video Renderer", theme=gr.themes.Soft()) as demo:
298
- gr.Markdown("# 🎬 Manim Video Renderer")
299
  gr.Markdown(
300
- "Create beautiful mathematical animations with Manim! Write your scene code below and click render to generate a video."
301
- )
302
-
303
- with gr.Row():
304
- with gr.Column(scale=2):
305
- code_input = gr.Code(
306
- value=DEFAULT_CODE,
307
- label="Manim Scene Code",
308
- language="python",
309
- lines=20,
310
- interactive=True
311
- )
312
-
313
- with gr.Row():
314
- render_button = gr.Button("🎬 Render Video", variant="primary", size="lg")
315
- clear_button = gr.Button("🗑️ Clear", variant="secondary")
316
-
317
- with gr.Column(scale=1):
318
- output_video = gr.Video(
319
- label="Rendered Animation",
320
- interactive=False,
321
- height=400
322
- )
323
-
324
- gr.Markdown("""
325
- ### 💡 Tips:
326
- - Your class must inherit from `Scene`
327
- - The `from manim import *` import will be added automatically
328
- - Use `self.play()` to animate objects
329
- - Use `self.wait()` to add pauses
330
- - Rendering may take 1-2 minutes
331
- """)
332
-
333
- # Event handlers
334
- render_button.click(
335
- fn=render_manim_video,
336
- inputs=[code_input],
337
- outputs=[output_video],
338
- show_progress=True
339
- )
340
-
341
- clear_button.click(
342
- fn=lambda: DEFAULT_CODE,
343
- inputs=[],
344
- outputs=[code_input]
345
  )
346
 
347
  # --- Launch ---
348
- if __name__ == "__main__":
349
- # Setup environment before launching
350
- setup_environment()
351
- demo.launch(
352
- server_name="0.0.0.0",
353
- server_port=7860,
354
- share=True,
355
- show_error=True,
356
- quiet=True
357
- )
 
 
 
 
 
 
 
 
 
 
 
 
1
  # --- Configuration ---
2
+ MANIM_SCRIPT_FILE = "proof.py"
3
+ MANIM_SCENE_NAME = "PythagoreanTheorem"
4
+ BASE_DIR = "/tmp/media"
5
+ OUTPUT_VIDEO_PATH = f"{BASE_DIR}/videos/proof/480p15/{MANIM_SCENE_NAME}.mp4"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
+ def render_manim_video():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  """
9
+ Runs the Manim command and returns the path to the rendered video.
10
+
11
  """
12
+ # 1. Clean up previous renders
13
+ if os.path.exists(BASE_DIR):
14
+ shutil.rmtree(BASE_DIR, ignore_errors=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ # 2. Make sure the output path exists
17
+ os.makedirs(os.path.dirname(OUTPUT_VIDEO_PATH), exist_ok=True)
 
 
18
 
19
+ # 3. Construct the Manim command
20
+ command = [
21
+ "manim",
22
+ "-ql",
23
+ ]
 
 
24
 
25
+ try:
26
  # Run the command
27
  process = subprocess.run(
28
  command,
29
  check=True,
 
 
 
30
  )
31
  print("Manim STDOUT:", process.stdout)
32
 
33
+ # 4. Move output video from default location to BASE_DIR
34
+ default_output = f"media/videos/{MANIM_SCRIPT_FILE.replace('.py', '')}/480p15/{MANIM_SCENE_NAME}.mp4"
 
35
  if os.path.exists(default_output):
36
+ shutil.move(default_output, OUTPUT_VIDEO_PATH)
37
+ return OUTPUT_VIDEO_PATH
 
38
  else:
39
+ raise gr.Error("Video was not created. Check Manim output.")
 
 
 
 
 
 
 
 
 
 
40
 
41
  except subprocess.CalledProcessError as e:
42
+
43
+ print("Manim STDERR:", e.stderr)
44
+ raise gr.Error(f"Manim failed to render. Error: {e.stderr}")
45
  except Exception as e:
46
  raise gr.Error(f"Unexpected error: {str(e)}")
 
 
 
 
 
 
 
 
 
47
 
48
  # --- Gradio UI ---
49
+ with gr.Blocks() as demo:
50
+ gr.Markdown("# Interactive Manim: Pythagorean Theorem Proof")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  gr.Markdown(
52
+ outputs=[output_video]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  )
54
 
55
  # --- Launch ---
56
+ demo.launch(server_name="0.0.0.0", share=True)