ginipick commited on
Commit
7f41a1f
·
verified ·
1 Parent(s): 820e456

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +283 -353
app.py CHANGED
@@ -37,19 +37,11 @@ def check_api_token():
37
  return token is not None and token.strip() != ""
38
 
39
  def upload_image_to_hosting(image):
40
- """Upload image to hosting service"""
 
41
  try:
42
- # Convert to RGB if needed
43
- if image.mode == 'RGBA':
44
- background = Image.new('RGB', image.size, (255, 255, 255))
45
- background.paste(image, mask=image.split()[3])
46
- image = background
47
- elif image.mode != 'RGB':
48
- image = image.convert('RGB')
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
 
@@ -58,32 +50,31 @@ def upload_image_to_hosting(image):
58
  data={
59
  'key': '6d207e02198a847aa98d0a2a901485a5',
60
  'image': img_base64,
61
- },
62
- timeout=30
63
  )
64
 
65
  if response.status_code == 200:
66
  data = response.json()
67
  if data.get('success'):
68
  return data['data']['url']
69
- except Exception as e:
70
- print(f"Upload error: {e}")
71
 
72
- # Try 0x0.st as fallback
73
  try:
74
  buffered = BytesIO()
75
  image.save(buffered, format="PNG")
76
  buffered.seek(0)
77
 
78
  files = {'file': ('image.png', buffered, 'image/png')}
79
- response = requests.post("https://0x0.st", files=files, timeout=30)
80
 
81
  if response.status_code == 200:
82
  return response.text.strip()
83
  except:
84
  pass
85
 
86
- # Final fallback to base64
87
  buffered = BytesIO()
88
  image.save(buffered, format="PNG")
89
  buffered.seek(0)
@@ -91,112 +82,93 @@ def upload_image_to_hosting(image):
91
  return f"data:image/png;base64,{img_base64}"
92
 
93
  # ===========================
94
- # Image Generation Functions
95
  # ===========================
96
 
97
  def process_images(prompt, image1, image2=None):
98
- """Process images using Replicate API with style transfer"""
99
- if not check_api_token():
100
- return None, "⚠️ Please set REPLICATE_API_TOKEN in Space settings (Settings > Repository secrets)", None
101
 
102
- # Check if we have at least one image for style transfer
103
- if image1 is None:
104
- # Pure text-to-image generation
105
- if not prompt or prompt.strip() == "":
106
- return None, "Please enter a prompt or upload an image", None
107
-
108
- try:
109
- print(f"Generating image from text: {prompt}")
110
-
111
- output = replicate.run(
112
- "stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b",
113
- input={
114
- "prompt": prompt + ", high quality, detailed, 8k",
115
- "negative_prompt": "blurry, low quality, distorted, deformed",
116
- "width": 1024,
117
- "height": 1024,
118
- "num_inference_steps": 30,
119
- "guidance_scale": 7.5
120
- }
121
- )
122
-
123
- if output and isinstance(output, list) and len(output) > 0:
124
- img_url = output[0]
125
- response = requests.get(img_url, timeout=30)
126
- if response.status_code == 200:
127
- img = Image.open(BytesIO(response.content))
128
- return img, "✨ Image generated from text!", img
129
-
130
- except Exception as e:
131
- return None, f"Error: {str(e)[:200]}", None
132
 
133
- else:
134
- # Style transfer with images
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  try:
136
- print(f"Processing style transfer with prompt: {prompt}")
137
-
138
- # Upload primary image
139
- url1 = upload_image_to_hosting(image1)
140
-
141
- # If we have two images, combine them for style transfer
142
- if image2:
143
- url2 = upload_image_to_hosting(image2)
144
-
145
- # Use a style transfer model (example using SDXL with image prompt)
146
- # Note: Replace with actual style transfer model if available
147
- output = replicate.run(
148
- "stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b",
149
- input={
150
- "prompt": f"{prompt}, style fusion, artistic blend",
151
- "negative_prompt": "blurry, low quality, distorted",
152
- "width": 1024,
153
- "height": 1024,
154
- "num_inference_steps": 30,
155
- "guidance_scale": 7.5,
156
- # Some models support image inputs for style
157
- # "image": url1, # Uncomment if model supports
158
- # "style_image": url2, # Uncomment if model supports
159
- }
160
- )
161
-
162
- status_msg = "✨ Style transfer completed with both images!"
163
- else:
164
- # Single image enhancement/modification
165
- output = replicate.run(
166
- "tencentarc/gfpgan:9283608cc6b7be6b65a8e44983db012355fde4132009bf99d976b2f0896856a3",
167
- input={
168
- "img": url1,
169
- "version": "v1.4",
170
- "scale": 2
171
- }
172
- )
173
-
174
- status_msg = "✨ Image enhanced successfully!"
175
 
