ginipick commited on
Commit
e22c216
·
verified ·
1 Parent(s): ba3a242

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +23 -560
app.py CHANGED
@@ -1,572 +1,35 @@
1
- #!/usr/bin/env python3
2
- """
3
- AI Video Generator with Gradio - WaveSpeed API Version
4
- Single file application - app.py
5
- """
6
-
7
  import os
8
- import gradio as gr
9
- import requests
10
- import json
11
- import time
12
- import base64
13
- from PIL import Image
14
- import io
15
- from datetime import datetime
16
- import tempfile
17
-
18
- # Try to import video processing libraries for watermark
19
- try:
20
- import cv2
21
- import numpy as np
22
- VIDEO_PROCESSING_AVAILABLE = True
23
- except ImportError:
24
- VIDEO_PROCESSING_AVAILABLE = False
25
- print("Warning: cv2 not available. Watermark feature will be disabled.")
26
-
27
- # API keys setup from environment variables
28
- API_KEY_T2V = os.getenv("WAVESPEED_API_KEY_T2V", "946b77acd6a456dfde349aa0bac7b6d2bdf9c0c995fff072898c6d8734f866c4")
29
- API_KEY_I2V = os.getenv("WAVESPEED_API_KEY_I2V", "5a90eaa7ded95066c07adf55b91185a83abfa8db3fdc74d12ba5ad66db1d68fe")
30
-
31
- # API endpoints
32
- API_BASE_URL = "https://api.wavespeed.ai/api/v3"
33
- T2V_ENDPOINT = f"{API_BASE_URL}/bytedance/seedance-v1-lite-t2v-480p"
34
- I2V_ENDPOINT = f"{API_BASE_URL}/bytedance/seedance-v1-lite-i2v-480p"
35
-
36
- # Aspect ratio options
37
- ASPECT_RATIOS = {
38
- "16:9": "16:9 (YouTube, Standard Video)",
39
- "4:3": "4:3 (Traditional TV Format)",
40
- "1:1": "1:1 (Instagram Feed)",
41
- "3:4": "3:4 (Instagram Portrait)",
42
- "9:16": "9:16 (Instagram Reels, TikTok)",
43
- "21:9": "21:9 (Cinematic Wide)",
44
- "9:21": "9:21 (Ultra Vertical)"
45
- }
46
 
47
- # Default prompts
48
- DEFAULT_TEXT_PROMPT = ""
49
- DEFAULT_IMAGE_PROMPT = "Generate a video with smooth and natural movement. Objects should have visible motion while maintaining fluid transitions."
50
-
51
- def add_watermark_cv2(input_video_path, output_video_path):
52
- """Add watermark to video using OpenCV"""
53
- if not VIDEO_PROCESSING_AVAILABLE:
54
- return False
55
-
56
  try:
57
- cap = cv2.VideoCapture(input_video_path)
58
- fps = int(cap.get(cv2.CAP_PROP_FPS))
59
- width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
60
- height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
61
-
62
- fourcc = cv2.VideoWriter_fourcc(*'mp4v')
63
- out = cv2.VideoWriter(output_video_path, fourcc, fps, (width, height))
64
 
65
- watermark_text = "ginigen.com"
66
- font = cv2.FONT_HERSHEY_SIMPLEX
67
- font_scale = max(0.4, height * 0.001)
68
- font_thickness = max(1, int(height * 0.002))
69
 
70
- (text_width, text_height), baseline = cv2.getTextSize(watermark_text, font, font_scale, font_thickness)
71
- padding = int(width * 0.02)
72
- x = width - text_width - padding
73
- y = height - padding
74
-
75
- while True:
76
- ret, frame = cap.read()
77
- if not ret:
78
- break
79
-
80
- overlay = frame.copy()
81
- cv2.rectangle(overlay,
82
- (x - 5, y - text_height - 5),
83
- (x + text_width + 5, y + 5),
84
- (0, 0, 0),
85
- -1)
86
- frame = cv2.addWeighted(frame, 0.7, overlay, 0.3, 0)
87
- cv2.putText(frame, watermark_text, (x, y), font, font_scale, (255, 255, 255), font_thickness, cv2.LINE_AA)
88
- out.write(frame)
89
 
