ginipick commited on
Commit
1d2a592
·
verified ·
1 Parent(s): e48246c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +331 -294
app.py CHANGED
@@ -4,7 +4,6 @@ import os
4
  from PIL import Image
5
  import requests
6
  from io import BytesIO
7
- import time
8
  import tempfile
9
  import base64
10
  import spaces
@@ -12,6 +11,7 @@ import torch
12
  import numpy as np
13
  import random
14
  import gc
 
15
 
16
  # ===========================
17
  # Configuration
@@ -21,26 +21,35 @@ import gc
21
  os.environ['REPLICATE_API_TOKEN'] = os.getenv('REPLICATE_API_TOKEN')
22
 
23
  # Video Model Configuration
24
- VIDEO_MODEL_ID = "cjwbw/videocrafter2:02e509c789964be7d70de8d8fef3a6dd18f160b37272bcccc742d5adabb9f38f" # Using public model
25
- LANDSCAPE_WIDTH = 512 # Reduced for stability
26
- LANDSCAPE_HEIGHT = 320 # Reduced for stability
27
  MAX_SEED = np.iinfo(np.int32).max
28
- FIXED_FPS = 8 # Reduced FPS
29
- MIN_FRAMES_MODEL = 8
30
- MAX_FRAMES_MODEL = 32 # Reduced max frames
31
 
32
  default_prompt_i2v = "make this image come alive, smooth animation"
33
  default_negative_prompt = "static, still, blurry, low quality"
34
 
35
  # ===========================
36
- # Image Processing Functions
37
  # ===========================
38
 
 
 
 
 
 
39
  def upload_image_to_hosting(image):
40
  """Upload image to hosting service"""
41
  try:
 
 
 
 
 
 
 
42
  buffered = BytesIO()
43
- image.save(buffered, format="PNG")
44
  buffered.seek(0)
45
  img_base64 = base64.b64encode(buffered.getvalue()).decode()
46
 
@@ -58,7 +67,7 @@ def upload_image_to_hosting(image):
58
  if data.get('success'):
59
  return data['data']['url']
60
  except Exception as e:
61
- print(f"Upload failed: {e}")
62
 
63
  # Fallback to base64
64
  buffered = BytesIO()
@@ -67,117 +76,152 @@ def upload_image_to_hosting(image):
67
  img_base64 = base64.b64encode(buffered.getvalue()).decode()
68
  return f"data:image/png;base64,{img_base64}"
69
 
 
 
 
 
70
  def process_images(prompt, image1, image2=None):
71
  """Process images using Replicate API"""
72
- if not image1:
73
- return None, "Please upload at least one image", None
74
 
75
- if not os.getenv('REPLICATE_API_TOKEN'):
76
- return None, "Please set REPLICATE_API_TOKEN in Space settings", None
77
 
78
  try:
79
- # Upload image
80
- url1 = upload_image_to_hosting(image1)
81
 
82
- # Use SDXL for image generation/editing
83
  output = replicate.run(
84
  "stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b",
85
  input={
86
- "prompt": prompt + ", high quality, detailed",
87
- "negative_prompt": "low quality, blurry, distorted",
88
  "width": 1024,
89
  "height": 1024,
90
- "num_inference_steps": 25
 
91
  }
92
  )
93
 
94
- if output and isinstance(output, list) and len(output) > 0:
95
- img_url = output[0]
 
 
 
 
 
 
 
 
96
  response = requests.get(img_url, timeout=30)
97
  if response.status_code == 200:
98
  img = Image.open(BytesIO(response.content))
99
- return img, "✨ Image generated successfully!", img
100
 
101
- return None, "Could not process output", None
102
 
103
  except Exception as e:
104
  error_msg = str(e)
105
- if "trial" in error_msg.lower():
106
- return None, "Replicate API limit reached. Please try again later.", None
107
- return None, f"Error: {error_msg[:200]}", None
 
 
 
108
 
109
  # ===========================
110
  # Video Generation Functions
111
  # ===========================
112
 