176
- # Process output
177
- if output:
178
- if isinstance(output, list) and len(output) > 0:
179
- img_url = output[0]
180
- elif isinstance(output, str):
181
- img_url = output
182
- else:
183
- img_url = str(output)
184
-
185
- response = requests.get(img_url, timeout=30)
186
  if response.status_code == 200:
187
  img = Image.open(BytesIO(response.content))
188
- return img, status_msg, img
189
-
190
- return None, "Failed to process images", None
191
-
192
- except Exception as e:
193
- error_msg = str(e)
194
- if "authentication" in error_msg.lower():
195
- return None, "❌ Invalid API token. Please check your REPLICATE_API_TOKEN.", None
196
- elif "rate limit" in error_msg.lower():
197
- return None, "⏳ Rate limit reached. Please try again later.", None
198
- else:
199
- return None, f"Error: {error_msg[:200]}", None
 
 
 
 
200
 
201
  # ===========================
202
  # Video Generation Functions
@@ -253,16 +225,15 @@ def generate_video_gpu(
253
  ):
254
  """GPU-accelerated video generation"""
255
  try:
256
- # This function runs on GPU
257
  # Clear GPU memory
258
  if torch.cuda.is_available():
259
  torch.cuda.empty_cache()
260
  gc.collect()
261
 
262
- # Simulate video generation for testing
263
  time.sleep(2)
264
 
265
- return None, seed, "🎬 GPU test completed (actual video generation requires specific models)"
266
 
267
  except Exception as e:
268
  return None, seed, f"GPU Error: {str(e)[:200]}"
