Chand11 commited on
Commit
78f2ffa
·
verified ·
1 Parent(s): 017cbcc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +135 -328
app.py CHANGED
@@ -1,374 +1,181 @@
1
- import gradio as gr
2
  import subprocess
3
- import os
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
- # 9. CRITICAL FIX: Add color=BLACK to Text objects that don't have color specified
74
- # This prevents invisible white text on white background
75
- # Match Text(...) that doesn't have color= parameter
76
- def add_black_color(match):
77
- text_content = match.group(0)
78
- # Check if color parameter already exists
79
- if 'color=' not in text_content:
80
- # Find the closing parenthesis of Text()
81
- # Add color=BLACK before the last closing paren
82
- if ', font_size=' in text_content:
83
- # Insert after font_size
84
- text_content = text_content.replace(')', ', color=BLACK)', 1)
85
- else:
86
- # Insert before closing paren
87
- text_content = text_content[:-1] + ', color=BLACK)'
88
- return text_content
89
-
90
- # Apply to all Text() objects
91
- code = re.sub(r'Text\([^)]+\)', add_black_color, code)
92
 
93
  return code
94
 
95
- def generate_code_with_gemini(prompt):
96
- """Generate Manim code using Gemini API"""
97
-
98
- if not GENAI_AVAILABLE:
99
- return None, "❌ google-generativeai package not installed"
100
-
101
  if not GEMINI_API_KEY:
102
- return None, "❌ GEMINI_API_KEY not set in environment variables"
103
 
104
  try:
105
  genai.configure(api_key=GEMINI_API_KEY)
106
- models = [m.name for m in genai.list_models() if 'generateContent' in m.supported_generation_methods]
107
- model_name = next((m for m in models if 'flash' in m.lower()), models[0])
108
-
109
- model = genai.GenerativeModel(model_name)
110
 
111
- full_prompt = f"""You are a Manim code generator. Generate WORKING Manim animation code.
112
 
113
- USER REQUEST: {prompt}
114
-
115
- CRITICAL RULES YOU MUST FOLLOW:
116
-
117
- 1. ALWAYS start with: from manim import *
118
-
119
- 2. Class structure MUST be:
120
- ```python
121
- from manim import *
122
-
123
- class MyScene(Scene):
124
- def construct(self):
125
- # Your animation code here
126
- # MUST have at least one self.play() or self.add()
127
- ```
128
-
129
- 3. COLORS - ONLY use these 10: BLUE, RED, GREEN, YELLOW, ORANGE, PURPLE, PINK, TEAL, WHITE, BLACK
130
- - NO color variants like BLUE_A, RED_B, etc.
131
-
132
- 4. TEXT RULES:
133
- - Use: Text("message", font_size=36, color=BLACK)
134
- - ALWAYS FadeOut text before creating new text in same position
135
- - Example:
136
- ```python
137
- text1 = Text("First", font_size=36, color=BLACK)
138
- self.play(Write(text1))
139
- self.wait(1)
140
- self.play(FadeOut(text1)) # Must FadeOut!
141
-
142
- text2 = Text("Second", font_size=36, color=BLACK)
143
- self.play(Write(text2))
144
  self.wait(1)
145
- self.play(FadeOut(text2))
146
- ```
147
-
148
- 5. SHAPES:
149
- - Circle: Circle(radius=1, color=BLUE)
150
- - Square: Square(side_length=2, color=RED)
151
- - Rectangle: Rectangle(width=3, height=2, color=GREEN)
152
- - Line: Line(start=LEFT, end=RIGHT, color=YELLOW)
153
-
154
- 6. POSITIONING:
155
- - Use .shift(UP), .shift(DOWN), .shift(LEFT), .shift(RIGHT)
156
- - Use .to_edge(UP), .to_edge(DOWN), .to_edge(LEFT), .to_edge(RIGHT)
157
- - Use .move_to(ORIGIN)
158
 
159
- 7. ANIMATIONS:
160
- - Show objects: Create(), Write(), FadeIn()
161
- - Hide objects: FadeOut(), Uncreate()
162
- - Transform: Transform(obj1, obj2)
163
- - Move: obj.animate.shift(UP)
164
 
165
- 8. NEVER USE:
166
- - get_x_axis_label(), get_y_axis_label()
167
- - SVGMobject, ImageMobject
168
- - Checkmark, XMark
169
- - .add_prefix(), .add_suffix()
170
-
171
- 9. SIMPLE AND WORKING:
172
- - Keep it simple
173
- - Test each animation
174
- - Make sure there's visible content
175
- - Animation should be 5-10 seconds long
176
-
177
- Generate COMPLETE, WORKING code that creates a visible animation for: {prompt}
178
-
179
- The code must actually show something on screen!"""
180
 