113
  def resize_image_for_video(image: Image.Image) -> Image.Image:
114
- """Resize image for video generation"""
115
- # Convert RGBA to RGB if necessary
116
  if image.mode == 'RGBA':
117
  background = Image.new('RGB', image.size, (255, 255, 255))
118
  background.paste(image, mask=image.split()[3])
119
  image = background
 
 
120
 
121
  # Resize to target dimensions
122
  image = image.resize((LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT), Image.LANCZOS)
123
  return image
124
 
125
- # GPU function with proper decorator
126
  @spaces.GPU(duration=60)
127
  def generate_video_gpu(
128
  input_image,
129
  prompt,
130
- steps=25,
131
- negative_prompt=default_negative_prompt,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  duration_seconds=2.0,
133
  seed=42,
134
- randomize_seed=False,
135
  ):
136
- """Generate video using Replicate API with GPU"""
 
 
 
137
 
138
  if input_image is None:
139
  return None, seed, "Please provide an input image"
140
 
141
  try:
142
- # Clear GPU memory
143
- if torch.cuda.is_available():
144
- torch.cuda.empty_cache()
145
- gc.collect()
146
-
147
- # Resize image
148
  resized_image = resize_image_for_video(input_image)
149
 
150
- # Save resized image temporarily
151
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_img:
152
- resized_image.save(tmp_img.name)
153
-
154
- # Upload to hosting
155
- img_url = upload_image_to_hosting(resized_image)
156
 
157
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
158
 
159
- # Use Replicate for video generation
160
  print("Generating video with Replicate...")
 
 
161
  output = replicate.run(
162
- VIDEO_MODEL_ID,
163
  input={
164
- "prompt": prompt,
165
- "image": img_url,
166
- "steps": int(steps),
167
- "fps": FIXED_FPS,
168
- "seconds": min(duration_seconds, 3), # Limit to 3 seconds
169
  "seed": current_seed
170
  }
171
  )
172
 
173
  if output:
174
  # Download video
175
- if isinstance(output, str):
176
- video_url = output
177
- elif hasattr(output, 'url'):
178
- video_url = output.url()
179
- else:
180
- video_url = str(output)
181
 
182
  response = requests.get(video_url, timeout=60)
183
  if response.status_code == 200:
