Adityahulk commited on
Commit
dad5e7d
Β·
1 Parent(s): e46a637

making the app in direct mode

Browse files
Files changed (2) hide show
  1. start.sh +22 -6
  2. streamlit_app.py +326 -61
start.sh CHANGED
@@ -1,13 +1,29 @@
1
  #!/bin/bash
2
 
3
- # Start the API server in the background
4
- echo "Starting API Server on port 8003..."
5
- python api_server.py &
 
 
 
 
 
 
 
 
6
 
7
- # Wait a few seconds for the API to initialize
8
- sleep 5
 
 
 
 
 
9
 
10
  # Start Streamlit
11
  # Hugging Face Spaces expects the app to run on port 7860
12
- echo "Starting Streamlit on port 7860..."
 
 
 
13
  streamlit run streamlit_app.py --server.port 7860 --server.address 0.0.0.0
 
1
  #!/bin/bash
2
 
3
+ # ============================================================================
4
+ # Vidsimplify Startup Script for Hugging Face Spaces
5
+ # ============================================================================
6
+ #
7
+ # This script starts only the Streamlit app in DIRECT_MODE.
8
+ # In DIRECT_MODE, the app executes video generation directly without
9
+ # needing a separate API server, which is ideal for Hugging Face Spaces.
10
+ #
11
+ # For local development with the API server, set DIRECT_MODE=false
12
+ # and run: python api_server.py & streamlit run streamlit_app.py
13
+ # ============================================================================
14
 
15
+ # Ensure DIRECT_MODE is enabled for Hugging Face
16
+ export DIRECT_MODE=true
17
+
18
+ # Ensure all required directories exist
19
+ mkdir -p media/voiceover/elevenlabs
20
+ mkdir -p media/voiceover/gtts
21
+ mkdir -p media/videos
22
 
23
  # Start Streamlit
24
  # Hugging Face Spaces expects the app to run on port 7860
25
+ echo "🎬 Starting Vidsimplify in Direct Mode..."
26
+ echo " Port: 7860"
27
+ echo " Mode: Direct (no API server needed)"
28
+ echo ""
29
  streamlit run streamlit_app.py --server.port 7860 --server.address 0.0.0.0
streamlit_app.py CHANGED
@@ -1,8 +1,25 @@
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
- import requests
3
  import time
4
  import base64
 
 
 
5
  from pathlib import Path
 
 
 
 
6
 
7
  # Page config