181
  response = model.generate_content(full_prompt)
182
- code = response.text
183
-
184
- return code, None
185
-
186
  except Exception as e:
187
- return None, f"❌ Gemini API error: {str(e)}"
188
 
189
- def render_video(prompt, quality="low"):
190
- """Main function to generate and render Manim video"""
191
-
192
- if not prompt or not prompt.strip():
193
- yield "❌ Please enter a prompt!", None, None
194
- return
195
 
196
- try:
197
- # Create temp directory
198
- temp_dir = Path(tempfile.mkdtemp(prefix="manim_"))
199
-
200
- yield "🤖 Generating code with Gemini AI...", None, None
201
-
202
- # Generate code
203
- code, error = generate_code_with_gemini(prompt)
204
 
205
  if error:
206
- yield error, None, None
207
- return
208
-
209
- if not code:
210
- yield "❌ Failed to generate code", None, None
211
- return
212
 
213
- # Extract Python code if wrapped in markdown
214
  if "```python" in code:
215
  code = code.split("```python")[1].split("```")[0]
216
  elif "```" in code:
217
  code = code.split("```")[1].split("```")[0]
218
  code = code.strip()
219
 
220
- # Apply fixes
221
- code = fix_all_known_errors(code)
222
-
223
- # Find Scene class
224
- match = re.search(r'class\s+(\w+)\s*\(Scene\)', code)
225
- if not match:
226
- yield "❌ No Scene class found in generated code!", code, None
227
- return
228
-
229
- class_name = match.group(1)
230
-
231
- # Save code to file
232
- code_file = temp_dir / "animation.py"
233
- with open(code_file, 'w', encoding='utf-8') as f:
234
- f.write(code)
235
-
236
- yield f"✓ Code generated (Scene: {class_name})\n🎬 Rendering video (this may take 1-2 minutes)...", code, None
237
-
238
- # Render video with absolute paths
239
- quality_map = {'low': '-ql', 'medium': '-qm', 'high': '-qh'}
240
- quality_flag = quality_map.get(quality, '-ql')
241
-
242
- abs_code_file = str(code_file.absolute())
243
- media_dir = str((temp_dir / "media").absolute())
244
-
245
- command = f"manim {abs_code_file} {class_name} {quality_flag} --disable_caching --media_dir {media_dir}"
246
-
247
- process = subprocess.Popen(
248
- command, shell=True,
249
- stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
250
- text=True
251
- )
252
-
253
- output_lines = []
254
- has_error = False
255
- for line in process.stdout:
256
- output_lines.append(line)
257
- # Check for common errors in output
258
- if any(err in line.lower() for err in ['error', 'exception', 'traceback', 'failed']):
259
- has_error = True
260
-
261
- process.wait()
262
-
263
- # Get full output for debugging
264
- full_output = ''.join(output_lines)
265
 