@@ -300,7 +271,7 @@ def generate_video_replicate(
300
 
301
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
302
 
303
- print("Generating video with Replicate...")
304
 
305
  # Use Stable Video Diffusion
306
  output = replicate.run(
@@ -324,7 +295,7 @@ def generate_video_replicate(
324
  if response.status_code == 200:
325
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp_video:
326
  tmp_video.write(response.content)
327
- return tmp_video.name, current_seed, f"🎬 Video generated! ({video_width}x{video_height}, aspect ratio preserved)"
328
 
329
  return None, seed, "Failed to generate video"
330
 
@@ -336,71 +307,94 @@ def generate_video_replicate(
336
  return None, seed, f"Error: {error_msg[:200]}"
337
 
338
  # ===========================
339
- # CSS Styling
340
  # ===========================
341
 
342
  css = """
343
  .gradio-container {
344
- max-width: 1400px !important;
345
- margin: 0 auto !important;
346
- padding: 20px !important;
347
  }
348
  .header-container {
349
- background: linear-gradient(135deg, #ffd93d 0%, #ffb347 50%, #ff6b6b 100%);
350
- padding: 3rem;
351
- border-radius: 20px;
352
- margin-bottom: 2rem;
353
- text-align: center;
354
- box-shadow: 0 20px 40px rgba(0,0,0,0.1);
355
  }
356
  .logo-text {
357
  font-size: 3.5rem;
358
  font-weight: 900;
359
  color: #2d3436;
 
360
  margin: 0;
361
- text-shadow: 3px 3px 6px rgba(0,0,0,0.1);
362
  letter-spacing: -2px;
363
  }
364
  .subtitle {
365
  color: #2d3436;
366
- font-size: 1.3rem;
 
367
  margin-top: 0.5rem;
 
368
  font-weight: 600;
369
  }
370
- .image-upload-container {
371
- border: 3px dashed #ffd93d;
372
- border-radius: 15px;
373
- padding: 20px;
374
- background: #fffef5;
375
- transition: all 0.3s ease;
376
- }
377
- .image-upload-container:hover {
378
- border-color: #ffb347;
379
- background: #fff9e6;
380
- }
381
- .status-box {
382
- padding: 12px;
383
- border-radius: 10px;
384
- margin: 15px 0;
385
- font-weight: 500;
386
- }
387
- .gr-button {
388
- transition: all 0.3s ease;
389
- font-weight: 600 !important;
390
- }
391
- .gr-button:hover {
392
- transform: translateY(-2px);
393
- box-shadow: 0 8px 20px rgba(0,0,0,0.15);
394
  }
395
  .gr-button-primary {
396
- background: linear-gradient(135deg, #ffd93d, #ffb347) !important;
397
- color: #2d3436 !important;
398
  border: none !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  }
400
  .gr-button-secondary {
401
- background: linear-gradient(135deg, #667eea, #764ba2) !important;
402
- color: white !important;
403
  border: none !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  }
405
  """
406
 
@@ -409,219 +403,197 @@ css = """
409
  # ===========================
410
 
411
  def create_interface():
412
- with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
413
  # Shared state
414
  generated_image_state = gr.State(None)
415
 
416
  # Header
417
- gr.HTML("""
418
- <div class="header-container">
419
  <h1 class="logo-text">🍌 Nano Banana + Video</h1>
420
- <p class="subtitle">Style Transfer & AI Video Generation</p>
421
- <div style="margin-top: 1rem;">
422
- <p style="color: #2d3436; font-size: 1rem; opacity: 0.9;">
423
- Upload up to 2 images for style transfer → Generate amazing visuals → Convert to video
424
- </p>
 
 
 
425
  </div>
426
- </div>
427
- """)
428
 
429
  # API Token Status
430
  with gr.Row():
431
  gr.HTML(f"""
432
  <div class="status-box" style="background: {'#d4edda' if check_api_token() else '#f8d7da'};
433
- color: {'#155724' if check_api_token() else '#721c24'};">
434
- <b>API Status:</b> {'✅ Replicate token configured' if check_api_token() else '❌ Token missing - Add REPLICATE_API_TOKEN in Settings > Repository secrets'}
 
435
  </div>
436
  """)
437
 
438
  # Tabs
439
- with gr.Tabs() as tabs:
440
- # Image Generation Tab
441
- with gr.TabItem("🎨 Step 1: Generate/Transform Image", id=1):
442
- with gr.Row():
443
- with gr.Column(scale=1):
444
- style_prompt = gr.Textbox(
445
- label="✏️ Style Description",
446
- placeholder="Describe the style or transformation you want...",
447
- lines=3,
448
- value="Transform into a magical fantasy scene with vibrant colors"
449
- )
450
-
451
- with gr.Column(elem_classes="image-upload-container"):
452
- gr.Markdown("### 📤 Upload Images (Optional)")
453
- gr.Markdown("Upload 1-2 images for style transfer, or leave empty for text-to-image")
454
 
455
- with gr.Row():
456
  image1 = gr.Image(
457
  label="Primary Image",
458
  type="pil",
459
- height=200
 
460
  )
461
  image2 = gr.Image(
462
  label="Secondary Image (Optional)",
463
  type="pil",
464
- height=200
 
465
  )
 
 
 
 
 
 
466
 
467
- generate_img_btn = gr.Button(
468
- "🎨 Generate/Transform Image",
469
- variant="primary",
470
- size="lg"
471
- )
472
-
473
- # Examples
474
- gr.Examples(
475
- examples=[
476
- ["Magical forest with glowing mushrooms, fantasy art style", None, None],
477
- ["Cyberpunk city with neon lights, blade runner style", None, None],
478
- ["Studio Ghibli style peaceful countryside", None, None],
479
- ["Steampunk mechanical dragon, brass and copper", None, None],
480
- ],
481
- inputs=[style_prompt, image1, image2],
482
- label="💡 Quick Prompts"
483
- )
484
-
485
- with gr.Column(scale=1):
486
- output_image = gr.Image(
487
- label="✨ Generated Result",
488
- type="pil",
489
- height=450
490
- )
491
-
492
- img_status = gr.Textbox(
493
- label="Status",
494
- interactive=False,
495
- value="Ready to generate..."
496
- )
497
-
498
- send_to_video_btn = gr.Button(
499
- "➡️ Send to Video Generation",
500
- variant="secondary",
501
- size="lg",
502
- visible=False
503
- )
504
-
505
- # Video Generation Tab
506
- with gr.TabItem("🎬 Step 2: Generate Video", id=2):
507
- gr.Markdown("### 🎥 Bring your image to life with AI-powered animation!")
508
-
509
- with gr.Row():
510
- with gr.Column(scale=1):
511
- video_input_image = gr.Image(
512
- type="pil",
513
- label="📸 Input Image",
514
- height=350
515
- )
516
-
517
- video_prompt = gr.Textbox(
518
- label="🎬 Animation Style",
519
- value=default_prompt_i2v,
520
- lines=2,
521
- placeholder="Describe how you want the image to move..."
522
- )
523
-
524
- with gr.Row():
525
- duration_input = gr.Slider(
526
- minimum=1.0,
527
- maximum=4.0,
528
- step=0.5,
529
- value=2.0,
530
- label="⏱️ Duration (seconds)"
531
  )
532
 
533
- maintain_aspect = gr.Checkbox(
534
- label="🖼️ Maintain Original Aspect Ratio",
535
- value=True
 
 
 
536
  )
537
-
538
- with gr.Accordion("⚙️ Advanced Settings", open=False):
539
- video_negative_prompt = gr.Textbox(
540
- label="Negative Prompt",
541
- value=default_negative_prompt,
542
- lines=2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
  )
544
 
545
  with gr.Row():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
546
  video_seed = gr.Slider(
547
- label="Seed",
548
- minimum=0,
549
- maximum=MAX_SEED,
550
- step=1,
551
  value=42
552
  )
553
  randomize_seed = gr.Checkbox(
554
- label="🎲 Random seed",
555
  value=True
556
  )
 
 
 
 
 
 
 
557
 
558
- steps_slider = gr.Slider(
559
- minimum=10,
560
- maximum=50,
561
- step=5,
562
- value=30,
563
- label="Quality Steps"
564
  )
565
 
566
- generate_video_btn = gr.Button(
567
- "🎬 Generate Video",
568
- variant="primary",
569
- size="lg"
570
- )
571
-
572
- # GPU Test Button (hidden by default)
573
- with gr.Accordion("🧪 Developer Options", open=False):
574
- test_gpu_btn = gr.Button(
575
- "🖥️ Test GPU Function",
576
- variant="secondary"
577
  )
578
-
579
- with gr.Column(scale=1):
580
- video_output = gr.Video(
581
- label="🎬 Generated Video",
582
- autoplay=True,
583
- height=450
584
- )
585
-
586
- video_status = gr.Textbox(
587
- label="Status",
588
- interactive=False,
589
- value="Ready to generate video..."
590
- )
591
-
592
- gr.Markdown("""
593
- ### 💡 Tips for better videos:
594
- - **Aspect Ratio**: Enable "Maintain Original Aspect Ratio" for best results
595
- - **Duration**: Start with 2 seconds for faster generation
596
- - **Motion**: Describe smooth, simple movements for stability
597
- """)
598
 
599
  # Event Handlers
600
  def on_image_generated(prompt, img1, img2):
601
  img, status, state_img = process_images(prompt, img1, img2)
602
  if img:
603
  return img, status, state_img, gr.update(visible=True)
604
- return None, status, None, gr.update(visible=False)
605
 
606
  def send_image_to_video(img):
607
  if img:
608
- return img, "Image loaded! Ready to generate video with original aspect ratio."
609
- return None, "No image to send."
610
 
611
- # Connect events
612
  generate_img_btn.click(
613
  fn=on_image_generated,
614
  inputs=[style_prompt, image1, image2],
615
  outputs=[output_image, img_status, generated_image_state, send_to_video_btn]
616
  )
617
 
 
618
  send_to_video_btn.click(
619
  fn=send_image_to_video,
620
  inputs=[generated_image_state],
621
  outputs=[video_input_image, video_status]
622
  )
623
 
624
- # Video generation with Replicate
625
  generate_video_btn.click(
626
  fn=generate_video_replicate,
627
  inputs=[
@@ -636,50 +608,13 @@ def create_interface():
636
  ],
637
  outputs=[video_output, video_seed, video_status]
638
  )
639
-
640
- # GPU test (optional)
641
- test_gpu_btn.click(
642
- fn=generate_video_gpu,
643
- inputs=[
644
- video_input_image,
645
- video_prompt,
646
- steps_slider,
647
- video_negative_prompt,
648
- duration_input,
649
- video_seed,
650
- randomize_seed,
651
- maintain_aspect
652
- ],
653
- outputs=[video_output, video_seed, video_status]
654
- )
655
-
656
- # Footer
657
- gr.HTML("""
658
- <div style="margin-top: 3rem; padding: 2rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px;">
659
- <p style="text-align: center; color: white; font-size: 1.1rem;">
660
- <b>🚀 Powered by Replicate API & Hugging Face Spaces</b><br>
661
- <span style="font-size: 0.9rem; opacity: 0.9;">
662
- Transform your ideas into stunning visuals and animations
663
- </span>
664
- </p>
665
- <p style="text-align: center; margin-top: 1rem;">
666
- <a href="https://replicate.com/" target="_blank" style="color: #ffd93d; text-decoration: none; margin: 0 10px;">
667
- 📝 Get Replicate Token
668
- </a>
669
- |
670
- <a href="https://huggingface.co/spaces" target="_blank" style="color: #ffd93d; text-decoration: none; margin: 0 10px;">
671
- 🤗 Hugging Face Spaces
672
- </a>
673
- </p>
674
- </div>
675
- """)
676
 
677
  return demo
678
 
679
  # Launch
680
  if __name__ == "__main__":
681
  print("=" * 50)
682
- print("🍌 Nano Banana + Video Application")
683
  print("=" * 50)
684
 
685
  if check_api_token():
@@ -689,11 +624,6 @@ if __name__ == "__main__":
689
  print("Please add it in Settings > Repository secrets")
690
 
691
  print("=" * 50)
692
- print("Features:")
693
- print("- Upload up to 2 images for style transfer")
694
- print("- Text-to-image generation")
695
- print("- Video generation with aspect ratio preservation")
696
- print("=" * 50)
697
 
698
  # Create and launch the interface
699
  demo = create_interface()
 
37
  return token is not None and token.strip() != ""
38
 
39
  def upload_image_to_hosting(image):
40
+ """Upload image to hosting service - exact same as example"""
41
+ # Method 1: Try imgbb.com (most reliable)
42
  try:
 
 
 
 
 
 
 
 
 
43
  buffered = BytesIO()
44
+ image.save(buffered, format="PNG")
45
  buffered.seek(0)
46
  img_base64 = base64.b64encode(buffered.getvalue()).decode()
47
 
 
50
  data={
51
  'key': '6d207e02198a847aa98d0a2a901485a5',
52
  'image': img_base64,
53
+ }
 
54
  )
55
 
56
  if response.status_code == 200:
57
  data = response.json()
58
  if data.get('success'):
59
  return data['data']['url']
60
+ except:
61
+ pass
62
 
63
+ # Method 2: Try 0x0.st (simple and reliable)
64
  try:
65
  buffered = BytesIO()
66
  image.save(buffered, format="PNG")
67
  buffered.seek(0)
68
 
69
  files = {'file': ('image.png', buffered, 'image/png')}
70
+ response = requests.post("https://0x0.st", files=files)
71
 
72
  if response.status_code == 200:
73
  return response.text.strip()
74
  except:
75
  pass
76
 
77
+ # Method 3: Fallback to base64
78
  buffered = BytesIO()
79
  image.save(buffered, format="PNG")
80
  buffered.seek(0)
 
82
  return f"data:image/png;base64,{img_base64}"
83
 
84
  # ===========================
85
+ # Image Generation with google/nano-banana
86
  # ===========================
87
 
88
  def process_images(prompt, image1, image2=None):
89
+ """Process images using google/nano-banana model - exact same logic as example"""
90
+ if not image1:
91
+ return None, "Please upload at least one image", None
92
 
93
+ if not check_api_token():
94
+ return None, "⚠️ Please set REPLICATE_API_TOKEN in Space settings", None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
+ try:
97
+ image_urls = []
98
+
99
+ # Upload images
100
+ url1 = upload_image_to_hosting(image1)
101
+ image_urls.append(url1)
102
+
103
+ if image2:
104
+ url2 = upload_image_to_hosting(image2)
105
+ image_urls.append(url2)
106
+
107
+ print(f"Running google/nano-banana with prompt: {prompt}")
108
+ print(f"Image URLs: {image_urls}")
109
+
110
+ # Run the model - exactly as in example
111
+ output = replicate.run(
112
+ "google/nano-banana",
113
+ input={
114
+ "prompt": prompt,
115
+ "image_input": image_urls
116
+ }
117
+ )
118
+
119
+ if output is None:
120
+ return None, "No output received", None
121
+
122
+ # Get the generated image - exact same handling as example
123
+ img = None
124
+
125
+ # Try method 1
126
  try:
127
+ if hasattr(output, 'read'):
128
+ img_data = output.read()
129
+ img = Image.open(BytesIO(img_data))
130
+ except:
131
+ pass
132
+
133
+ # Try method 2
134
+ if img is None:
135
+ try:
136
+ if hasattr(output, 'url'):
137
+ output_url = output.url()
138
+ response = requests.get(output_url, timeout=30)
139
+ if response.status_code == 200:
140
+ img = Image.open(BytesIO(response.content))
141
+ except:
142
+ pass
143
+
144
+ # Try method 3
145
+ if img is None:
146
+ output_url = None
147
+ if isinstance(output, str):
148
+ output_url = output
149
+ elif isinstance(output, list) and len(output) > 0:
150
+ output_url = output[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
+ if output_url:
153
+ response = requests.get(output_url, timeout=30)
 
 
 
 
 
 
 
 
154
  if response.status_code == 200:
155
  img = Image.open(BytesIO(response.content))
156
+
157
+ if img:
158
+ return img, " Image generated successfully! You can now create a video.", img
159
+ else:
160
+ return None, "Could not process output", None
161
+
162
+ except Exception as e:
163
+ error_msg = str(e)
164
+ print(f"Error in process_images: {error_msg}")
165
+
166
+ if "authentication" in error_msg.lower():
167
+ return None, " Invalid API token. Please check your REPLICATE_API_TOKEN.", None
168
+ elif "rate limit" in error_msg.lower():
169
+ return None, "⏳ Rate limit reached. Please try again later.", None
170
+ else:
171
+ return None, f"Error: {str(e)[:100]}", None
172
 
173
  # ===========================
174
  # Video Generation Functions
 
225
  ):
226
  """GPU-accelerated video generation"""
227
  try:
 
228
  # Clear GPU memory
229
  if torch.cuda.is_available():
230
  torch.cuda.empty_cache()
231
  gc.collect()
232
 
233
+ # Simulate processing
234
  time.sleep(2)
235
 
236
+ return None, seed, "🎬 GPU test completed successfully"
237
 
238
  except Exception as e:
239
  return None, seed, f"GPU Error: {str(e)[:200]}"
 
271
 
272
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
273
 
274
+ print("Generating video with Stable Video Diffusion...")
275
 
276
  # Use Stable Video Diffusion
277
  output = replicate.run(
 
295
  if response.status_code == 200:
296
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp_video:
297
  tmp_video.write(response.content)
298
+ return tmp_video.name, current_seed, f"🎬 Video generated! ({video_width}x{video_height})"
299
 
300
  return None, seed, "Failed to generate video"
301
 
 
307
  return None, seed, f"Error: {error_msg[:200]}"
308
 
309
  # ===========================
310
+ # Enhanced CSS (same as example)
311
  # ===========================
312
 
313
  css = """
314
  .gradio-container {
315
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
316
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
317
+ min-height: 100vh;
318
  }
319
  .header-container {
320
+ background: linear-gradient(135deg, #ffd93d 0%, #ffb347 100%);
321
+ padding: 2.5rem;
322
+ border-radius: 24px;
323
+ margin-bottom: 2.5rem;
324
+ box-shadow: 0 20px 60px rgba(255, 179, 71, 0.25);
 
325
  }
326
  .logo-text {
327
  font-size: 3.5rem;
328
  font-weight: 900;
329
  color: #2d3436;
330
+ text-align: center;
331
  margin: 0;
 
332
  letter-spacing: -2px;
333
  }
334
  .subtitle {
335
  color: #2d3436;
336
+ text-align: center;
337
+ font-size: 1.2rem;
338
  margin-top: 0.5rem;
339
+ opacity: 0.9;
340
  font-weight: 600;
341
  }
342
+ .main-content {
343
+ background: rgba(255, 255, 255, 0.95);
344
+ backdrop-filter: blur(20px);
345
+ border-radius: 24px;
346
+ padding: 2.5rem;
347
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
348
+ margin-bottom: 2rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  }
350
  .gr-button-primary {
351
+ background: linear-gradient(135deg, #ffd93d 0%, #ffb347 100%) !important;
 
352
  border: none !important;
353
+ color: #2d3436 !important;
354
+ font-weight: 700 !important;
355
+ font-size: 1.1rem !important;
356
+ padding: 1.2rem 2rem !important;
357
+ border-radius: 14px !important;
358
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
359
+ text-transform: uppercase;
360
+ letter-spacing: 1px;
361
+ width: 100%;
362
+ margin-top: 1rem !important;
363
+ }
364
+ .gr-button-primary:hover {
365
+ transform: translateY(-3px) !important;
366
+ box-shadow: 0 15px 40px rgba(255, 179, 71, 0.35) !important;
367
  }
368
  .gr-button-secondary {
369
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
 
370
  border: none !important;
371
+ color: white !important;
372
+ font-weight: 700 !important;
373
+ font-size: 1.1rem !important;
374
+ padding: 1.2rem 2rem !important;
375
+ border-radius: 14px !important;
376
+ }
377
+ .section-title {
378
+ font-size: 1.8rem;
379
+ font-weight: 800;
380
+ color: #2d3436;
381
+ margin-bottom: 1rem;
382
+ padding-bottom: 0.5rem;
383
+ border-bottom: 3px solid #ffd93d;
384
+ }
385
+ .status-text {
386
+ font-family: 'SF Mono', 'Monaco', monospace;
387
+ color: #00b894;
388
+ font-size: 0.9rem;
389
+ }
390
+ .image-container {
391
+ border-radius: 14px !important;
392
+ overflow: hidden;
393
+ border: 2px solid #e1e8ed !important;
394
+ background: #fafbfc !important;
395
+ }
396
+ footer {
397
+ display: none !important;
398
  }
399
  """
400
 
 
403
  # ===========================
404
 
405
  def create_interface():
406
+ with gr.Blocks(css=css, theme=gr.themes.Base()) as demo:
407
  # Shared state
408
  generated_image_state = gr.State(None)
409
 
410
  # Header
411
+ with gr.Column(elem_classes="header-container"):
412
+ gr.HTML("""
413
  <h1 class="logo-text">🍌 Nano Banana + Video</h1>
414
+ <p class="subtitle">AI-Powered Image Style Transfer with Video Generation</p>
415
+ <div style="display: flex; justify-content: center; align-items: center; gap: 10px; margin-top: 20px;">
416
+ <a href="https://huggingface.co/spaces/openfree/Nano-Banana-Upscale" target="_blank">
417
+ <img src="https://img.shields.io/static/v1?label=NANO%20BANANA&message=UPSCALE&color=%230000ff&labelColor=%23800080&logo=GOOGLE&logoColor=white&style=for-the-badge" alt="Nano Banana Upscale">
418
+ </a>
419
+ <a href="https://discord.gg/openfreeai" target="_blank">
420
+ <img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="Discord Openfree AI">
421
+ </a>
422
  </div>
423
+ """)
 
424
 
425
  # API Token Status
426
  with gr.Row():
427
  gr.HTML(f"""
428
  <div class="status-box" style="background: {'#d4edda' if check_api_token() else '#f8d7da'};
429
+ color: {'#155724' if check_api_token() else '#721c24'};
430
+ padding: 12px; border-radius: 10px; margin: 15px 0;">
431
+ <b>API Status:</b> {'✅ Token configured' if check_api_token() else '❌ Token missing - Add REPLICATE_API_TOKEN in Settings > Repository secrets'}
432
  </div>
433
  """)
434
 
435
  # Tabs
436
+ with gr.Tabs():
437
+ # Tab 1: Image Generation
438
+ with gr.TabItem("🎨 Step 1: Generate Image"):
439
+ with gr.Column(elem_classes="main-content"):
440
+ gr.HTML('<h2 class="section-title">🎨 Image Style Transfer</h2>')
441
+
442
+ with gr.Row(equal_height=True):
443
+ with gr.Column(scale=1):
444
+ style_prompt = gr.Textbox(
445
+ label="Style Description",
446
+ placeholder="Describe your style...",
447
+ lines=3,
448
+ value="Make the sheets in the style of the logo. Make the scene natural.",
449
+ )
 
450
 
451
+ with gr.Row(equal_height=True):
452
  image1 = gr.Image(
453
  label="Primary Image",
454
  type="pil",
455
+ height=200,
456
+ elem_classes="image-container"
457
  )
458
  image2 = gr.Image(
459
  label="Secondary Image (Optional)",
460
  type="pil",
461
+ height=200,
462
+ elem_classes="image-container"
463
  )
464
+
465
+ generate_img_btn = gr.Button(
466
+ "Generate Magic ✨",
467
+ variant="primary",
468
+ size="lg"
469
+ )
470
 
471
+ with gr.Column(scale=1):
472
+ output_image = gr.Image(
473
+ label="Generated Result",
474
+ type="pil",
475
+ height=420,
476
+ elem_classes="image-container"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  )
478
 
479
+ img_status = gr.Textbox(
480
+ label="Status",
481
+ interactive=False,
482
+ lines=1,
483
+ elem_classes="status-text",
484
+ value="Ready to generate..."
485
  )
486
+
487
+ send_to_video_btn = gr.Button(
488
+ "Send to Video Generation →",
489
+ variant="secondary",
490
+ size="lg",
491
+ visible=False
492
+ )
493
+
494
+ # Tab 2: Video Generation
495
+ with gr.TabItem("🎬 Step 2: Generate Video"):
496
+ with gr.Column(elem_classes="main-content"):
497
+ gr.HTML('<h2 class="section-title">🎬 Video Generation from Image</h2>')
498
+
499
+ with gr.Row():
500
+ with gr.Column():
501
+ video_input_image = gr.Image(
502
+ type="pil",
503
+ label="Input Image (from Step 1 or upload new)",
504
+ elem_classes="image-container"
505
+ )
506
+ video_prompt = gr.Textbox(
507
+ label="Animation Prompt",
508
+ value=default_prompt_i2v,
509
+ lines=3
510
  )
511
 
512
  with gr.Row():
513
+ duration_input = gr.Slider(
514
+ minimum=1.0,
515
+ maximum=4.0,
516
+ step=0.5,
517
+ value=2.0,
518
+ label="Duration (seconds)"
519
+ )
520
+
521
+ maintain_aspect = gr.Checkbox(
522
+ label="Maintain Original Aspect Ratio",
523
+ value=True
524
+ )
525
+
526
+ with gr.Accordion("Advanced Settings", open=False):
527
+ video_negative_prompt = gr.Textbox(
528
+ label="Negative Prompt",
529
+ value=default_negative_prompt,
530
+ lines=3
531
+ )
532
  video_seed = gr.Slider(
533
+ label="Seed",
534
+ minimum=0,
535
+ maximum=MAX_SEED,
536
+ step=1,
537
  value=42
538
  )
539
  randomize_seed = gr.Checkbox(
540
+ label="Randomize seed",
541
  value=True
542
  )
543
+ steps_slider = gr.Slider(
544
+ minimum=10,
545
+ maximum=50,
546
+ step=5,
547
+ value=30,
548
+ label="Quality Steps"
549
+ )
550
 
551
+ generate_video_btn = gr.Button(
552
+ "Generate Video 🎬",
553
+ variant="primary",
554
+ size="lg"
 
 
555
  )
556
 
557
+ with gr.Column():
558
+ video_output = gr.Video(
559
+ label="Generated Video",
560
+ autoplay=True
561
+ )
562
+ video_status = gr.Textbox(
563
+ label="Status",
564
+ interactive=False,
565
+ lines=1,
566
+ elem_classes="status-text",
567
+ value="Ready to generate video..."
568
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
569
 
570
  # Event Handlers
571
  def on_image_generated(prompt, img1, img2):
572
  img, status, state_img = process_images(prompt, img1, img2)
573
  if img:
574
  return img, status, state_img, gr.update(visible=True)
575
+ return img, status, state_img, gr.update(visible=False)
576
 
577
  def send_image_to_video(img):
578
  if img:
579
+ return img, "Image loaded! Ready to generate video."
580
+ return None, "No image to send."
581
 
582
+ # Image generation events
583
  generate_img_btn.click(
584
  fn=on_image_generated,
585
  inputs=[style_prompt, image1, image2],
586
  outputs=[output_image, img_status, generated_image_state, send_to_video_btn]
587
  )
588
 
589
+ # Send to video tab
590
  send_to_video_btn.click(
591
  fn=send_image_to_video,
592
  inputs=[generated_image_state],
593
  outputs=[video_input_image, video_status]
594
  )
595
 
596
+ # Video generation events
597
  generate_video_btn.click(
598
  fn=generate_video_replicate,
599
  inputs=[
 
608
  ],
609
  outputs=[video_output, video_seed, video_status]
610
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
611
 
612
  return demo
613
 
614
  # Launch
615
  if __name__ == "__main__":
616
  print("=" * 50)
617
+ print("Starting Nano Banana + Video Application")
618
  print("=" * 50)
619
 
620
  if check_api_token():
 
624
  print("Please add it in Settings > Repository secrets")
625
 
626
  print("=" * 50)
 
 
 
 
 
627
 
628
  # Create and launch the interface
629
  demo = create_interface()