8
  st.set_page_config(
@@ -41,25 +58,240 @@ st.markdown("""
41
  .css-1d391kg {
42
  padding-top: 3rem;
43
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  </style>
45
  """, unsafe_allow_html=True)
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  # Sidebar
 
 
48
  with st.sidebar:
49
  st.title("🎬 Vidsimplify")
50
  st.markdown("---")
51
- st.markdown("### Configuration")
52
- api_url = st.text_input("API URL", value="http://localhost:8003")
 
 
 
 
 
53
 
54
  st.markdown("### Settings")
55
- quality = st.selectbox("Quality", ["low", "medium", "high", "ultra"], index=2)
56
  category = st.selectbox("Category", ["tech_system", "product_startup", "mathematical"], index=0)
57
 
58
  st.markdown("---")
59
  st.markdown("### About")
60
  st.info("Generate educational animation videos from text, blogs, or PDFs using Manim and AI.")
61
 
62
- # Main content
 
 
 
63
  st.title("AI Video Generator")
64
  st.markdown("Turn your content into engaging animations in minutes.")
65
 
@@ -91,72 +323,105 @@ with tab3:
91
  uploaded_file = st.file_uploader("Choose a PDF file", type="pdf")
92
  if uploaded_file:
93
  input_type = "pdf"
94
- # Convert PDF to base64
95
  bytes_data = uploaded_file.getvalue()
96
  base64_pdf = base64.b64encode(bytes_data).decode('utf-8')
97
  input_data = base64_pdf
98
 
99
- # Generate button
 
 
 
 
100
  if st.button("Generate Video", type="primary", use_container_width=True):
101
  if not input_data:
102
  st.error("Please provide input data.")
103
  else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  try:
105
- with st.spinner("Submitting job to API..."):
106
- payload = {
107
- "input_type": input_type,
108
- "input_data": input_data,
109
- "quality": quality,
110
- "category": category
111
- }
112
-
113
- response = requests.post(f"{api_url}/api/videos", json=payload)
114
-
115
- if response.status_code == 200:
116
- job_data = response.json()
117
- job_id = job_data["job_id"]
118
- st.success(f"Job started! ID: {job_id}")
119
-
120
- # Polling for status
121
- progress_bar = st.progress(0)
122
- status_text = st.empty()
123
- video_placeholder = st.empty()
124
-
125
- while True:
126
- status_response = requests.get(f"{api_url}/api/jobs/{job_id}")
127
- if status_response.status_code == 200:
128
- status_data = status_response.json()
129
- status = status_data["status"]
130
- progress = status_data.get("progress", {})
131
- percentage = progress.get("percentage", 0)
132
- message = progress.get("message", "Processing...")
133
-
134
- progress_bar.progress(percentage / 100)
135
- status_text.info(f"Status: {status.upper()} - {message}")
136
-
137
- if status == "completed":
138
- st.balloons()
139
- status_text.success("Video generation completed!")
140
-
141
- # Display video
142
- video_url = f"{api_url}/api/videos/{job_id}"
143
- st.video(video_url)
144
- break
145
-
146
- elif status == "failed":
147
- error_msg = status_data.get("error", "Unknown error")
148
- status_text.error(f"Job failed: {error_msg}")
149
- break
150
-
151
- time.sleep(2)
152
- else:
153
- st.error(f"Failed to start job: {response.text}")
154
-
155
- except requests.exceptions.ConnectionError:
156
- st.error(f"Could not connect to API at {api_url}. Is the server running?")
157
  except Exception as e:
158
- st.error(f"An error occurred: {str(e)}")
 
 
 
 
 
 
159
 
 
160
  # Footer
 
 
161
  st.markdown("---")
162
- st.markdown("Built with Streamlit and Manimator")
 
 
 
 
 
 
 
 
1
+ """
2
+ Vidsimplify Streamlit Application
3
+
4
+ Supports two modes:
5
+ 1. DIRECT MODE (for Hugging Face) - directly executes generation code
6
+ 2. API MODE (for local development) - uses the FastAPI server
7
+
8
+ Set DIRECT_MODE=true in environment to use direct execution.
9
+ """
10
+
11
  import streamlit as st
12
+ import os
13
  import time
14
  import base64
15
+ import uuid
16
+ import subprocess
17
+ import asyncio
18
  from pathlib import Path
19
+ from datetime import datetime
20
+
21
+ # Check if we're in direct mode (for Hugging Face deployment)
22
+ DIRECT_MODE = os.getenv("DIRECT_MODE", "true").lower() == "true"
23
 
24
  # Page config
25
  st.set_page_config(
 
58
  .css-1d391kg {
59
  padding-top: 3rem;
60
  }
61
+ .status-box {
62
+ padding: 1rem;
63
+ border-radius: 10px;
64
+ margin: 1rem 0;
65
+ }
66
+ .status-generating {
67
+ background-color: #1e3a5f;
68
+ border-left: 4px solid #3b82f6;
69
+ }
70
+ .status-rendering {
71
+ background-color: #1e3a5f;
72
+ border-left: 4px solid #f59e0b;
73
+ }
74
+ .status-complete {
75
+ background-color: #1a3d2e;
76
+ border-left: 4px solid #10b981;
77
+ }
78
+ .status-error {
79
+ background-color: #3d1a1a;
80
+ border-left: 4px solid #ef4444;
81
+ }
82
  </style>
83
  """, unsafe_allow_html=True)
84
 
85
+
86
+ # ============================================================================
87
+ # Direct Mode Functions (No API needed)
88
+ # ============================================================================
89
+
90
+ def generate_video_direct(input_type: str, input_data: str, quality: str, category: str, progress_callback=None):
91
+ """
92
+ Generate video directly without API.
93
+ Used for Hugging Face deployment.
94
+ """
95
+ from manimator.api.animation_generation import generate_animation_response
96
+ from manimator.utils.code_fixer import CodeFixer
97
+
98
+ job_id = str(uuid.uuid4())
99
+ base_dir = Path(__file__).parent
100
+
101
+ # Quality flags for Manim
102
+ quality_flags = {
103
+ "low": "-pql",
104
+ "medium": "-pqm",
105
+ "high": "-pqh",
106
+ "ultra": "-pqk"
107
+ }
108
+
109
+ quality_dirs = {
110
+ "low": "480p15",
111
+ "medium": "720p30",
112
+ "high": "1080p60",
113
+ "ultra": "2160p60"
114
+ }
115
+
116
+ try:
117
+ # Stage 1: Generate code
118
+ if progress_callback:
119
+ progress_callback("generating_code", 20, "Generating Manim code with AI...")
120
+
121
+ code = generate_animation_response(
122
+ input_data=input_data,
123
+ input_type=input_type,
124
+ category=category,
125
+ job_id=job_id
126
+ )
127
+
128
+ # Save code to file
129
+ scene_name = f"Scene_{uuid.uuid4().hex[:8]}"
130
+ code_file = base_dir / f"scene_{job_id}.py"
131
+
132
+ # Replace class name in code
133
+ import re
134
+ code = re.sub(r'class\s+\w+\s*\(\s*VoiceoverScene\s*\)', f'class {scene_name}(VoiceoverScene)', code)
135
+
136
+ with open(code_file, 'w') as f:
137
+ f.write(code)
138
+
139
+ if progress_callback:
140
+ progress_callback("code_generated", 40, "Code generated successfully!")
141
+
142
+ # Stage 2: Render video with retry loop
143
+ fixer = CodeFixer()
144
+ max_retries = 3
145
+ video_path = None
146
+
147
+ # Ensure media directories exist
148
+ media_dir = base_dir / "media"
149
+ (media_dir / "voiceover" / "elevenlabs").mkdir(parents=True, exist_ok=True)
150
+ (media_dir / "voiceover" / "gtts").mkdir(parents=True, exist_ok=True)
151
+ (media_dir / "videos").mkdir(parents=True, exist_ok=True)
152
+
153
+ for attempt in range(max_retries):
154
+ try:
155
+ if progress_callback:
156
+ progress_callback("rendering", 50 + (attempt * 10), f"Rendering video (attempt {attempt + 1}/{max_retries})...")
157
+
158
+ # Run Manim
159
+ cmd = [
160
+ "manim",
161
+ quality_flags[quality],
162
+ "--media_dir", str(media_dir),
163
+ str(code_file),
164
+ scene_name
165
+ ]
166
+
167
+ env = os.environ.copy()
168
+ env["MEDIA_DIR"] = str(media_dir.resolve())
169
+
170
+ result = subprocess.run(
171
+ cmd,
172
+ capture_output=True,
173
+ text=True,
174
+ cwd=str(base_dir),
175
+ env=env,
176
+ timeout=300 # 5 minute timeout
177
+ )
178
+
179
+ if result.returncode != 0:
180
+ error_msg = result.stderr[-500:] if result.stderr else "Unknown render error"
181
+ raise Exception(f"Manim rendering failed: {error_msg}")
182
+
183
+ # Find video file
184
+ video_dir = media_dir / "videos" / code_file.stem / quality_dirs[quality]
185
+ video_files = list(video_dir.glob("*.mp4"))
186
+
187
+ if not video_files:
188
+ raise Exception(f"No video file found in {video_dir}")
189
+
190
+ video_path = video_files[0]
191
+ break # Success!
192
+
193
+ except Exception as e:
194
+ if attempt < max_retries - 1:
195
+ # Try to fix code
196
+ if progress_callback:
197
+ progress_callback("fixing", 50 + (attempt * 10), "Fixing rendering error...")
198
+
199
+ with open(code_file, 'r') as f:
200
+ current_code = f.read()
201
+
202
+ fixed_code = fixer.fix_runtime_error(current_code, str(e))
203
+
204
+ with open(code_file, 'w') as f:
205
+ f.write(fixed_code)
206
+ else:
207
+ raise e
208
+
209
+ # Cleanup code file
210
+ try:
211
+ code_file.unlink()
212
+ except:
213
+ pass
214
+
215
+ if progress_callback:
216
+ progress_callback("completed", 100, "Video generation completed!")
217
+
218
+ return video_path
219
+
220
+ except Exception as e:
221
+ if progress_callback:
222
+ progress_callback("failed", 0, f"Error: {str(e)}")
223
+ raise e
224
+
225
+
226
+ def generate_via_api(api_url: str, input_type: str, input_data: str, quality: str, category: str, progress_callback=None):
227
+ """
228
+ Generate video via API (for local development).
229
+ """
230
+ import requests
231
+
232
+ payload = {
233
+ "input_type": input_type,
234
+ "input_data": input_data,
235
+ "quality": quality,
236
+ "category": category
237
+ }
238
+
239
+ response = requests.post(f"{api_url}/api/videos", json=payload, timeout=30)
240
+
241
+ if response.status_code != 200:
242
+ raise Exception(f"Failed to start job: {response.text}")
243
+
244
+ job_data = response.json()
245
+ job_id = job_data["job_id"]
246
+
247
+ # Poll for status
248
+ while True:
249
+ status_response = requests.get(f"{api_url}/api/jobs/{job_id}", timeout=10)
250
+ if status_response.status_code == 200:
251
+ status_data = status_response.json()
252
+ status = status_data["status"]
253
+ progress = status_data.get("progress", {})
254
+ percentage = progress.get("percentage", 0)
255
+ message = progress.get("message", "Processing...")
256
+
257
+ if progress_callback:
258
+ progress_callback(status, percentage, message)
259
+
260
+ if status == "completed":
261
+ return f"{api_url}/api/videos/{job_id}"
262
+ elif status == "failed":
263
+ raise Exception(status_data.get("error", "Unknown error"))
264
+
265
+ time.sleep(2)
266
+
267
+
268
+ # ============================================================================
269
  # Sidebar
270
+ # ============================================================================
271
+
272
  with st.sidebar:
273
  st.title("🎬 Vidsimplify")
274
  st.markdown("---")
275
+
276
+ # Show mode indicator
277
+ if DIRECT_MODE:
278
+ st.success("πŸš€ Direct Mode (Hugging Face)")
279
+ else:
280
+ st.info("πŸ”Œ API Mode")
281
+ api_url = st.text_input("API URL", value="http://localhost:8000")
282
 
283
  st.markdown("### Settings")
284
+ quality = st.selectbox("Quality", ["low", "medium", "high"], index=1)
285
  category = st.selectbox("Category", ["tech_system", "product_startup", "mathematical"], index=0)
286
 
287
  st.markdown("---")
288
  st.markdown("### About")
289
  st.info("Generate educational animation videos from text, blogs, or PDFs using Manim and AI.")
290
 
291
+ # ============================================================================
292
+ # Main Content
293
+ # ============================================================================
294
+
295
  st.title("AI Video Generator")
296
  st.markdown("Turn your content into engaging animations in minutes.")
297
 
 
323
  uploaded_file = st.file_uploader("Choose a PDF file", type="pdf")
324
  if uploaded_file:
325
  input_type = "pdf"
 
326
  bytes_data = uploaded_file.getvalue()
327
  base64_pdf = base64.b64encode(bytes_data).decode('utf-8')
328
  input_data = base64_pdf
329
 
330
+
331
+ # ============================================================================
332
+ # Generate Button & Logic
333
+ # ============================================================================
334
+
335
  if st.button("Generate Video", type="primary", use_container_width=True):
336
  if not input_data:
337
  st.error("Please provide input data.")
338
  else:
339
+ # Create status containers
340
+ progress_bar = st.progress(0)
341
+ status_container = st.empty()
342
+ video_container = st.empty()
343
+
344
+ def update_progress(status, percentage, message):
345
+ progress_bar.progress(min(percentage / 100, 1.0))
346
+
347
+ status_class = {
348
+ "generating_code": "status-generating",
349
+ "code_generated": "status-generating",
350
+ "rendering": "status-rendering",
351
+ "fixing": "status-rendering",
352
+ "completed": "status-complete",
353
+ "failed": "status-error"
354
+ }.get(status, "status-generating")
355
+
356
+ status_container.markdown(
357
+ f'<div class="status-box {status_class}">πŸ“Š <strong>{status.upper()}</strong>: {message}</div>',
358
+ unsafe_allow_html=True
359
+ )
360
+
361
  try:
362
+ if DIRECT_MODE:
363
+ # Direct execution (Hugging Face)
364
+ update_progress("starting", 5, "Initializing video generation...")
365
+
366
+ video_path = generate_video_direct(
367
+ input_type=input_type,
368
+ input_data=input_data,
369
+ quality=quality,
370
+ category=category,
371
+ progress_callback=update_progress
372
+ )
373
+
374
+ # Display video from local file
375
+ st.balloons()
376
+ with open(video_path, 'rb') as video_file:
377
+ video_bytes = video_file.read()
378
+ video_container.video(video_bytes)
379
+
380
+ # Offer download
381
+ st.download_button(
382
+ label="πŸ“₯ Download Video",
383
+ data=video_bytes,
384
+ file_name=f"vidsimplify_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4",
385
+ mime="video/mp4"
386
+ )
387
+
388
+ else:
389
+ # API mode (local development)
390
+ import requests
391
+
392
+ update_progress("submitting", 5, "Submitting job to API...")
393
+
394
+ video_url = generate_via_api(
395
+ api_url=api_url,
396
+ input_type=input_type,
397
+ input_data=input_data,
398
+ quality=quality,
399
+ category=category,
400
+ progress_callback=update_progress
401
+ )
402
+
403
+ st.balloons()
404
+ video_container.video(video_url)
405
+
 
 
 
 
 
 
 
 
406
  except Exception as e:
407
+ error_msg = str(e)
408
+ st.error(f"❌ Generation failed: {error_msg}")
409
+
410
+ # Show helpful message for common errors
411
+ if "Could not connect" in error_msg or "Connection" in error_msg:
412
+ st.warning("πŸ’‘ If running locally, make sure the API server is running: `python api_server.py`")
413
+
414
 
415
+ # ============================================================================
416
  # Footer
417
+ # ============================================================================
418
+
419
  st.markdown("---")
420
+ col1, col2, col3 = st.columns(3)
421
+ with col1:
422
+ st.markdown("Built with ❀️ using Streamlit")
423
+ with col2:
424
+ st.markdown("Powered by Manim & AI")
425
+ with col3:
426
+ mode_text = "Direct Mode" if DIRECT_MODE else "API Mode"
427
+ st.markdown(f"Running in: **{mode_text}**")