raksa-the-wildcats commited on
Commit
7b49b17
Β·
1 Parent(s): ca309c8

Add application file

Browse files
Files changed (4) hide show
  1. Dockerfile +34 -0
  2. app.py +424 -0
  3. packages.txt +5 -0
  4. requirements.txt +5 -0
Dockerfile ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Install system dependencies required for Manim
4
+ RUN apt-get update && apt-get install -y \
5
+ libcairo2-dev \
6
+ libpango1.0-dev \
7
+ libgirepository1.0-dev \
8
+ libxml2-dev \
9
+ libxslt1-dev \
10
+ libffi-dev \
11
+ libssl-dev \
12
+ pkg-config \
13
+ build-essential \
14
+ ffmpeg \
15
+ git \
16
+ && rm -rf /var/lib/apt/lists/*
17
+
18
+ # Set working directory
19
+ WORKDIR /app
20
+
21
+ # Copy requirements first for better caching
22
+ COPY requirements.txt .
23
+
24
+ # Install Python dependencies
25
+ RUN pip install --no-cache-dir -r requirements.txt
26
+
27
+ # Copy application code
28
+ COPY . .
29
+
30
+ # Expose port
31
+ EXPOSE 7860
32
+
33
+ # Run the application
34
+ CMD ["python", "paste.txt"]
app.py ADDED
@@ -0,0 +1,424 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import subprocess
3
+ import os
4
+ import tempfile
5
+ import shutil
6
+ from pathlib import Path
7
+ import time
8
+ import sys
9
+
10
+ # Check if Manim is available
11
+ try:
12
+ import manim
13
+ MANIM_AVAILABLE = True
14
+ except ImportError:
15
+ MANIM_AVAILABLE = False
16
+
17
+ class ManimAnimationGenerator:
18
+ def __init__(self):
19
+ self.temp_dir = None
20
+ self.output_dir = None
21
+
22
+ def setup_directories(self):
23
+ """Setup temporary directories for Manim execution"""
24
+ self.temp_dir = tempfile.mkdtemp()
25
+ self.output_dir = os.path.join(self.temp_dir, "media", "videos", "480p15")
26
+ os.makedirs(self.output_dir, exist_ok=True)
27
+ return self.temp_dir
28
+
29
+ def cleanup_directories(self):
30
+ """Clean up temporary directories"""
31
+ if self.temp_dir and os.path.exists(self.temp_dir):
32
+ shutil.rmtree(self.temp_dir)
33
+ self.temp_dir = None
34
+ self.output_dir = None
35
+
36
+ def validate_manim_code(self, code):
37
+ """Basic validation of Manim code"""
38
+ required_imports = ["from manim import *", "import manim"]
39
+ has_import = any(imp in code for imp in required_imports)
40
+
41
+ if not has_import:
42
+ return False, "Code must include 'from manim import *' or 'import manim'"
43
+
44
+ if "class" not in code:
45
+ return False, "Code must contain at least one class definition"
46
+
47
+ if "Scene" not in code:
48
+ return False, "Class must inherit from Scene or a Scene subclass"
49
+
50
+ return True, "Code validation passed"
51
+
52
+ def install_system_dependencies(self):
53
+ """Install required system dependencies"""
54
+ try:
55
+ # Install system dependencies for Manim
56
+ subprocess.check_call([
57
+ "apt-get", "update"
58
+ ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
59
+
60
+ subprocess.check_call([
61
+ "apt-get", "install", "-y",
62
+ "libcairo2-dev", "libpango1.0-dev", "libgirepository1.0-dev",
63
+ "libxml2-dev", "libxslt1-dev", "libffi-dev", "libssl-dev",
64
+ "pkg-config", "build-essential"
65
+ ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
66
+
67
+ return True, "System dependencies installed"
68
+ except subprocess.CalledProcessError as e:
69
+ return False, f"Failed to install system dependencies: {str(e)}"
70
+ except Exception as e:
71
+ return False, f"Error installing dependencies: {str(e)}"
72
+
73
+ def install_manim(self):
74
+ """Try to install Manim if not available"""
75
+ try:
76
+ # Try to install Manim first
77
+ subprocess.check_call([
78
+ sys.executable, "-m", "pip", "install",
79
+ "manim", "--quiet"
80
+ ])
81
+
82
+ # Try to install manimpango separately if needed
83
+ try:
84
+ subprocess.check_call([
85
+ sys.executable, "-m", "pip", "install",
86
+ "manimpango", "--quiet"
87
+ ])
88
+ except subprocess.CalledProcessError:
89
+ # manimpango might fail, but manim might still work
90
+ pass
91
+
92
+ return True, "Manim installed successfully"
93
+ except subprocess.CalledProcessError as e:
94
+ return False, f"Failed to install Manim: {str(e)}"
95
+
96
+ def execute_manim_code(self, code, quality="low", format_type="gif"):
97
+ """Execute Manim code and return the generated animation"""
98
+ global MANIM_AVAILABLE
99
+
100
+ # Check if Manim is available, try to install if not
101
+ if not MANIM_AVAILABLE:
102
+ success, message = self.install_manim()
103
+ if not success:
104
+ return None, f"❌ Manim Installation Error: {message}", ""
105
+ MANIM_AVAILABLE = True
106
+
107
+ try:
108
+ # Validate code
109
+ is_valid, message = self.validate_manim_code(code)
110
+ if not is_valid:
111
+ return None, f"❌ Validation Error: {message}", ""
112
+
113
+ # Setup directories
114
+ temp_dir = self.setup_directories()
115
+
116
+ # Create Python file
117
+ python_file = os.path.join(temp_dir, "animation.py")
118
+ with open(python_file, "w") as f:
119
+ f.write(code)
120
+
121
+ # Extract class name for Manim command
122
+ class_name = self.extract_class_name(code)
123
+ if not class_name:
124
+ self.cleanup_directories()
125
+ return None, "❌ Error: Could not find a valid Scene class in the code", ""
126
+
127
+ # Quality settings
128
+ quality_map = {
129
+ "low": "-ql",
130
+ "medium": "-qm",
131
+ "high": "-qh"
132
+ }
133
+ quality_flag = quality_map.get(quality, "-ql")
134
+
135
+ # Format settings
136
+ format_flag = "--format=gif" if format_type == "gif" else ""
137
+
138
+ # Build Manim command
139
+ cmd = [
140
+ sys.executable, "-m", "manim",
141
+ quality_flag,
142
+ python_file,
143
+ class_name
144
+ ]
145
+
146
+ if format_flag:
147
+ cmd.append(format_flag)
148
+
149
+ # Set environment variables to help with rendering
150
+ env = os.environ.copy()
151
+ env['MPLBACKEND'] = 'Agg' # Use non-interactive backend
152
+
153
+ # Execute Manim
154
+ result = subprocess.run(
155
+ cmd,
156
+ cwd=temp_dir,
157
+ capture_output=True,
158
+ text=True,
159
+ timeout=120, # 2 minute timeout
160
+ env=env
161
+ )
162
+
163
+ if result.returncode != 0:
164
+ error_msg = f"❌ Manim execution failed:\n{result.stderr}"
165
+ self.cleanup_directories()
166
+ return None, error_msg, result.stdout
167
+
168
+ # Find generated file
169
+ output_file = self.find_output_file(temp_dir, class_name, format_type)
170
+ if not output_file:
171
+ self.cleanup_directories()
172
+ return None, "❌ Error: Could not find generated animation file", result.stdout
173
+
174
+ # Copy to a permanent location for Gradio
175
+ permanent_file = f"/tmp/{class_name}_{int(time.time())}.{format_type}"
176
+ shutil.copy2(output_file, permanent_file)
177
+
178
+ success_msg = f"βœ… Animation generated successfully!\nClass: {class_name}\nQuality: {quality}\nFormat: {format_type}"
179
+
180
+ self.cleanup_directories()
181
+ return permanent_file, success_msg, result.stdout
182
+
183
+ except subprocess.TimeoutExpired:
184
+ self.cleanup_directories()
185
+ return None, "❌ Error: Animation generation timed out (2 minutes)", ""
186
+ except Exception as e:
187
+ self.cleanup_directories()
188
+ return None, f"❌ Error: {str(e)}", ""
189
+
190
+ def extract_class_name(self, code):
191
+ """Extract the first Scene class name from the code"""
192
+ lines = code.split('\n')
193
+ for line in lines:
194
+ if line.strip().startswith('class ') and 'Scene' in line:
195
+ # Extract class name
196
+ class_def = line.strip().split('class ')[1].split('(')[0].strip()
197
+ return class_def
198
+ return None
199
+
200
+ def find_output_file(self, temp_dir, class_name, format_type):
201
+ """Find the generated output file"""
202
+ # Search recursively for the output file
203
+ for root, dirs, files in os.walk(temp_dir):
204
+ for file in files:
205
+ if file.startswith(class_name) and file.endswith(f".{format_type}"):
206
+ return os.path.join(root, file)
207
+
208
+ return None
209
+
210
+ # Initialize the generator
211
+ generator = ManimAnimationGenerator()
212
+
213
+ # Example Manim codes for users
214
+ example_codes = {
215
+ "Simple Square": '''from manim import *
216
+
217
+ class CreateSquare(Scene):
218
+ def construct(self):
219
+ square = Square()
220
+ square.set_fill(BLUE, opacity=0.5)
221
+ square.set_stroke(WHITE, width=2)
222
+
223
+ self.play(Create(square))
224
+ self.play(square.animate.rotate(PI/4))
225
+ self.wait()''',
226
+
227
+ "Moving Circle": '''from manim import *
228
+
229
+ class MovingCircle(Scene):
230
+ def construct(self):
231
+ circle = Circle()
232
+ circle.set_fill(RED, opacity=0.5)
233
+
234
+ self.play(Create(circle))
235
+ self.play(circle.animate.shift(RIGHT * 2))
236
+ self.play(circle.animate.shift(UP * 2))
237
+ self.play(circle.animate.shift(LEFT * 2))
238
+ self.play(circle.animate.shift(DOWN * 2))
239
+ self.wait()''',
240
+
241
+ "Text Animation": '''from manim import *
242
+
243
+ class TextAnimation(Scene):
244
+ def construct(self):
245
+ text = Text("Hello Manim!", font_size=48)
246
+ text2 = Text("Animated Math!", font_size=48)
247
+
248
+ self.play(Write(text))
249
+ self.wait()
250
+ self.play(Transform(text, text2))
251
+ self.wait()''',
252
+
253
+ "Mathematical Formula": '''from manim import *
254
+
255
+ class MathFormula(Scene):
256
+ def construct(self):
257
+ formula = MathTex(r"\\frac{d}{dx}(x^2) = 2x")
258
+ formula.scale(2)
259
+
260
+ self.play(Write(formula))
261
+ self.wait()
262
+
263
+ formula2 = MathTex(r"\\int_0^1 x^2 dx = \\frac{1}{3}")
264
+ formula2.scale(2)
265
+
266
+ self.play(Transform(formula, formula2))
267
+ self.wait()'''
268
+ }
269
+
270
+ def check_manim_installation():
271
+ """Check if Manim is properly installed"""
272
+ try:
273
+ result = subprocess.run([
274
+ sys.executable, "-c", "import manim; print('Manim version:', manim.__version__)"
275
+ ], capture_output=True, text=True, timeout=10)
276
+
277
+ if result.returncode == 0:
278
+ return True, f"βœ… Manim is available: {result.stdout.strip()}"
279
+ else:
280
+ return False, f"❌ Manim import failed: {result.stderr}"
281
+ except Exception as e:
282
+ return False, f"❌ Error checking Manim: {str(e)}"
283
+
284
+ def generate_animation(code, quality, format_type, progress=gr.Progress()):
285
+ """Main function to generate animation"""
286
+ progress(0, desc="Starting animation generation...")
287
+
288
+ if not code.strip():
289
+ return None, "❌ Please enter some Manim code", ""
290
+
291
+ progress(0.2, desc="Checking Manim installation...")
292
+ manim_ok, manim_status = check_manim_installation()
293
+
294
+ progress(0.3, desc="Validating code...")
295
+ result = generator.execute_manim_code(code, quality, format_type)
296
+
297
+ progress(0.7, desc="Generating animation...")
298
+
299
+ if result[0]: # Success
300
+ progress(1.0, desc="Animation generated successfully!")
301
+ return result[0], result[1], result[2]
302
+ else: # Error
303
+ return None, result[1], result[2]
304
+
305
+ def load_example(example_name):
306
+ """Load example code"""
307
+ return example_codes.get(example_name, "")
308
+
309
+ # Create Gradio interface
310
+ with gr.Blocks(title="Manim Animation Generator", theme=gr.themes.Soft()) as app:
311
+ gr.Markdown("""
312
+ # 🎬 Manim Animation Generator
313
+
314
+ Create beautiful mathematical animations using [Manim](https://www.manim.community/)!
315
+ Enter your Python code below and watch it come to life.
316
+
317
+ ## πŸ“ How to use:
318
+ 1. Write or select example Manim code
319
+ 2. Choose quality and format settings
320
+ 3. Click "Generate Animation"
321
+ 4. Wait for your animation to render
322
+
323
+ ## ⚠️ Note:
324
+ This app will automatically install Manim if it's not available. First run may take longer.
325
+
326
+ ## πŸ’‘ Tips:
327
+ - Your code must include `from manim import *`
328
+ - Create a class that inherits from `Scene`
329
+ - Use the `construct` method to define your animation
330
+ """)
331
+
332
+ # System status
333
+ manim_available, status_msg = check_manim_installation()
334
+ gr.Markdown(f"**System Status:** {status_msg}")
335
+
336
+ with gr.Row():
337
+ with gr.Column(scale=2):
338
+ code_input = gr.Code(
339
+ label="Manim Code",
340
+ language="python",
341
+ lines=20,
342
+ value=example_codes["Simple Square"]
343
+ )
344
+
345
+ with gr.Row():
346
+ quality = gr.Dropdown(
347
+ choices=["low", "medium", "high"],
348
+ value="low",
349
+ label="Quality"
350
+ )
351
+ format_type = gr.Dropdown(
352
+ choices=["gif", "mp4"],
353
+ value="gif",
354
+ label="Format"
355
+ )
356
+
357
+ generate_btn = gr.Button("🎬 Generate Animation", variant="primary", size="lg")
358
+
359
+ gr.Markdown("### πŸ“š Example Codes:")
360
+ example_dropdown = gr.Dropdown(
361
+ choices=list(example_codes.keys()),
362
+ label="Load Example",
363
+ value="Simple Square"
364
+ )
365
+ load_example_btn = gr.Button("πŸ“‚ Load Example")
366
+
367
+ with gr.Column(scale=2):
368
+ output_video = gr.File(label="Generated Animation", file_types=[".gif", ".mp4"])
369
+ status_output = gr.Textbox(label="Status", lines=5, max_lines=10)
370
+ logs_output = gr.Textbox(label="Manim Logs", lines=8, max_lines=15, visible=False)
371
+
372
+ with gr.Row():
373
+ show_logs_btn = gr.Button("Show Logs", size="sm")
374
+ hide_logs_btn = gr.Button("Hide Logs", size="sm")
375
+
376
+ # Event handlers
377
+ generate_btn.click(
378
+ fn=generate_animation,
379
+ inputs=[code_input, quality, format_type],
380
+ outputs=[output_video, status_output, logs_output]
381
+ )
382
+
383
+ load_example_btn.click(
384
+ fn=load_example,
385
+ inputs=[example_dropdown],
386
+ outputs=[code_input]
387
+ )
388
+
389
+ example_dropdown.change(
390
+ fn=load_example,
391
+ inputs=[example_dropdown],
392
+ outputs=[code_input]
393
+ )
394
+
395
+ show_logs_btn.click(
396
+ fn=lambda: gr.update(visible=True),
397
+ outputs=[logs_output]
398
+ )
399
+
400
+ hide_logs_btn.click(
401
+ fn=lambda: gr.update(visible=False),
402
+ outputs=[logs_output]
403
+ )
404
+
405
+ gr.Markdown("""
406
+ ## πŸ”§ Troubleshooting:
407
+ - **Installation errors**: The app will try to install Manim automatically
408
+ - **Timeout errors**: Try simpler animations or lower quality
409
+ - **Import errors**: Make sure to include `from manim import *`
410
+ - **Class errors**: Your class must inherit from `Scene`
411
+ - **No output**: Check that your `construct` method has animation commands
412
+
413
+ ## 🎯 Common Manim Objects:
414
+ - **Shapes**: `Circle()`, `Square()`, `Triangle()`, `Rectangle()`
415
+ - **Text**: `Text("Hello")`, `MathTex(r"x^2")`
416
+ - **Animations**: `Create()`, `Write()`, `Transform()`, `FadeIn()`, `FadeOut()`
417
+ - **Colors**: `RED`, `BLUE`, `GREEN`, `YELLOW`, `WHITE`, `BLACK`
418
+
419
+ ---
420
+ Made with ❀️ using [Manim](https://www.manim.community/) and [Gradio](https://gradio.app/)
421
+ """)
422
+
423
+ if __name__ == "__main__":
424
+ app.launch(debug=True, share=True)
packages.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ libcairo2-dev
2
+ libpango1.0-dev
3
+ libgirepository1.0-dev
4
+ pkg-config
5
+ build-essential
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ manim
3
+ numpy
4
+ matplotlib
5
+ Pillow