ginipick commited on
Commit
989bf3e
Β·
verified Β·
1 Parent(s): 84f75cc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +176 -320
app.py CHANGED
@@ -1,21 +1,21 @@
1
  #!/usr/bin/env python3
2
  """
3
- AI Video Generator with Gradio
4
  Single file application - app.py
5
  """
6
 
7
  import os
8
  import gradio as gr
9
- import replicate
 
 
10
  import base64
11
- from PIL import Image, ImageDraw, ImageFont
12
  import io
13
- import requests
14
  from datetime import datetime
15
  import tempfile
16
- import time
17
 
18
- # Try to import video processing libraries
19
  try:
20
  import cv2
21
  import numpy as np
@@ -24,10 +24,14 @@ except ImportError:
24
  VIDEO_PROCESSING_AVAILABLE = False
25
  print("Warning: cv2 not available. Watermark feature will be disabled.")
26
 
27
- # API token setup
28
- api_token = os.getenv("RAPI_TOKEN")
29
- if api_token:
30
- os.environ["REPLICATE_API_TOKEN"] = api_token
 
 
 
 
31
 
32
  # Aspect ratio options
33
  ASPECT_RATIOS = {
@@ -50,39 +54,29 @@ def add_watermark_cv2(input_video_path, output_video_path):
50
  return False
51
 
52
  try:
53
- # Open the video
54
  cap = cv2.VideoCapture(input_video_path)
55
-
56
- # Get video properties
57
  fps = int(cap.get(cv2.CAP_PROP_FPS))
58
  width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
59
  height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
60
 
61
- # Define codec and create VideoWriter
62
  fourcc = cv2.VideoWriter_fourcc(*'mp4v')
63
  out = cv2.VideoWriter(output_video_path, fourcc, fps, (width, height))
64
 
65
- # Watermark settings
66
  watermark_text = "ginigen.com"
67
  font = cv2.FONT_HERSHEY_SIMPLEX
68
- font_scale = max(0.4, height * 0.001) # Scale based on video height
69
  font_thickness = max(1, int(height * 0.002))
70
 
71
- # Get text size
72
  (text_width, text_height), baseline = cv2.getTextSize(watermark_text, font, font_scale, font_thickness)
73
-
74
- # Position (bottom right with padding)
75
  padding = int(width * 0.02)
76
  x = width - text_width - padding
77
  y = height - padding
78
 
79
- # Process each frame
80
  while True:
81
  ret, frame = cap.read()
82
  if not ret:
83
  break
84
 
85
- # Add semi-transparent background for text
86
  overlay = frame.copy()
87
  cv2.rectangle(overlay,
88
  (x - 5, y - text_height - 5),
@@ -90,43 +84,32 @@ def add_watermark_cv2(input_video_path, output_video_path):
90
  (0, 0, 0),
91
  -1)
92
  frame = cv2.addWeighted(frame, 0.7, overlay, 0.3, 0)
93
-
94
- # Add text
95
  cv2.putText(frame, watermark_text, (x, y), font, font_scale, (255, 255, 255), font_thickness, cv2.LINE_AA)
96
-
97
- # Write frame
98
  out.write(frame)
99
 
100
- # Release everything
101
  cap.release()
102
  out.release()
103
  cv2.destroyAllWindows()
104
-
105
  return True
106
 
107
  except Exception as e:
108
  print(f"Watermark error: {str(e)}")
109
  return False
110
 
111
- def add_watermark_simple(input_video_path, output_video_path):
112
- """Simple fallback - just copy the video without watermark"""
113
- try:
114
- import shutil
115
- shutil.copy2(input_video_path, output_video_path)
116
- return False
117
- except Exception as e:
118
- print(f"Copy error: {str(e)}")
119
- return False
120
-
121
  def add_watermark(input_video_path, output_video_path):
122
- """Add watermark to video - tries cv2 first, then fallback"""
123
  if VIDEO_PROCESSING_AVAILABLE:
124
  success = add_watermark_cv2(input_video_path, output_video_path)
125
  if success:
126
  return True
127
 
128
  # Fallback - just copy without watermark
129
- return add_watermark_simple(input_video_path, output_video_path)
 
 
 
 
 
130
 
131
  def update_prompt_placeholder(mode):
132
  """Update prompt placeholder based on mode"""
@@ -148,69 +131,44 @@ def update_image_input(mode):
148
  else:
149
  return gr.update(visible=False)
150
 
151
- def warmup_model(api_key_input, progress=gr.Progress()):
152
- """Warm up the model with a simple request"""
153
- token = api_key_input or api_token
154
- if not token:
155
- return gr.update(visible=True, value="❌ API token required. Please set it in API Settings.")
156
-
157
- replicate.api_token = token
158
 
159
- try:
160
- progress(0, desc="Starting model warm-up...")
161
-
162
- # Very simple warm-up request with minimal parameters
163
- warm_up_params = {
164
- "prompt": "red", # Extremely simple prompt
165
- "duration": 1, # Minimum duration
166
- "resolution": "480p",
167
- "aspect_ratio": "16:9",
168
- "seed": 42
169
- }
170
-
171
- progress(0.5, desc="Sending warm-up request (this may fail, that's normal)...")
172
-
173
  try:
174
- output = replicate.run("bytedance/seedance-1-lite", input=warm_up_params)
175
- if output:
176
- return gr.update(visible=True, value="""βœ… **Warm-up successful!**
177
-
178
- The model is now awake. You can generate videos normally.
179
- Note: The model stays active for ~5 minutes, so generate quickly!""")
180
- except Exception as e:
181
- if "PA" in str(e) or "interrupted" in str(e).lower():
182
- return gr.update(visible=True, value="""⏰ **Warm-up request sent!**
183
-
184
- The model is waking up. This is normal for a cold start.
185
-
186
- **Next steps:**
187
- 1. Wait 2-3 minutes for the model to fully initialize
188
- 2. Try a simple prompt: "a cat walking"
189
- 3. Once that works, use your actual prompt
190
-
191
- The wake-up process takes time, but subsequent generations will be fast!""")
192
- else:
193
- return gr.update(visible=True, value=f"""⚠️ **Warm-up attempted**
194
-
195
- Error: {str(e)[:200]}...
196
-
197
- **What to do:**
198
- 1. Wait 2-3 minutes
199
- 2. Try generating with a simple prompt
200
- 3. The model might already be warming up!""")
201
 
202
- except Exception as e:
203
- return gr.update(visible=True, value=f"❌ Warm-up error: {str(e)[:200]}... Try generating anyway after waiting a minute.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
- def generate_video(mode, prompt, image, aspect_ratio, seed, api_key_input, progress=gr.Progress()):
206
- """Main video generation function"""
207
-
208
- # API token check
209
- token = api_key_input or api_token
210
- if not token:
211
- return None, "❌ API token required. Please set RAPI_TOKEN environment variable or enter your API key."
212
-
213
- os.environ["REPLICATE_API_TOKEN"] = token
214
 
215
  # Input validation
216
  if not prompt:
@@ -220,147 +178,83 @@ def generate_video(mode, prompt, image, aspect_ratio, seed, api_key_input, progr
220
  return None, "❌ Please upload an image."
221
 
222
  try:
223
- progress(0, desc="Preparing video generation...")
224
-
225
- # Input parameters setup
226
- input_params = {
227
- "prompt": prompt,
228
- "duration": 5, # Fixed at 5 seconds
229
- "resolution": "480p",
230
- "aspect_ratio": aspect_ratio,
231
- "seed": seed
232
- }
233
 
234
- # Image to video mode
235
- if mode == "Image to Video" and image is not None:
236
- progress(0.1, desc="Processing image...")
 
237
 
238
- # Convert PIL Image to base64
239
- if isinstance(image, str): # File path
240
- with Image.open(image) as img:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  buffered = io.BytesIO()
242
- img.save(buffered, format="PNG")
243
  image_base64 = base64.b64encode(buffered.getvalue()).decode()
244
- else: # PIL Image object
245
- buffered = io.BytesIO()
246
- image.save(buffered, format="PNG")
247
- image_base64 = base64.b64encode(buffered.getvalue()).decode()
248
 
249
- input_params["image"] = f"data:image/png;base64,{image_base64}"
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
- progress(0.2, desc="Preparing API request...")
252
 
253
- # Set up Replicate with the API token
254
- replicate.api_token = token
 
 
 
 
255
 
256
- # Skip model availability check to avoid delays
257
- # The actual run will handle cold start retries
258
 
259
- progress(0.3, desc="Calling Replicate API...")
 
260
 
261
- # Run Replicate with retry logic and cold start handling
262
- max_attempts = 3 # Reduced attempts, focus on user guidance
263
- output = None
264
 
265
- for attempt in range(max_attempts):
266
- try:
267
- # Run the actual request
268
- progress(0.3 + attempt * 0.1, desc=f"Generating video... (Attempt {attempt + 1}/{max_attempts})")
269
-
270
- # Run the model
271
- output = replicate.run(
272
- "bytedance/seedance-1-lite",
273
- input=input_params
274
- )
275
-
276
- # If we got output, break the retry loop
277
- if output:
278
- break
279
-
280
- except Exception as e:
281
- error_str = str(e)
282
- print(f"Attempt {attempt + 1} error: {error_str}") # Debug logging
283
-
284
- # Check for cold start indicators
285
- if "Prediction interrupted" in error_str or "code: PA" in error_str or "PA" in error_str:
286
- if attempt == 0:
287
- # First cold start - provide immediate guidance
288
- return None, """❌ **Model is cold starting (code: PA)**
289
-
290
- **Quick Fix - Follow these steps:**
291
-
292
- 1️⃣ Click the **"πŸ”₯ Warm Up Model"** button above
293
- 2️⃣ Wait for the warm-up to complete (it may also fail, that's OK)
294
- 3️⃣ **Wait 2-3 minutes** for the model to fully initialize
295
- 4️⃣ Try this simple prompt first: **"a cat walking"**
296
- 5️⃣ Once that works, try your original prompt
297
-
298
- **Why this happens:** The model goes to sleep after inactivity to save resources. The first request wakes it up, which takes time.
299
-
300
- **Alternative:** Try again in 5 minutes when the model should be fully awake."""
301
- elif attempt < max_attempts - 1:
302
- # Subsequent attempts - wait and retry
303
- wait_time = 30 + (attempt * 15)
304
- progress(0.3, desc=f"Model still warming up. Waiting {wait_time}s... (Attempt {attempt + 2}/{max_attempts})")
305
- time.sleep(wait_time)
306
- continue
307
- else:
308
- return None, """❌ Model is taking longer than usual to start.
309
-
310
- **Please try one of these options:**
311
- 1. Use the "πŸ”₯ Warm Up Model" button first
312
- 2. Wait 5 minutes and try again
313
- 3. Try a very simple prompt: "a red circle"
314
- 4. Try during off-peak hours (evening/night US time)
315
-
316
- The model needs to 'warm up' after being idle."""
317
-
318
- elif "cold boot" in error_str.lower() or "starting" in error_str.lower():
319
- if attempt < max_attempts - 1:
320
- progress(0.3, desc=f"Model is booting up, waiting 60s...")
321
- time.sleep(60)
322
- continue
323
-
324
- elif "timeout" in error_str.lower():
325
- if attempt < max_attempts - 1:
326
- progress(0.3, desc=f"Request timed out, retrying... (Attempt {attempt + 2}/{max_attempts})")
327
- time.sleep(15)
328
- continue
329
-
330
- else:
331
- # For any other error, still retry a few times
332
- if attempt < max_attempts - 1:
333
- wait_time = 10 + (attempt * 5)
334
- progress(0.3, desc=f"Error occurred, retrying in {wait_time}s... (Attempt {attempt + 2}/{max_attempts})")
335
- time.sleep(wait_time)
336
- continue
337
- else:
338
- return None, f"❌ Error after {max_attempts} attempts: {error_str}"
339
-
340
- # Check if we got output
341
- if not output:
342
- return None, """❌ Failed to generate video after multiple attempts.
343
-
344
- **This usually happens when:**
345
- - The model is cold starting (hasn't been used recently)
346
- - The server is under heavy load
347
-
348
- **Please try:**
349
- 1. Wait 3-5 minutes for the model to warm up
350
- 2. Start with a simple prompt: "a cat walking" or "a ball bouncing"
351
- 3. Once that works, try your original prompt
352
-
353
- The first generation often takes longer, but subsequent ones will be faster."""
354
-
355
- progress(0.7, desc="Downloading video...")
356
 
357
- # Get video data
358
- if hasattr(output, 'read'):
359
- video_data = output.read()
360
- else:
361
- # Download from URL with timeout
362
- response = requests.get(output, timeout=60)
363
- video_data = response.content
364
 
365
  # Save to temporary file
366
  with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file:
@@ -372,7 +266,7 @@ The first generation often takes longer, but subsequent ones will be faster."""
372
  final_video_path = temp_video_path
373
 
374
  if VIDEO_PROCESSING_AVAILABLE:
375
- progress(0.8, desc="Adding watermark...")
376
  final_video_path = tempfile.mktemp(suffix='.mp4')
377
  watermark_added = add_watermark(temp_video_path, final_video_path)
378
 
@@ -405,67 +299,54 @@ The first generation often takes longer, but subsequent ones will be faster."""
405
  - Duration: 5 seconds
406
  - Resolution: 480p
407
  - Watermark: {watermark_status}
 
 
408
  - File: output.mp4"""
409
 
410
  return final_video_path, info
411
 
412
  except requests.exceptions.Timeout:
413
- return None, "⏱️ Request timed out. The server might be under heavy load. Please try again in a few minutes."
414
  except Exception as e:
415
- error_msg = f"❌ Error occurred: {str(e)}"
416
- if "timeout" in str(e).lower():
417
- error_msg += "\n\nπŸ’‘ Tip: The model might be cold starting. Please wait a minute and try again."
418
- return None, error_msg
419
 
420
  # Gradio interface
421
  with gr.Blocks(title="Bytedance Seedance Video Free", theme=gr.themes.Soft()) as app:
422
  gr.Markdown("""
423
  # 🎬 Bytedance Seedance Video' Free
424
 
425
- Generate videos from text or images using **Replicate API**.
426
 
427
- [![Powered by Ginigen](https://img.shields.io/badge/Powered%20by-Replicate-blue)](https://ginigen.com/)
428
- """)
429
 
430
- # Cold start warning box
431
- with gr.Accordion("⚠️ Getting 'Prediction interrupted (PA)' error? Click here!", open=False):
432
- gr.Markdown("""
433
- ### οΏ½οΏ½οΏ½οΏ½ Model Cold Start Guide
434
-
435
- If you're getting **"Prediction interrupted (code: PA)"** errors, the model is sleeping and needs to wake up.
436
-
437
- **Quick Solution:**
438
- 1. Click the **"πŸ”₯ Warm Up Model"** button
439
- 2. Wait 2-3 minutes after warm-up
440
- 3. Try a simple prompt: **"a cat walking"**
441
- 4. Once that works, use your actual prompt
442
-
443
- **Why this happens:** Free tier models sleep after ~5 minutes of inactivity to save resources.
444
- The first request wakes them up, which can take 2-5 minutes.
445
-
446
- **Pro tip:** Once warmed up, the model stays active for ~5 minutes. Generate multiple videos quickly!
447
- """)
448
 
449
  with gr.Row():
450
  with gr.Column(scale=1):
451
  # API Settings
452
- with gr.Accordion("βš™οΈ API Settings", open=not bool(api_token)):
453
- if api_token:
454
- gr.Markdown("βœ… API token loaded from environment variable.")
455
- api_key_input = gr.Textbox(
456
- label="Replicate API Token (Optional)",
457
- type="password",
458
- placeholder="Enter to override environment variable",
459
- value=""
460
- )
461
- else:
462
- gr.Markdown("⚠️ RAPI_TOKEN environment variable not set.")
463
- api_key_input = gr.Textbox(
464
- label="Replicate API Token (Required)",
465
- type="password",
466
- placeholder="Enter your Replicate API token",
467
- value=""
468
- )
 
 
 
 
 
469
 
470
  # Generation mode
471
  mode = gr.Radio(
@@ -476,12 +357,12 @@ with gr.Blocks(title="Bytedance Seedance Video Free", theme=gr.themes.Soft()) as
476
 
477
  # Image upload
478
  image_input = gr.Image(
479
- label="πŸ“· Upload Image",
480
  type="pil",
481
  visible=False
482
  )
483
 
484
- # Aspect ratio
485
  aspect_ratio = gr.Dropdown(
486
  label="πŸ“ Aspect Ratio",
487
  choices=list(ASPECT_RATIOS.keys()),
@@ -495,9 +376,9 @@ with gr.Blocks(title="Bytedance Seedance Video Free", theme=gr.themes.Soft()) as
495
  # Seed setting
496
  seed = gr.Number(
497
  label="🎲 Random Seed",
498
- value=42,
499
  precision=0,
500
- info="Use same seed value to reproduce same results"
501
  )
502
 
503
  # Fixed settings display
@@ -506,6 +387,7 @@ with gr.Blocks(title="Bytedance Seedance Video Free", theme=gr.themes.Soft()) as
506
  ### πŸ“‹ Fixed Settings
507
  - **Duration**: 5 seconds
508
  - **Resolution**: 480p
 
509
  - **Watermark**: {watermark_info}
510
  """)
511
 
@@ -518,23 +400,18 @@ with gr.Blocks(title="Bytedance Seedance Video Free", theme=gr.themes.Soft()) as
518
  value=""
519
  )
520
 
521
- # Generate and warm-up buttons
522
- with gr.Row():
523
- generate_btn = gr.Button("🎬 Generate Video", variant="primary", size="lg", scale=2)
524
- warmup_btn = gr.Button("πŸ”₯ Warm Up Model First", variant="secondary", size="lg", scale=1)
525
-
526
- gr.Markdown("*Use Warm Up if you're getting PA errors or it's your first generation today*")
527
 
528
  # Results display
529
  with gr.Column():
530
- warmup_status = gr.Textbox(label="Status", visible=False)
531
  output_video = gr.Video(
532
  label="πŸ“Ή Generated Video",
533
  autoplay=True
534
  )
535
  output_info = gr.Textbox(
536
  label="Information",
537
- lines=8,
538
  interactive=False
539
  )
540
 
@@ -545,7 +422,7 @@ with gr.Blocks(title="Bytedance Seedance Video Free", theme=gr.themes.Soft()) as
545
 
546
  1. **Install required packages**:
547
  ```bash
548
- pip install gradio replicate pillow requests
549
  ```
550
 
551
  2. **For watermark support (optional)**:
@@ -553,9 +430,10 @@ with gr.Blocks(title="Bytedance Seedance Video Free", theme=gr.themes.Soft()) as
553
  pip install opencv-python
554
  ```
555
 
556
- 3. **Set environment variable** (optional):
557
  ```bash
558
- export RAPI_TOKEN="your-replicate-api-token"
 
559
  ```
560
 
561
  4. **Run**:
@@ -566,52 +444,36 @@ with gr.Blocks(title="Bytedance Seedance Video Free", theme=gr.themes.Soft()) as
566
  ### Features
567
 
568
  - **Text to Video**: Generate video from text description only
569
- - **Image to Video**: Transform uploaded image into animated video
570
  - **Aspect Ratios**: Choose ratios optimized for various social media platforms
571
- - **Seed Value**: Use same seed to reproduce identical results
572
  - **Watermark**: Automatically adds "ginigen.com" watermark (requires opencv-python)
573
 
 
 
 
 
 
 
 
574
  ### Prompt Writing Tips
575
 
576
  - Use specific and detailed descriptions
577
  - Specify camera movements (e.g., zoom in, pan left, tracking shot)
578
  - Describe lighting and atmosphere (e.g., golden hour, dramatic lighting)
579
  - Indicate movement speed (e.g., slowly, rapidly, gently)
580
-
581
- ### Troubleshooting
582
-
583
- #### If you get "Prediction interrupted (code: PA)":
584
-
585
- This means the model is cold starting. Follow these steps:
586
-
587
- 1. **First Try**: Click the "πŸ”₯ Warm Up Model" button and wait for completion
588
- 2. **Wait**: After warm-up, wait 2-3 minutes for the model to fully initialize
589
- 3. **Simple Test**: Try generating with a very simple prompt: "a cat walking"
590
- 4. **Your Prompt**: Once the simple test works, try your actual prompt
591
-
592
- **Other Issues:**
593
- - **Repeated failures**: The model may be under heavy load. Try during off-peak hours
594
- - **Timeout errors**: Increase wait time between attempts
595
- - **Watermark not showing**: Install opencv-python for watermark support
596
-
597
- **Pro Tips:**
598
- - First generation of the day always takes longer
599
- - Keep prompts under 100 words for best results
600
- - Simple prompts work better for warming up the model
601
- - Once warmed up, generate multiple videos quickly before the model sleeps again!
602
  """)
603
 
604
  # Examples
605
  gr.Examples(
606
  examples=[
607
- ["Text to Video", "a red ball bouncing", None, "16:9", 42], # Very simple warm-up
608
- ["Text to Video", "a cat walking", None, "16:9", 42], # Simple warm-up prompt
609
  ["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],
610
  ["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],
611
  ["Text to Video", "Close-up of a flower blooming in time-lapse, soft natural lighting, shallow depth of field.", None, "1:1", 789],
612
  ],
613
  inputs=[mode, prompt, image_input, aspect_ratio, seed],
614
- label="Example Prompts (⚑ Start with 'red ball' or 'cat walking' if model is cold)"
615
  )
616
 
617
  # Event handlers
@@ -633,15 +495,9 @@ with gr.Blocks(title="Bytedance Seedance Video Free", theme=gr.themes.Soft()) as
633
  outputs=[ratio_info]
634
  )
635
 
636
- warmup_btn.click(
637
- fn=warmup_model,
638
- inputs=[api_key_input],
639
- outputs=[warmup_status]
640
- )
641
-
642
  generate_btn.click(
643
  fn=generate_video,
644
- inputs=[mode, prompt, image_input, aspect_ratio, seed, api_key_input],
645
  outputs=[output_video, output_info]
646
  )
647
 
 
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
 
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("WS_API_KEY_T2V", "946b77acd6a456dfde349aa0bac7b6d2bdf9c0c995fff072898c6d8734f866c4")
29
+ API_KEY_I2V = os.getenv("WS_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 = {
 
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),
 
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 update_prompt_placeholder(mode):
115
  """Update prompt placeholder based on mode"""
 
131
  else:
132
  return gr.update(visible=False)
133
 
134
+ def poll_for_result(request_id, api_key, progress_callback=None):
135
+ """Poll WaveSpeed API for video generation result"""
136
+ url = f"{API_BASE_URL}/predictions/{request_id}/result"
137
+ headers = {"Authorization": f"Bearer {api_key}"}
 
 
 
138
 
139
+ start_time = time.time()
140
+ while True:
 
 
 
 
 
 
 
 
 
 
 
 
141
  try:
142
+ response = requests.get(url, headers=headers, timeout=30)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
+ if response.status_code == 200:
145
+ result = response.json()["data"]
146
+ status = result["status"]
147
+
148
+ elapsed = time.time() - start_time
149
+ if progress_callback:
150
+ progress_val = min(0.3 + (elapsed / 120) * 0.6, 0.9)
151
+ progress_callback(progress_val, desc=f"Processing... Status: {status} ({int(elapsed)}s)")
152
+
153
+ if status == "completed":
154
+ video_url = result["outputs"][0]
155
+ return True, video_url
156
+ elif status == "failed":
157
+ error_msg = result.get('error', 'Unknown error')
158
+ return False, f"Generation failed: {error_msg}"
159
+ elif elapsed > 300: # 5 minute timeout
160
+ return False, "Timeout: Generation took too long"
161
+
162
+ # Continue polling
163
+ time.sleep(2)
164
+ else:
165
+ return False, f"API Error: {response.status_code} - {response.text}"
166
+
167
+ except Exception as e:
168
+ return False, f"Connection error: {str(e)}"
169
 
170
+ def generate_video(mode, prompt, image, aspect_ratio, seed, api_key_override_t2v, api_key_override_i2v, progress=gr.Progress()):
171
+ """Main video generation function using WaveSpeed API"""
 
 
 
 
 
 
 
172
 
173
  # Input validation
174
  if not prompt:
 
178
  return None, "❌ Please upload an image."
179
 
180
  try:
181
+ progress(0, desc="Preparing request...")
 
 
 
 
 
 
 
 
 
182
 
183
+ # Determine which API to use
184
+ if mode == "Text to Video":
185
+ api_key = api_key_override_t2v or API_KEY_T2V
186
+ endpoint = T2V_ENDPOINT
187
 
188
+ payload = {
189
+ "aspect_ratio": aspect_ratio,
190
+ "duration": 5,
191
+ "prompt": prompt,
192
+ "seed": seed if seed >= 0 else -1
193
+ }
194
+ else: # Image to Video
195
+ api_key = api_key_override_i2v or API_KEY_I2V
196
+ endpoint = I2V_ENDPOINT
197
+
198
+ # Handle image upload
199
+ if isinstance(image, str) and (image.startswith('http://') or image.startswith('https://')):
200
+ # If it's already a URL
201
+ image_url = image
202
+ else:
203
+ # Convert PIL Image to base64 for upload
204
+ progress(0.1, desc="Processing image...")
205
+ if isinstance(image, str):
206
+ with Image.open(image) as img:
207
+ buffered = io.BytesIO()
208
+ img.save(buffered, format="PNG")
209
+ image_base64 = base64.b64encode(buffered.getvalue()).decode()
210
+ else:
211
  buffered = io.BytesIO()
212
+ image.save(buffered, format="PNG")
213
  image_base64 = base64.b64encode(buffered.getvalue()).decode()
214
+
215
+ # For this demo, we'll assume the image needs to be uploaded somewhere
216
+ # In production, you'd upload to a CDN and get a URL
217
+ return None, "❌ Image upload to CDN not implemented. Please use an image URL."
218
 
219
+ payload = {
220
+ "duration": 5,
221
+ "image": image_url,
222
+ "prompt": prompt,
223
+ "seed": seed if seed >= 0 else -1
224
+ }
225
+
226
+ # Submit request
227
+ progress(0.2, desc="Submitting request to WaveSpeed AI...")
228
+
229
+ headers = {
230
+ "Content-Type": "application/json",
231
+ "Authorization": f"Bearer {api_key}"
232
+ }
233
 
234
+ response = requests.post(endpoint, headers=headers, data=json.dumps(payload), timeout=30)
235
 
236
+ if response.status_code == 200:
237
+ result = response.json()["data"]
238
+ request_id = result["id"]
239
+ progress(0.3, desc=f"Request submitted. ID: {request_id}")
240
+ else:
241
+ return None, f"❌ API Error: {response.status_code} - {response.text}"
242
 
243
+ # Poll for result
244
+ success, result = poll_for_result(request_id, api_key, lambda p, desc: progress(p, desc=desc))
245
 
246
+ if not success:
247
+ return None, f"❌ {result}"
248
 
249
+ video_url = result
250
+ progress(0.9, desc="Downloading video...")
 
251
 
252
+ # Download video
253
+ video_response = requests.get(video_url, timeout=60)
254
+ if video_response.status_code != 200:
255
+ return None, f"❌ Failed to download video from {video_url}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
 
257
+ video_data = video_response.content
 
 
 
 
 
 
258
 
259
  # Save to temporary file
260
  with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file:
 
266
  final_video_path = temp_video_path
267
 
268
  if VIDEO_PROCESSING_AVAILABLE:
269
+ progress(0.95, desc="Adding watermark...")
270
  final_video_path = tempfile.mktemp(suffix='.mp4')
271
  watermark_added = add_watermark(temp_video_path, final_video_path)
272
 
 
299
  - Duration: 5 seconds
300
  - Resolution: 480p
301
  - Watermark: {watermark_status}
302
+ - API: WaveSpeed AI
303
+ - Cost: $0.06
304
  - File: output.mp4"""
305
 
306
  return final_video_path, info
307
 
308
  except requests.exceptions.Timeout:
309
+ return None, "⏱️ Request timed out. Please try again."
310
  except Exception as e:
311
+ return None, f"❌ Error occurred: {str(e)}"
 
 
 
312
 
313
  # Gradio interface
314
  with gr.Blocks(title="Bytedance Seedance Video Free", theme=gr.themes.Soft()) as app:
315
  gr.Markdown("""
316
  # 🎬 Bytedance Seedance Video' Free
317
 
318
+ Generate videos from text or images using **WaveSpeed AI API**.
319
 
320
+ [![Powered by Ginigen](https://img.shields.io/badge/Powered%20by-WaveSpeed%20AI-blue)](https://ginigen.com/)
 
321
 
322
+ ⚠️ **Note**: Each generation costs $0.06
323
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
 
325
  with gr.Row():
326
  with gr.Column(scale=1):
327
  # API Settings
328
+ with gr.Accordion("βš™οΈ API Settings", open=False):
329
+ gr.Markdown("""
330
+ API keys are loaded from environment variables:
331
+ - `WAVESPEED_API_KEY_T2V` for Text-to-Video
332
+ - `WAVESPEED_API_KEY_I2V` for Image-to-Video
333
+
334
+ You can override them below if needed.
335
+ """)
336
+
337
+ api_key_override_t2v = gr.Textbox(
338
+ label="Text-to-Video API Key (Optional Override)",
339
+ type="password",
340
+ placeholder="Leave empty to use environment variable",
341
+ value=""
342
+ )
343
+
344
+ api_key_override_i2v = gr.Textbox(
345
+ label="Image-to-Video API Key (Optional Override)",
346
+ type="password",
347
+ placeholder="Leave empty to use environment variable",
348
+ value=""
349
+ )
350
 
351
  # Generation mode
352
  mode = gr.Radio(
 
357
 
358
  # Image upload
359
  image_input = gr.Image(
360
+ label="πŸ“· Upload Image or Provide URL",
361
  type="pil",
362
  visible=False
363
  )
364
 
365
+ # Aspect ratio (only for T2V)
366
  aspect_ratio = gr.Dropdown(
367
  label="πŸ“ Aspect Ratio",
368
  choices=list(ASPECT_RATIOS.keys()),
 
376
  # Seed setting
377
  seed = gr.Number(
378
  label="🎲 Random Seed",
379
+ value=-1,
380
  precision=0,
381
+ info="Use -1 for random, or set a specific value for reproducible results"
382
  )
383
 
384
  # Fixed settings display
 
387
  ### πŸ“‹ Fixed Settings
388
  - **Duration**: 5 seconds
389
  - **Resolution**: 480p
390
+ - **Cost**: $0.06 per video
391
  - **Watermark**: {watermark_info}
392
  """)
393
 
 
400
  value=""
401
  )
402
 
403
+ # Generate button
404
+ generate_btn = gr.Button("🎬 Generate Video ($0.06)", variant="primary", size="lg")
 
 
 
 
405
 
406
  # Results display
407
  with gr.Column():
 
408
  output_video = gr.Video(
409
  label="πŸ“Ή Generated Video",
410
  autoplay=True
411
  )
412
  output_info = gr.Textbox(
413
  label="Information",
414
+ lines=10,
415
  interactive=False
416
  )
417
 
 
422
 
423
  1. **Install required packages**:
424
  ```bash
425
+ pip install gradio requests pillow
426
  ```
427
 
428
  2. **For watermark support (optional)**:
 
430
  pip install opencv-python
431
  ```
432
 
433
+ 3. **Set environment variables**:
434
  ```bash
435
+ export WAVESPEED_API_KEY_T2V="your-t2v-api-key"
436
+ export WAVESPEED_API_KEY_I2V="your-i2v-api-key"
437
  ```
438
 
439
  4. **Run**:
 
444
  ### Features
445
 
446
  - **Text to Video**: Generate video from text description only
447
+ - **Image to Video**: Transform uploaded image into animated video (requires image URL)
448
  - **Aspect Ratios**: Choose ratios optimized for various social media platforms
449
+ - **Seed Value**: Use -1 for random or set specific value for reproducible results
450
  - **Watermark**: Automatically adds "ginigen.com" watermark (requires opencv-python)
451
 
452
+ ### API Information
453
+
454
+ - **Provider**: WaveSpeed AI
455
+ - **Models**: ByteDance Seedance v1 Lite
456
+ - **Cost**: $0.06 per 5-second video
457
+ - **Resolution**: 480p
458
+
459
  ### Prompt Writing Tips
460
 
461
  - Use specific and detailed descriptions
462
  - Specify camera movements (e.g., zoom in, pan left, tracking shot)
463
  - Describe lighting and atmosphere (e.g., golden hour, dramatic lighting)
464
  - Indicate movement speed (e.g., slowly, rapidly, gently)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  """)
466
 
467
  # Examples
468
  gr.Examples(
469
  examples=[
470
+ ["Text to Video", "A cat walking gracefully across a sunlit room", None, "16:9", -1],
 
471
  ["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],
472
  ["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],
473
  ["Text to Video", "Close-up of a flower blooming in time-lapse, soft natural lighting, shallow depth of field.", None, "1:1", 789],
474
  ],
475
  inputs=[mode, prompt, image_input, aspect_ratio, seed],
476
+ label="Example Prompts"
477
  )
478
 
479
  # Event handlers
 
495
  outputs=[ratio_info]
496
  )
497
 
 
 
 
 
 
 
498
  generate_btn.click(
499
  fn=generate_video,
500
+ inputs=[mode, prompt, image_input, aspect_ratio, seed, api_key_override_t2v, api_key_override_i2v],
501
  outputs=[output_video, output_info]
502
  )
503