266
- if process.returncode == 0:
267
- # Find the generated video
268
- media_path = temp_dir / "media"
269
- video_files = list(media_path.rglob("*.mp4"))
270
-
271
- if video_files:
272
- video_path = str(video_files[0])
273
- # Check if video file is too small (blank video indicator)
274
- video_size = os.path.getsize(video_path)
275
-
276
- if video_size < 5000: # Less than 5KB means blank/error
277
- yield f"⚠️ Video rendered but seems blank (only {video_size} bytes)!\n\nMaybe there's an error in the code. Check the generated code below.\n\nRender output (last 40 lines):\n{chr(10).join(output_lines[-40:])}", code, None
278
- elif has_error:
279
- yield f"⚠️ Video rendered but errors detected!\n\nRender output (last 40 lines):\n{chr(10).join(output_lines[-40:])}", code, video_path
280
- else:
281
- yield f"✅ Video rendered successfully! 🎉", code, video_path
282
  else:
283
- yield "❌ Video file not found after rendering", code, None
 
 
284
  else:
285
- error_msg = ''.join(output_lines[-40:]) # Last 40 lines
286
- yield f"❌ Rendering failed:\n\n{error_msg}", code, None
287
-
288
- # Cleanup
289
- try:
290
- shutil.rmtree(temp_dir)
291
- except:
292
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
 
294
- except Exception as e:
295
- import traceback
296
- error_details = traceback.format_exc()
297
- yield f"❌ Error: {str(e)}\n\nDetails:\n{error_details}", None, None
298
-
299
- # Gradio Interface
300
- def create_interface():
301
- with gr.Blocks(title="Manim Video Generator") as demo:
302
- gr.Markdown("""
303
- # 🎬 Manim Video Generator
304
- Create mathematical animations using AI! Describe what you want to see animated.
305
-
306
- **⚠️ IMPORTANT: You need to set GEMINI_API_KEY in Space Settings → Variables and secrets**
307
-
308
- Get your free Gemini API key from: https://aistudio.google.com/app/apikey
309
-
310
- **Examples:**
311
- - "Explain Pythagorean theorem with animation"
312
- - "Show a sine wave transforming into a cosine wave"
313
- - "Animate a circle morphing into a square"
314
- - "Show the concept of derivatives with a tangent line"
315
- """)
316
-
317
- with gr.Row():
318
- with gr.Column():
319
- prompt_input = gr.Textbox(
320
- label="What animation do you want?",
321
- placeholder="e.g., Show a circle morphing into a square",
322
- lines=4
323
- )
324
- quality_input = gr.Radio(
325
- choices=["low", "medium", "high"],
326
- value="low",
327
- label="Quality (low is faster, recommended)"
328
  )
329
- generate_btn = gr.Button("🎬 Generate Video", variant="primary", size="lg")
330
-
331
- with gr.Column():
332
- status_output = gr.Textbox(label="Status", lines=4)
333
- video_output = gr.Video(label="Generated Animation")
334
-
335
- code_output = gr.Code(label="Generated Manim Code", language="python", lines=15)
336
-
337
- gr.Markdown("""
338
- ### 💡 Tips:
339
- - Be specific in your description
340
- - Start with simple prompts to test
341
- - Low quality renders faster (30 seconds to 1 minute)
342
- - Medium/High quality may take 2-3 minutes
343
- - Complex animations may timeout on free tier
344
-
345
- ### 🔧 Setup Required:
346
- 1. Get a free Gemini API key from: https://aistudio.google.com/app/apikey
347
- 2. In your Space, go to Settings → Variables and secrets
348
- 3. Add: Name: `GEMINI_API_KEY`, Value: `your-api-key-here`
349
- 4. Restart the Space
350
- """)
351
-
352
- generate_btn.click(
353
- fn=render_video,
354
- inputs=[prompt_input, quality_input],
355
- outputs=[status_output, code_output, video_output]
356
- )
357
-
358
- # Examples
359
- gr.Examples(
360
- examples=[
361
- ["Show a blue circle morphing into a red square", "low"],
362
- ["Animate the Pythagorean theorem: a² + b² = c²", "low"],
363
- ["Show a sine wave moving across the screen", "low"],
364
- ["Create an animation showing 3 dots forming a triangle", "low"],
365
- ["Show a number counting from 0 to 10", "low"],
366
- ],
367
- inputs=[prompt_input, quality_input]
368
- )
369
-
370
- return demo
371
-
372
- if __name__ == "__main__":
373
- demo = create_interface()
374
- demo.launch()
 
