parthraninga commited on
Commit
41554b8
·
verified ·
1 Parent(s): 8e90f07

Update app.py

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