@@ -189,44 +233,10 @@ def generate_video_gpu(
189
 
190
  except Exception as e:
191
  error_msg = str(e)
192
- if "out of memory" in error_msg.lower():
193
- torch.cuda.empty_cache()
194
- gc.collect()
195
- return None, seed, "GPU memory exceeded. Try reducing duration."
196
- return None, seed, f"Error: {error_msg[:200]}"
197
-
198
- # Wrapper function for video generation
199
- def generate_video(
200
- input_image,
201
- prompt,
202
- steps=25,
203
- negative_prompt=default_negative_prompt,
204
- duration_seconds=2.0,
205
- seed=42,
206
- randomize_seed=False,
207
- ):
208
- """Wrapper function that calls the GPU function"""
209
- if not os.getenv('REPLICATE_API_TOKEN'):
210
- return None, seed, "Please set REPLICATE_API_TOKEN in Space settings"
211
-
212
- return generate_video_gpu(
213
- input_image,
214
- prompt,
215
- steps,
216
- negative_prompt,
217
- duration_seconds,
218
- seed,
219
- randomize_seed
220
- )
221
-
222
- # ===========================
223
- # Simple dummy GPU function for startup
224
- # ===========================
225
-
226
- @spaces.GPU(duration=1)
227
- def dummy_gpu_function():
228
- """Dummy function to satisfy Spaces GPU requirement"""
229
- return "GPU initialized"
230
 
231
  # ===========================
232
  # CSS Styling
@@ -236,36 +246,40 @@ css = """
236
  .gradio-container {
237
  max-width: 1200px !important;
238
  margin: 0 auto !important;
 
239
  }
240
  .header-container {
241
  background: linear-gradient(135deg, #ffd93d, #ffb347);
242
- padding: 2rem;
243
- border-radius: 15px;
244
  margin-bottom: 2rem;
245
  text-align: center;
 
246
  }
247
  .logo-text {
248
- font-size: 2.5rem;
249
- font-weight: bold;
250
  color: #2d3436;
 
 
251
  }
252
  .subtitle {
253
  color: #2d3436;
254
- font-size: 1.1rem;
255
  margin-top: 0.5rem;
 
256
  }
257
- .gr-button {
258
- font-size: 1rem !important;
259
- padding: 12px 24px !important;
 
260
  }
261
- .gr-button-primary {
262
- background: linear-gradient(135deg, #ffd93d, #ffb347) !important;
263
- border: none !important;
264
  }
265
- .gr-button-secondary {
266
- background: linear-gradient(135deg, #667eea, #764ba2) !important;
267
- color: white !important;
268
- border: none !important;
269
  }
270
  """
271
 
@@ -273,203 +287,226 @@ css = """
273
  # Gradio Interface
274
  # ===========================
275
 
276
- with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
277
- # Initialize GPU on startup
278
- startup_status = gr.State(dummy_gpu_function())
279
-
280
- # Shared state
281
- generated_image_state = gr.State(None)
282
-
283
- gr.HTML("""
284
- <div class="header-container">
285
- <h1 class="logo-text">🍌 Nano Banana + Video</h1>
286
- <p class="subtitle">AI Image Generation with Video Creation</p>
287
- <p style="color: #636e72; font-size: 0.9rem; margin-top: 10px;">
288
- ⚠️ Note: Add REPLICATE_API_TOKEN in Space Settings > Repository secrets
289
- </p>
290
- </div>
291
- """)
292
-
293
- with gr.Tabs():
294
- # Tab 1: Image Generation
295
- with gr.TabItem("🎨 Step 1: Generate Image"):
296
- with gr.Row():
297
- with gr.Column(scale=1):
298
- style_prompt = gr.Textbox(
299
- label="Image Description",
300
- placeholder="Describe what you want to create...",
301
- lines=3,
302
- value="A beautiful fantasy landscape with mountains and a river, studio ghibli style"
303
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
- with gr.Row():
306
- image1 = gr.Image(
307
- label="Reference Image (Optional)",
308
  type="pil",
309
- height=200
310
  )
311
- image2 = gr.Image(
312
- label="Style Reference (Optional)",
313
- type="pil",
314
- height=200
 
315
  )
316
-
317
- generate_img_btn = gr.Button(
318
- "🎨 Generate Image",
319
- variant="primary",
320
- size="lg"
321
- )
322
-
323
- with gr.Column(scale=1):
324
- output_image = gr.Image(
325
- label="Generated Result",
326
- type="pil",
327
- height=400
328
- )
329
-
330
- img_status = gr.Textbox(
331
- label="Status",
332
- interactive=False,
333
- value="Ready to generate..."
334
- )
335
-
336
- send_to_video_btn = gr.Button(
337
- "➡️ Send to Video Generation",
338
- variant="secondary",
339
- visible=False
340
- )
341
-
342
- # Tab 2: Video Generation
343
- with gr.TabItem("🎬 Step 2: Generate Video"):
344
- gr.Markdown("### Transform your image into a video")
345
 
346
- with gr.Row():
347
- with gr.Column(scale=1):
348
- video_input_image = gr.Image(
349
- type="pil",
350
- label="Input Image",
351
- height=300
352
- )
353
-
354
- video_prompt = gr.Textbox(
355
- label="Animation Description",
356
- value=default_prompt_i2v,
357
- lines=2
358
- )
359
-
360
- with gr.Row():
361
- duration_input = gr.Slider(
362
- minimum=1.0,
363
- maximum=3.0,
364
- step=0.5,
365
- value=2.0,
366
- label="Duration (seconds)"
367
  )
368
 
369
- steps_slider = gr.Slider(
370
- minimum=10,
371
- maximum=50,
372
- step=5,
373
- value=25,
374
- label="Quality Steps"
375
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
 
377
- with gr.Row():
378
- video_seed = gr.Slider(
379
- label="Seed",
380
- minimum=0,
381
- maximum=MAX_SEED,
382
- step=1,
383
- value=42
384
  )
385
 
386
- randomize_seed = gr.Checkbox(
387
- label="Random seed",
388
- value=True
 
389
  )
390
-
391
- video_negative_prompt = gr.Textbox(
392
- label="Negative Prompt",
393
- value=default_negative_prompt,
394
- lines=2
395
- )
396
-
397
- generate_video_btn = gr.Button(
398
- "🎬 Generate Video",
399
- variant="primary",
400
- size="lg"
401
- )
402
-
403
- with gr.Column(scale=1):
404
- video_output = gr.Video(
405
- label="Generated Video",
406
- autoplay=True,
407
- height=400
408
- )
409
-
410
- video_status = gr.Textbox(
411
- label="Status",
412
- interactive=False,
413
- value="Ready to generate video..."
414
- )
415
-
416
- # Event Handlers
417
- def on_image_generated(prompt, img1, img2):
418
- img, status, state_img = process_images(prompt, img1, img2)
419
- if img:
420
- return img, status, state_img, gr.update(visible=True)
421
- return None, status, None, gr.update(visible=False)
422
-
423
- def send_image_to_video(img):
424
- if img:
425
- return img, "Image loaded! Ready to generate video."
426
- return None, "No image to send."
427
-
428
- # Connect events
429
- generate_img_btn.click(
430
- fn=on_image_generated,
431
- inputs=[style_prompt, image1, image2],
432
- outputs=[output_image, img_status, generated_image_state, send_to_video_btn]
433
- )
434
-
435
- send_to_video_btn.click(
436
- fn=send_image_to_video,
437
- inputs=[generated_image_state],
438
- outputs=[video_input_image, video_status]
439
- )
440
-
441
- generate_video_btn.click(
442
- fn=generate_video,
443
- inputs=[
444
- video_input_image,
445
- video_prompt,
446
- steps_slider,
447
- video_negative_prompt,
448
- duration_input,
449
- video_seed,
450
- randomize_seed
451
- ],
452
- outputs=[video_output, video_seed, video_status]
453
- )
 
 
454
 
455
- # Examples
456
- gr.Examples(
457
- examples=[
458
- ["A majestic castle on a hilltop at sunset, fantasy art style"],
459
- ["Cute robot in a flower garden, pixar animation style"],
460
- ["Northern lights over a frozen lake, photorealistic"],
461
- ["Ancient temple in a jungle, mysterious atmosphere"],
462
- ],
463
- inputs=[style_prompt],
464
- label="Example Prompts"
465
- )
466
 
467
- # Launch the app
468
  if __name__ == "__main__":
469
- print("Starting Nano Banana + Video app...")
470
- print("Make sure to set REPLICATE_API_TOKEN in your Space settings!")
 
 
 
 
 
 
 
 
 
471
 
 
 
472
  demo.launch(
473
- share=False,
474
- show_error=True
475
  )
 
4
  from PIL import Image
5
  import requests
6
  from io import BytesIO
 
7
  import tempfile
8
  import base64
9
  import spaces
 
11
  import numpy as np
12
  import random
13
  import gc
14
+ import time
15
 
16
  # ===========================
17
  # Configuration
 
21
  os.environ['REPLICATE_API_TOKEN'] = os.getenv('REPLICATE_API_TOKEN')
22
 
23
  # Video Model Configuration
24
+ LANDSCAPE_WIDTH = 512
25
+ LANDSCAPE_HEIGHT = 320
 
26
  MAX_SEED = np.iinfo(np.int32).max
27
+ FIXED_FPS = 8
 
 
28
 
29
  default_prompt_i2v = "make this image come alive, smooth animation"
30
  default_negative_prompt = "static, still, blurry, low quality"
31
 
32
  # ===========================
33
+ # Helper Functions
34
  # ===========================
35
 
36
+ def check_api_token():
37
+ """Check if Replicate API token is set"""
38
+ token = os.getenv('REPLICATE_API_TOKEN')
39
+ return token is not None and token.strip() != ""
40
+
41
  def upload_image_to_hosting(image):
42
  """Upload image to hosting service"""
43
  try:
44
+ # Convert to RGB if needed
45
+ if image.mode == 'RGBA':
46
+ background = Image.new('RGB', image.size, (255, 255, 255))
47
+ background.paste(image, mask=image.split()[3])
48
+ image = background
49
+
50
+ # Try imgbb.com first
51
  buffered = BytesIO()
52
+ image.save(buffered, format="PNG", optimize=True, quality=95)
53
  buffered.seek(0)
54
  img_base64 = base64.b64encode(buffered.getvalue()).decode()
55
 
 
67
  if data.get('success'):
68
  return data['data']['url']
69
  except Exception as e:
70
+ print(f"Upload error: {e}")
71
 
72
  # Fallback to base64
73
  buffered = BytesIO()
 
76
  img_base64 = base64.b64encode(buffered.getvalue()).decode()
77
  return f"data:image/png;base64,{img_base64}"
78
 
79
+ # ===========================
80
+ # Image Generation Functions
81
+ # ===========================
82
+
83
  def process_images(prompt, image1, image2=None):
84
  """Process images using Replicate API"""
85
+ if not prompt or prompt.strip() == "":
86
+ return None, "Please enter a prompt", None
87
 
88
+ if not check_api_token():
89
+ return None, "⚠️ Please set REPLICATE_API_TOKEN in Space settings (Settings > Repository secrets)", None
90
 
91
  try:
92
+ # Simple text-to-image generation using SDXL
93
+ print(f"Generating image with prompt: {prompt}")
94
 
 
95
  output = replicate.run(
96
  "stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b",
97
  input={
98
+ "prompt": prompt + ", high quality, detailed, 8k",
99
+ "negative_prompt": "blurry, low quality, distorted, deformed",
100
  "width": 1024,
101
  "height": 1024,
102
+ "num_inference_steps": 30,
103
+ "guidance_scale": 7.5
104
  }
105
  )
106
 
107
+ # Handle output
108
+ if output:
109
+ if isinstance(output, list) and len(output) > 0:
110
+ img_url = output[0]
111
+ elif isinstance(output, str):
112
+ img_url = output
113
+ else:
114
+ img_url = str(output)
115
+
116
+ # Download image
117
  response = requests.get(img_url, timeout=30)
118
  if response.status_code == 200:
119
  img = Image.open(BytesIO(response.content))
120
+ return img, "✨ Image generated successfully! You can now create a video from it.", img
121
 
122
+ return None, "Failed to generate image", None
123
 
124
  except Exception as e:
125
  error_msg = str(e)
126
+ if "authentication" in error_msg.lower():
127
+ return None, " Invalid API token. Please check your REPLICATE_API_TOKEN.", None
128
+ elif "rate limit" in error_msg.lower():
129
+ return None, "⏳ Rate limit reached. Please try again later.", None
130
+ else:
131
+ return None, f"Error: {error_msg[:200]}", None
132
 
133
  # ===========================
134
  # Video Generation Functions
135
  # ===========================
136
 
137
  def resize_image_for_video(image: Image.Image) -> Image.Image:
138
+ """Resize and prepare image for video generation"""
139
+ # Convert RGBA to RGB
140
  if image.mode == 'RGBA':
141
  background = Image.new('RGB', image.size, (255, 255, 255))
142
  background.paste(image, mask=image.split()[3])
143
  image = background
144
+ elif image.mode != 'RGB':
145
+ image = image.convert('RGB')
146
 
147
  # Resize to target dimensions
148
  image = image.resize((LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT), Image.LANCZOS)
149
  return image
150
 
 
151
  @spaces.GPU(duration=60)
152
  def generate_video_gpu(
153
  input_image,
154
  prompt,
155
+ steps,
156
+ negative_prompt,
157
+ duration_seconds,
158
+ seed,
159
+ randomize_seed
160
+ ):
161
+ """GPU-accelerated video generation"""
162
+ try:
163
+ # This function runs on GPU
164
+ # For demo purposes, we'll just process the image
165
+ # In production, you would run actual video generation here
166
+
167
+ # Clear GPU memory
168
+ if torch.cuda.is_available():
169
+ torch.cuda.empty_cache()
170
+ gc.collect()
171
+
172
+ # Simulate video generation
173
+ time.sleep(2) # Simulate processing time
174
+
175
+ # For now, return a placeholder since actual video generation requires specific models
176
+ return None, seed, "🎬 Video generation simulated (GPU function executed successfully)"
177
+
178
+ except Exception as e:
179
+ return None, seed, f"GPU Error: {str(e)[:200]}"
180
+
181
+ def generate_video_replicate(
182
+ input_image,
183
+ prompt,
184
+ steps=30,
185
+ negative_prompt="",
186
  duration_seconds=2.0,
187
  seed=42,
188
+ randomize_seed=False
189
  ):
190
+ """Generate video using Replicate API (no GPU needed)"""
191
+
192
+ if not check_api_token():
193
+ return None, seed, "⚠️ Please set REPLICATE_API_TOKEN"
194
 
195
  if input_image is None:
196
  return None, seed, "Please provide an input image"
197
 
198
  try:
199
+ # Resize and prepare image
 
 
 
 
 
200
  resized_image = resize_image_for_video(input_image)
201
 
202
+ # Upload image
203
+ img_url = upload_image_to_hosting(resized_image)
 
 
 
 
204
 
205
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
206
 
 
207
  print("Generating video with Replicate...")
208
+
209
+ # Use AnimateDiff or similar model
210
  output = replicate.run(
211
+ "stability-ai/stable-video-diffusion:3f0457e4619daac51203dedb472816fd4af51f3149fa7a9e0b5ffcf1b8172438",
212
  input={
213
+ "input_image": img_url,
214
+ "frames_per_second": FIXED_FPS,
215
+ "motion_bucket_id": 127, # Controls motion amount
216
+ "cond_aug": 0.02,
217
+ "decoding_t": 7, # Number of frames
218
  "seed": current_seed
219
  }
220
  )
221
 
222
  if output:
223
  # Download video
224
+ video_url = output if isinstance(output, str) else str(output)
 
 
 
 
 
225
 
226
  response = requests.get(video_url, timeout=60)
227
  if response.status_code == 200:
 
233
 
234
  except Exception as e:
235
  error_msg = str(e)
236
+ if "authentication" in error_msg.lower():
237
+ return None, seed, "❌ Invalid API token"
238
+ else:
239
+ return None, seed, f"Error: {error_msg[:200]}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
  # ===========================
242
  # CSS Styling
 
246
  .gradio-container {
247
  max-width: 1200px !important;
248
  margin: 0 auto !important;
249
+ padding: 20px !important;
250
  }
251
  .header-container {
252
  background: linear-gradient(135deg, #ffd93d, #ffb347);
253
+ padding: 2.5rem;
254
+ border-radius: 20px;
255
  margin-bottom: 2rem;
256
  text-align: center;
257
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
258
  }
259
  .logo-text {
260
+ font-size: 3rem;
261
+ font-weight: 900;
262
  color: #2d3436;
263
+ margin: 0;
264
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
265
  }
266
  .subtitle {
267
  color: #2d3436;
268
+ font-size: 1.2rem;
269
  margin-top: 0.5rem;
270
+ font-weight: 500;
271
  }
272
+ .status-box {
273
+ padding: 10px;
274
+ border-radius: 8px;
275
+ margin: 10px 0;
276
  }
277
+ .gr-button {
278
+ transition: all 0.3s ease;
 
279
  }
280
+ .gr-button:hover {
281
+ transform: translateY(-2px);
282
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
 
283
  }
284
  """
285
 
 
287
  # Gradio Interface
288
  # ===========================
289
 
290
+ def create_interface():
291
+ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
292
+ # Shared state
293
+ generated_image_state = gr.State(None)
294
+
295
+ # Header
296
+ gr.HTML("""
297
+ <div class="header-container">
298
+ <h1 class="logo-text">🍌 Nano Banana + Video</h1>
299
+ <p class="subtitle">Transform Text to Image, Then Bring It to Life!</p>
300
+ <div style="margin-top: 1rem;">
301
+ <p style="color: #636e72; font-size: 0.9rem;">
302
+ Step 1: Generate an image from text | Step 2: Convert it to video
303
+ </p>
304
+ </div>
305
+ </div>
306
+ """)
307
+
308
+ # API Token Status
309
+ with gr.Row():
310
+ gr.HTML(f"""
311
+ <div class="status-box" style="background: {'#d4edda' if check_api_token() else '#f8d7da'};
312
+ color: {'#155724' if check_api_token() else '#721c24'};">
313
+ <b>API Status:</b> {'✅ Token configured' if check_api_token() else '❌ Token missing - Add REPLICATE_API_TOKEN in Settings > Repository secrets'}
314
+ </div>
315
+ """)
316
+
317
+ # Tabs
318
+ with gr.Tabs() as tabs:
319
+ # Image Generation Tab
320
+ with gr.TabItem("🎨 Step 1: Generate Image", id=1):
321
+ with gr.Row():
322
+ with gr.Column(scale=3):
323
+ style_prompt = gr.Textbox(
324
+ label="Describe your image",
325
+ placeholder="E.g., A magical forest with glowing mushrooms, fantasy art style",
326
+ lines=3,
327
+ value=""
328
+ )
329
+
330
+ # Example prompts
331
+ gr.Examples(
332
+ examples=[
333
+ ["A majestic dragon flying over a medieval castle at sunset, fantasy art"],
334
+ ["Cyberpunk city street with neon lights, rainy night, blade runner style"],
335
+ ["Cute robot watering flowers in a garden, pixar animation style"],
336
+ ["Northern lights dancing over snowy mountains, photorealistic"],
337
+ ["Steampunk airship floating among clouds, detailed mechanical parts"],
338
+ ],
339
+ inputs=[style_prompt],
340
+ label="💡 Example Prompts"
341
+ )
342
+
343
+ generate_img_btn = gr.Button(
344
+ "🎨 Generate Image",
345
+ variant="primary",
346
+ size="lg"
347
+ )
348
 
349
+ with gr.Column(scale=3):
350
+ output_image = gr.Image(
351
+ label="Generated Image",
352
  type="pil",
353
+ height=400
354
  )
355
+
356
+ img_status = gr.Textbox(
357
+ label="Status",
358
+ interactive=False,
359
+ value="Ready to generate..."
360
  )
361
+
362
+ with gr.Row():
363
+ send_to_video_btn = gr.Button(
364
+ "➡️ Use for Video",
365
+ variant="secondary",
366
+ visible=False
367
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
369
+ # Video Generation Tab
370
+ with gr.TabItem("🎬 Step 2: Generate Video", id=2):
371
+ gr.Markdown("### Transform your static image into a dynamic video!")
372
+
373
+ with gr.Row():
374
+ with gr.Column(scale=3):
375
+ video_input_image = gr.Image(
376
+ type="pil",
377
+ label="Input Image (from Step 1 or upload)",
378
+ height=300
 
 
 
 
 
 
 
 
 
 
 
379
  )
380
 
381
+ video_prompt = gr.Textbox(
382
+ label="Video Motion Description (optional)",
383
+ value="smooth camera pan, gentle movement",
384
+ lines=2
 
 
385
  )
386
+
387
+ with gr.Row():
388
+ video_seed = gr.Slider(
389
+ label="Seed",
390
+ minimum=0,
391
+ maximum=MAX_SEED,
392
+ step=1,
393
+ value=42
394
+ )
395
+ randomize_seed = gr.Checkbox(
396
+ label="Random seed",
397
+ value=True
398
+ )
399
+
400
+ generate_video_btn = gr.Button(
401
+ "🎬 Generate Video (Replicate API)",
402
+ variant="primary",
403
+ size="lg"
404
+ )
405
+
406
+ # GPU Test Button (optional)
407
+ with gr.Accordion("Advanced Options", open=False):
408
+ test_gpu_btn = gr.Button(
409
+ "🖥️ Test GPU Function",
410
+ variant="secondary"
411
+ )
412
 
413
+ with gr.Column(scale=3):
414
+ video_output = gr.Video(
415
+ label="Generated Video",
416
+ autoplay=True
 
 
 
417
  )
418
 
419
+ video_status = gr.Textbox(
420
+ label="Status",
421
+ interactive=False,
422
+ value="Ready to generate video..."
423
  )
424
+
425
+ # Event Handlers
426
+ def on_image_generated(prompt, dummy1=None, dummy2=None):
427
+ img, status, state_img = process_images(prompt, dummy1, dummy2)
428
+ if img:
429
+ return img, status, state_img, gr.update(visible=True)
430
+ return None, status, None, gr.update(visible=False)
431
+
432
+ def send_image_to_video(img):
433
+ if img:
434
+ return img, "✅ Image loaded! Ready to generate video."
435
+ return None, "❌ No image to send."
436
+
437
+ # Connect events
438
+ generate_img_btn.click(
439
+ fn=on_image_generated,
440
+ inputs=[style_prompt],
441
+ outputs=[output_image, img_status, generated_image_state, send_to_video_btn]
442
+ )
443
+
444
+ send_to_video_btn.click(
445
+ fn=send_image_to_video,
446
+ inputs=[generated_image_state],
447
+ outputs=[video_input_image, video_status]
448
+ )
449
+
450
+ # Video generation with Replicate
451
+ generate_video_btn.click(
452
+ fn=generate_video_replicate,
453
+ inputs=[
454
+ video_input_image,
455
+ video_prompt,
456
+ gr.State(30), # steps
457
+ gr.State("blurry, distorted"), # negative prompt
458
+ gr.State(2.0), # duration
459
+ video_seed,
460
+ randomize_seed
461
+ ],
462
+ outputs=[video_output, video_seed, video_status]
463
+ )
464
+
465
+ # GPU test (optional)
466
+ test_gpu_btn.click(
467
+ fn=generate_video_gpu,
468
+ inputs=[
469
+ video_input_image,
470
+ video_prompt,
471
+ gr.State(30),
472
+ gr.State("blurry"),
473
+ gr.State(2.0),
474
+ video_seed,
475
+ randomize_seed
476
+ ],
477
+ outputs=[video_output, video_seed, video_status]
478
+ )
479
+
480
+ # Footer
481
+ gr.HTML("""
482
+ <div style="margin-top: 2rem; padding: 1rem; background: #f8f9fa; border-radius: 10px;">
483
+ <p style="text-align: center; color: #6c757d;">
484
+ Made with ❤️ using Gradio and Replicate API<br>
485
+ <a href="https://replicate.com/" target="_blank">Get your API token</a> |
486
+ <a href="https://huggingface.co/spaces" target="_blank">Hugging Face Spaces</a>
487
+ </p>
488
+ </div>
489
+ """)
490
 
491
+ return demo
 
 
 
 
 
 
 
 
 
 
492
 
493
+ # Launch
494
  if __name__ == "__main__":
495
+ print("=" * 50)
496
+ print("Starting Nano Banana + Video Application")
497
+ print("=" * 50)
498
+
499
+ if check_api_token():
500
+ print("✅ Replicate API token found")
501
+ else:
502
+ print("⚠️ REPLICATE_API_TOKEN not found")
503
+ print("Please add it in Settings > Repository secrets")
504
+
505
+ print("=" * 50)
506
 
507
+ # Create and launch the interface
508
+ demo = create_interface()
509
  demo.launch(
510
+ show_error=True,
511
+ share=False
512
  )