1
+ import streamlit as st
2
  import subprocess
3
+ import tempfile
4
  import re
 
5
  from pathlib import Path
6
+ import google.generativeai as genai
7
+ import os
 
 
 
 
 
 
 
8
 
 
9
  GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
10
 
11
+ st.set_page_config(page_title="Manim Video Generator", page_icon="🎬")
12
+
13
+ def fix_code(code):
14
+ """Fix common Manim errors"""
15
  color_map = {
16
+ 'ORANGE_A': 'ORANGE', 'GRAY_A': 'WHITE', 'GREY': 'WHITE',
17
+ 'PURPLE_A': 'PURPLE', 'RED_A': 'RED', 'BLUE_A': 'BLUE',
18
+ 'BROWN': 'ORANGE', 'GOLD': 'YELLOW'
 
 
 
 
 
 
 
19
  }
20
  for old, new in color_map.items():
21
  code = re.sub(rf'\b{old}\b', new, code)
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  code = re.sub(r'\.get_x_axis_label\([^)]+\)', '', code)
24
  code = re.sub(r'\.get_y_axis_label\([^)]+\)', '', code)
25
+ code = re.sub(r'Checkmark\([^)]*\)', 'Circle(radius=0.3, color=GREEN)', code)
 
26
  code = re.sub(r'font_size=(\d+)', lambda m: f'font_size={min(int(m.group(1)), 36)}', code)
27
 
28
+ # Add color=BLACK to Text without color
29
+ def add_color(match):
30
+ text = match.group(0)
31
+ if 'color=' not in text:
32
+ text = text[:-1] + ', color=BLACK)'
33
+ return text
34
+ code = re.sub(r'Text\([^)]+\)', add_color, code)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  return code
37
 
38
+ def generate_code(prompt):
39
+ """Generate Manim code using Gemini"""
 
 
 
 
40
  if not GEMINI_API_KEY:
41
+ return None, "❌ GEMINI_API_KEY not set in secrets"
42
 
43
  try:
44
  genai.configure(api_key=GEMINI_API_KEY)
45
+ model = genai.GenerativeModel('gemini-1.5-flash')
 
 
 
46
 