90
- cap.release()
91
- out.release()
92
- cv2.destroyAllWindows()
93
- return True
94
 
95
- except Exception as e:
96
- print(f"Watermark error: {str(e)}")
97
- return False
98
-
99
- def add_watermark(input_video_path, output_video_path):
100
- """Add watermark to video"""
101
- if VIDEO_PROCESSING_AVAILABLE:
102
- success = add_watermark_cv2(input_video_path, output_video_path)
103
- if success:
104
- return True
105
-
106
- # Fallback - just copy without watermark
107
- try:
108
- import shutil
109
- shutil.copy2(input_video_path, output_video_path)
110
- return False
111
- except:
112
- return False
113
-
114
- def compress_image(image, max_size_mb=2, max_dimension=1920):
115
- """Compress and resize image to meet size requirements"""
116
- # Convert to RGB if necessary
117
- if image.mode in ('RGBA', 'LA'):
118
- rgb_image = Image.new('RGB', image.size, (255, 255, 255))
119
- rgb_image.paste(image, mask=image.split()[-1] if image.mode == 'RGBA' else None)
120
- image = rgb_image
121
- elif image.mode not in ('RGB', 'L'):
122
- image = image.convert('RGB')
123
-
124
- # Resize if dimensions are too large
125
- width, height = image.size
126
- if width > max_dimension or height > max_dimension:
127
- ratio = min(max_dimension / width, max_dimension / height)
128
- new_width = int(width * ratio)
129
- new_height = int(height * ratio)
130
- image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
131
-
132
- # Compress with varying quality to meet size requirement
133
- quality = 95
134
- while quality > 20:
135
- buffered = io.BytesIO()
136
- image.save(buffered, format="JPEG", quality=quality, optimize=True)
137
- size_mb = len(buffered.getvalue()) / (1024 * 1024)
138
-
139
- if size_mb <= max_size_mb:
140
- return buffered.getvalue()
141
-
142
- quality -= 5
143
-
144
- # If still too large, resize more aggressively
145
- width, height = image.size
146
- image = image.resize((width // 2, height // 2), Image.Resampling.LANCZOS)
147
- buffered = io.BytesIO()
148
- image.save(buffered, format="JPEG", quality=50, optimize=True)
149
- return buffered.getvalue()
150
-
151
- def update_prompt_placeholder(mode):
152
- """Update prompt placeholder based on mode"""
153
- if mode == "Text to Video":
154
- return gr.update(
155
- placeholder="Describe the video you want to create.\nExample: The sun rises slowly between tall buildings. [Ground-level follow shot] Bicycle tires roll over a dew-covered street at dawn.",
156
- value=""
157
- )
158
- else:
159
- return gr.update(
160
- placeholder="Describe how the image should move.\nExample: Camera slowly zooms in while clouds move across the sky. The subject's hair gently moves in the wind.",
161
- value=DEFAULT_IMAGE_PROMPT
162
- )
163
-
164
- def update_image_input(mode):
165
- """Show/hide image input based on mode"""
166
- if mode == "Image to Video":
167
- # Show image input, hide aspect ratio for I2V
168
- return gr.update(visible=True), gr.update(visible=False)
169
- else:
170
- # Hide image input, show aspect ratio for T2V
171
- return gr.update(visible=False), gr.update(visible=True)
172
-
173
- def poll_for_result(request_id, api_key, progress_callback=None):
174
- """Poll WaveSpeed API for video generation result"""
175
- url = f"{API_BASE_URL}/predictions/{request_id}/result"
176
- headers = {"Authorization": f"Bearer {api_key}"}
177
-
178
- start_time = time.time()
179
- while True:
180
  try:
181
- response = requests.get(url, headers=headers, timeout=30)
182
-
183
- if response.status_code == 200:
184
- result = response.json()["data"]
185
- status = result["status"]
186
-
187
- elapsed = time.time() - start_time
188
- if progress_callback:
189
- progress_val = min(0.3 + (elapsed / 120) * 0.6, 0.9)
190
- progress_callback(progress_val, desc=f"Processing... Status: {status} ({int(elapsed)}s)")
191
-
192
- if status == "completed":
193
- video_url = result["outputs"][0]
194
- return True, video_url
195
- elif status == "failed":
196
- error_msg = result.get('error', 'Unknown error')
197
- return False, f"Generation failed: {error_msg}"
198
- elif elapsed > 300: # 5 minute timeout
199
- return False, "Timeout: Generation took too long"
200
-
201
- # Continue polling
202
- time.sleep(2)
203
- else:
204
- return False, f"API Error: {response.status_code} - {response.text}"
205
-
206
- except Exception as e:
207
- return False, f"Connection error: {str(e)}"
208
-
209
- def generate_video(mode, prompt, image_url, image_file, aspect_ratio, seed, api_key_override_t2v, api_key_override_i2v, progress=gr.Progress()):
210
- """Main video generation function using WaveSpeed API"""
211
-
212
- # Input validation
213
- if not prompt:
214
- return None, "❌ Please enter a prompt."
215
-
216
- if mode == "Image to Video":
217
- if not image_url and image_file is None:
218
- return None, "❌ Please provide an image URL or upload an image."
219
-
220
- try:
221
- progress(0, desc="Preparing request...")
222
-
223
- # Determine which API to use
224
- if mode == "Text to Video":
225
- api_key = api_key_override_t2v or API_KEY_T2V
226
- endpoint = T2V_ENDPOINT
227
-
228
- payload = {
229
- "aspect_ratio": aspect_ratio,
230
- "duration": 5,
231
- "prompt": prompt,
232
- "seed": seed if seed >= 0 else -1
233
- }
234
- else: # Image to Video
235
- api_key = api_key_override_i2v or API_KEY_I2V
236
- endpoint = I2V_ENDPOINT
237
-
238
- # Handle image input
239
- if image_url:
240
- # Use provided URL directly
241
- final_image_url = image_url
242
- elif image_file is not None:
243
- # Compress image to reduce size
244
- try:
245
- # Get original size
246
- orig_buffered = io.BytesIO()
247
- image_file.save(orig_buffered, format="PNG")
248
- orig_size_mb = len(orig_buffered.getvalue()) / (1024 * 1024)
249
-
250
- progress(0.1, desc=f"Compressing image (Original: {orig_size_mb:.1f}MB)...")
251
-
252
- compressed_data = compress_image(image_file, max_size_mb=2, max_dimension=1920)
253
- compressed_size_mb = len(compressed_data) / (1024 * 1024)
254
-
255
- progress(0.15, desc=f"Image compressed: {orig_size_mb:.1f}MB → {compressed_size_mb:.1f}MB")
256
-
257
- image_base64 = base64.b64encode(compressed_data).decode()
258
-
259
- # Check encoded size
260
- encoded_size_mb = len(image_base64) / (1024 * 1024)
261
- if encoded_size_mb > 20: # Conservative limit for base64
262
- return None, f"""❌ Image too large even after compression.
263
-
264
- Original size: {orig_size_mb:.1f}MB
265
- Compressed size: {compressed_size_mb:.1f}MB
266
- Encoded size: {encoded_size_mb:.1f}MB (exceeds API limit)
267
-
268
- Please use an image hosting service:
269
- 1. Go to [imgur.com](https://imgur.com)
270
- 2. Upload your image (drag & drop)
271
- 3. Right-click uploaded image → "Copy image address"
272
- 4. Paste URL in the Image URL field above"""
273
-
274
- # Try data URL format
275
- final_image_url = f"data:image/jpeg;base64,{image_base64}"
276
-
277
- except Exception as e:
278
- return None, f"❌ Error processing image: {str(e)}"
279
- else:
280
- return None, "❌ No image provided."
281
 
282
- payload = {
283
- "duration": 5,
284
- "image": final_image_url,
285
- "prompt": prompt,
286
- "seed": seed if seed >= 0 else -1
287
- }
288
-
289
- # Submit request
290
- progress(0.2, desc="Submitting request to WaveSpeed AI...")
291
-
292
- headers = {
293
- "Content-Type": "application/json",
294
- "Authorization": f"Bearer {api_key}"
295
- }
296
-
297
- response = requests.post(endpoint, headers=headers, data=json.dumps(payload), timeout=30)
298
-
299
- if response.status_code == 200:
300
- result = response.json()["data"]
301
- request_id = result["id"]
302
- progress(0.3, desc=f"Request submitted. ID: {request_id}")
303
- else:
304
- error_detail = response.text
305
- if mode == "Image to Video" and image_file is not None and not image_url:
306
- # Check if it's a size limit error
307
- if "size exceeds" in error_detail or "30MB" in error_detail:
308
- return None, f"""❌ Image size exceeds API limit (30MB).
309
-
310
- Your image was compressed but still too large for the API.
311
-
312
- **Please use an image hosting service instead:**
313
- 1. Go to [imgur.com](https://imgur.com) (no account needed)
314
- 2. Click "New post" → Drop your image
315
- 3. After upload, right-click the image → "Copy image address"
316
- 4. Paste the URL in the Image URL field above
317
-
318
- Alternative services:
319
- - [imgbb.com](https://imgbb.com) - Simple, no account needed
320
- - [postimages.org](https://postimages.org) - Direct links available
321
-
322
- Error: {response.status_code}"""
323
- else:
324
- return None, f"""❌ API Error: {response.status_code}
325
-
326
- The API does not accept base64 data URLs. Please use a direct image URL instead:
327
-
328
- 1. Upload your image to [imgur.com](https://imgur.com)
329
- 2. Copy the direct image URL (ends with .jpg or .png)
330
- 3. Paste it in the Image URL field above
331
-
332
- Error details: {error_detail}"""
333
- else:
334
- return None, f"❌ API Error: {response.status_code} - {error_detail}"
335
-
336
- # Poll for result
337
- success, result = poll_for_result(request_id, api_key, lambda p, desc: progress(p, desc=desc))
338
-
339
- if not success:
340
- return None, f"❌ {result}"
341
-
342
- video_url = result
343
- progress(0.9, desc="Downloading video...")
344
-
345
- # Download video
346
- video_response = requests.get(video_url, timeout=60)
347
- if video_response.status_code != 200:
348
- return None, f"❌ Failed to download video from {video_url}"
349
-
350
- video_data = video_response.content
351
-
352
- # Save to temporary file
353
- with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file:
354
- tmp_file.write(video_data)
355
- temp_video_path = tmp_file.name
356
-
357
- # Try to add watermark
358
- watermark_added = False
359
- final_video_path = temp_video_path
360
-
361
- if VIDEO_PROCESSING_AVAILABLE:
362
- progress(0.95, desc="Adding watermark...")
363
- final_video_path = tempfile.mktemp(suffix='.mp4')
364
- watermark_added = add_watermark(temp_video_path, final_video_path)
365
-
366
- if not watermark_added or not os.path.exists(final_video_path):
367
- final_video_path = temp_video_path
368
-
369
- # Save final video
370
- with open(final_video_path, "rb") as f:
371
- final_video_data = f.read()
372
- with open("output.mp4", "wb") as file:
373
- file.write(final_video_data)
374
-
375
- # Clean up temp files
376
- if temp_video_path != final_video_path and os.path.exists(temp_video_path):
377
- try:
378
- os.unlink(temp_video_path)
379
- except:
380
- pass
381
-
382
- progress(1.0, desc="Complete!")
383
-
384
- # Generation info
385
- watermark_status = "Added" if watermark_added else "Not available (cv2 not installed)" if not VIDEO_PROCESSING_AVAILABLE else "Failed"
386
-
387
- # Add image info if applicable
388
- image_info = ""
389
- if mode == "Image to Video":
390
- if image_url:
391
- image_info = "\n- Image Source: URL provided"
392
- elif image_file:
393
- image_info = "\n- Image Source: Uploaded & compressed"
394
-
395
- info = f"""✅ Video generated successfully!
396
-
397
- 📊 Generation Info:
398
- - Mode: {mode}
399
- - Aspect Ratio: {aspect_ratio if mode == "Text to Video" else "N/A"}{image_info}
400
- - Seed: {seed}
401
- - Duration: 5 seconds
402
- - Resolution: 480p
403
- - Watermark: {watermark_status}
404
- - API: WaveSpeed AI
405
- - File: output.mp4"""
406
-
407
- return final_video_path, info
408
-
409
- except requests.exceptions.Timeout:
410
- return None, "⏱️ Request timed out. Please try again."
411
  except Exception as e:
412
- return None, f" Error occurred: {str(e)}"
413
-
414
- # Gradio interface
415
- with gr.Blocks(title="Bytedance Seedance Video Free", theme=gr.themes.Soft()) as app:
416
- gr.Markdown("""
417
- # 🎬 Bytedance Seedance Video' Free
418
-
419
- Generate videos from text or images using **API**.
420
-
421
- [![Powered by Ginigen](https://img.shields.io/badge/Powered%20by-Ginigen)](https://ginigen.com/)
422
-
423
- """)
424
-
425
- with gr.Row():
426
- with gr.Column(scale=1):
427
- # API Settings
428
- with gr.Accordion("⚙️ API Settings", open=False):
429
- gr.Markdown("""
430
- API keys are loaded from environment variables:
431
- - `WS_API_KEY_T2V` for Text-to-Video
432
- - `WS_API_KEY_I2V` for Image-to-Video
433
-
434
- You can override them below if needed.
435
- """)
436
-
437
- api_key_override_t2v = gr.Textbox(
438
- label="Text-to-Video API Key (Optional Override)",
439
- type="password",
440
- placeholder="Leave empty to use environment variable",
441
- value=""
442
- )
443
-
444
- api_key_override_i2v = gr.Textbox(
445
- label="Image-to-Video API Key (Optional Override)",
446
- type="password",
447
- placeholder="Leave empty to use environment variable",
448
- value=""
449
- )
450
-
451
- # Generation mode
452
- mode = gr.Radio(
453
- label="🎯 Generation Mode",
454
- choices=["Text to Video", "Image to Video"],
455
- value="Text to Video"
456
- )
457
-
458
- # Image input - URL or file
459
- with gr.Column(visible=False) as image_input_group:
460
- gr.Markdown("### Image Input")
461
- image_url_input = gr.Textbox(
462
- label="📷 Image URL (Recommended) - Direct image URL works best",
463
- placeholder="https://example.com/image.jpg"
464
- )
465
- gr.Markdown("**OR**")
466
- image_file_input = gr.Image(
467
- label="📤 Upload Image (Will be compressed, max 2MB after compression)",
468
- type="pil"
469
- )
470
- gr.Markdown("""
471
- ⚠️ **Note**: API has a 30MB limit. Large images will be compressed.
472
- If upload fails, use [imgur.com](https://imgur.com) to host your image.
473
- """)
474
-
475
- # Aspect ratio (only for T2V)
476
- aspect_ratio = gr.Dropdown(
477
- label="📐 Aspect Ratio (Text to Video only)",
478
- choices=list(ASPECT_RATIOS.keys()),
479
- value="16:9",
480
- info="Choose ratio optimized for social media platforms",
481
- visible=True
482
- )
483
-
484
- # Ratio description
485
- ratio_info = gr.Markdown(value=f"Selected ratio: {ASPECT_RATIOS['16:9']}")
486
-
487
- # Seed setting
488
- seed = gr.Number(
489
- label="🎲 Random Seed",
490
- value=-1,
491
- precision=0,
492
- info="Use -1 for random, or set a specific value for reproducible results"
493
- )
494
-
495
- # Fixed settings display
496
- watermark_info = "ginigen.com" if VIDEO_PROCESSING_AVAILABLE else "ginigen.com (requires cv2)"
497
- gr.Markdown(f"""
498
- ### 📋 Fixed Settings
499
- - **Duration**: 5 seconds
500
- - **Resolution**: 480p
501
- - **Watermark**: {watermark_info}
502
- """)
503
-
504
- with gr.Column(scale=2):
505
- # Prompt input
506
- prompt = gr.Textbox(
507
- label="✍️ Prompt",
508
- lines=5,
509
- placeholder="Describe the video you want to create.\nExample: The sun rises slowly between tall buildings. [Ground-level follow shot] Bicycle tires roll over a dew-covered street at dawn.",
510
- value=""
511
- )
512
-
513
- # Generate button
514
- generate_btn = gr.Button("🎬 Generate Video", variant="primary", size="lg")
515
-
516
- # Results display
517
- with gr.Column():
518
- output_video = gr.Video(
519
- label="📹 Generated Video",
520
- autoplay=True
521
- )
522
- output_info = gr.Textbox(
523
- label="Information",
524
- lines=10,
525
- interactive=False
526
- )
527
-
528
-
529
- # Examples
530
- gr.Examples(
531
- examples=[
532
- ["Text to Video", "A cat walking gracefully across a sunlit room", "", None, "16:9", -1],
533
- ["Text to Video", "A serene lake at sunrise with mist rolling over the water. Camera slowly pans across the landscape as birds fly overhead.", "", None, "16:9", 42],
534
- ["Text to Video", "Urban street scene at night with neon lights reflecting on wet pavement. People walking with umbrellas, camera tracking forward.", "", None, "9:16", 123],
535
- ],
536
- inputs=[mode, prompt, image_url_input, image_file_input, aspect_ratio, seed],
537
- label="Example Prompts"
538
- )
539
-
540
- # Event handlers
541
- mode.change(
542
- fn=update_prompt_placeholder,
543
- inputs=[mode],
544
- outputs=[prompt]
545
- )
546
-
547
- mode.change(
548
- fn=update_image_input,
549
- inputs=[mode],
550
- outputs=[image_input_group, aspect_ratio]
551
- )
552
-
553
- aspect_ratio.change(
554
- fn=lambda x: f"Selected ratio: {ASPECT_RATIOS[x]}",
555
- inputs=[aspect_ratio],
556
- outputs=[ratio_info]
557
- )
558
-
559
- generate_btn.click(
560
- fn=generate_video,
561
- inputs=[mode, prompt, image_url_input, image_file_input, aspect_ratio, seed, api_key_override_t2v, api_key_override_i2v],
562
- outputs=[output_video, output_info]
563
- )
564
 
565
- # Run app
566
  if __name__ == "__main__":
567
- app.launch(
568
- server_name="0.0.0.0",
569
- server_port=7860,
570
- share=False,
571
- inbrowser=True
572
- )
 
 
 
 
 
 
 
1
  import os
2
+ import sys
3
+ import streamlit as st
4
+ from tempfile import NamedTemporaryFile
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
+ def main():
 
 
 
 
 
 
 
 
7
  try:
8
+ # Get the code from secrets
9
+ code = os.environ.get("MAIN_CODE")
 
 
 
 
 
10
 
11
+ if not code:
12
+ st.error("⚠️ The application code wasn't found in secrets. Please add the MAIN_CODE secret.")
13
+ return
 
14
 
15
+ # Create a temporary Python file
16
+ with NamedTemporaryFile(suffix='.py', delete=False, mode='w') as tmp:
17
+ tmp.write(code)
18
+ tmp_path = tmp.name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ # Execute the code
21
+ exec(compile(code, tmp_path, 'exec'), globals())
 
 
22
 
23
+ # Clean up the temporary file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  try:
25
+ os.unlink(tmp_path)
26
+ except:
27
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  except Exception as e:
30
+ st.error(f"⚠️ Error loading or executing the application: {str(e)}")
31
+ import traceback
32
+ st.code(traceback.format_exc())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
 
34
  if __name__ == "__main__":
35
+ main()