47
+ full_prompt = f"""Generate simple Manim code.
48
 
49
+ RULES:
50
+ 1. Use ONLY: BLUE, RED, GREEN, YELLOW, ORANGE, PURPLE, PINK, TEAL, WHITE, BLACK
51
+ 2. Background is WHITE - ALL Text MUST have color=BLACK
52
+ 3. Always FadeOut before new text:
53
+ text = Text("Hello", color=BLACK)
54
+ self.play(Write(text))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  self.wait(1)
56
+ self.play(FadeOut(text))
57
+ 4. Keep simple, 10-15 seconds max
58
+ 5. MUST include: from manim import *
 
 
 
 
 
 
 
 
 
 
59
 
60
+ User request: {prompt}
 
 
 
 
61
 
62
+ Generate complete working code:"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  response = model.generate_content(full_prompt)
65
+ return response.text, None
 
 
 
66
  except Exception as e:
67
+ return None, str(e)
68
 
69
+ def render_video(prompt):
70
+ """Render Manim video"""
71
+ temp_dir = Path(tempfile.mkdtemp())
 
 
 
72
 
73
+ # Generate code
74
+ with st.spinner("🤖 Generating code..."):
75
+ code, error = generate_code(prompt)
 
 
 
 
 
76
 
77
  if error:
78
+ st.error(error)
79
+ return None, None
 
 
 
 
80
 
81
+ # Extract Python code
82
  if "```python" in code:
83
  code = code.split("```python")[1].split("```")[0]
84
  elif "```" in code:
85
  code = code.split("```")[1].split("```")[0]
86
  code = code.strip()
87
 
88
+ # Fix code
89
+ code = fix_code(code)
90
+
91
+ # Find Scene class
92
+ match = re.search(r'class\s+(\w+)\s*\(Scene\)', code)
93
+ if not match:
94
+ st.error("No Scene class found!")
95
+ return None, code
96
+
97
+ class_name = match.group(1)
98
+
99
+ # Save code
100
+ code_file = temp_dir / "animation.py"
101
+ code_file.write_text(code)
102
+
103
+ # Render video
104
+ with st.spinner(f"🎬 Rendering {class_name}... (this takes 1-2 minutes)"):
105
+ cmd = f"manim {code_file} {class_name} -ql --disable_caching --media_dir {temp_dir}/media"
106
+ result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=180)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
+ if result.returncode == 0:
109
+ # Find video
110
+ videos = list((temp_dir / "media").rglob("*.mp4"))
111
+ if videos:
112
+ return videos[0], code
 
 
 
 
 
 
 
 
 
 
 
113
  else:
114
+ st.error("Video file not found after rendering")
115
+ st.code(result.stdout, language="text")
116
+ return None, code
117
  else:
118
+ st.error("Rendering failed!")
119
+ st.code(result.stderr, language="text")
120
+ return None, code
121
+
122
+ # UI
123
+ st.title("🎬 Manim Video Generator")
124
+ st.markdown("Create mathematical animations using AI!")
125
+
126
+ if not GEMINI_API_KEY:
127
+ st.error("⚠️ GEMINI_API_KEY not set! Add it in Settings → Secrets")
128
+ st.markdown("Get your free API key: https://aistudio.google.com/app/apikey")
129
+ st.stop()
130
+
131
+ # Input
132
+ prompt = st.text_area(
133
+ "What animation do you want?",
134
+ placeholder="e.g., Show a blue circle morphing into a red square",
135
+ height=100
136
+ )
137
+
138
+ # Examples
139
+ st.markdown("**Examples:**")
140
+ col1, col2, col3 = st.columns(3)
141
+ with col1:
142
+ if st.button("🔵 Circle → Square"):
143
+ prompt = "Show a blue circle morphing into a red square"
144
+ with col2:
145
+ if st.button("📐 Pythagorean"):
146
+ prompt = "Animate the Pythagorean theorem: a² + b² = c²"
147
+ with col3:
148
+ if st.button("〰️ Sine Wave"):
149
+ prompt = "Show a sine wave moving"
150
+
151
+ # Generate button
152
+ if st.button("🎬 Generate Video", type="primary", use_container_width=True):
153
+ if not prompt:
154
+ st.warning("Please enter a prompt!")
155
+ else:
156
+ video_path, code = render_video(prompt)
157
+
158
+ if video_path:
159
+ st.success("✅ Video rendered successfully!")
160
 
161
+ # Display video
162
+ st.video(str(video_path))
163
+
164
+ # Download button
165
+ with open(video_path, 'rb') as f:
166
+ st.download_button(
167
+ label="📥 Download Video",
168
+ data=f.read(),
169
+ file_name=f"manim_{video_path.stem}.mp4",
170
+ mime="video/mp4"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  )
172
+
173
+ # Show code
174
+ with st.expander("📝 View Generated Code"):
175
+ st.code(code, language="python")
176
+ elif code:
177
+ st.warning("Video rendering failed, but here's the generated code:")
178
+ st.code(code, language="python")
179
+
180
+ st.markdown("---")
181
+ st.markdown("💡 **Tips:** Start with simple prompts • Rendering takes 1-2 minutes • Be specific about what you want")