import gradio as gr import replicate import os from PIL import Image import requests from io import BytesIO import tempfile import base64 import spaces import torch import numpy as np import random import gc import time # =========================== # Configuration # =========================== # Set up Replicate API key os.environ['REPLICATE_API_TOKEN'] = os.getenv('REPLICATE_API_TOKEN') # Video Model Configuration MAX_SEED = np.iinfo(np.int32).max FIXED_FPS = 16 default_prompt_i2v = "make this image come alive, smooth animation, cinematic motion" default_negative_prompt = "static, still, blurry, low quality, distorted" # =========================== # Helper Functions # =========================== def check_api_token(): """Check if Replicate API token is set""" token = os.getenv('REPLICATE_API_TOKEN') return token is not None and token.strip() != "" def upload_image_to_hosting(image): """Upload image to hosting service - exact same as example""" # Method 1: Try imgbb.com (most reliable) try: buffered = BytesIO() image.save(buffered, format="PNG") buffered.seek(0) img_base64 = base64.b64encode(buffered.getvalue()).decode() response = requests.post( "https://api.imgbb.com/1/upload", data={ 'key': '6d207e02198a847aa98d0a2a901485a5', 'image': img_base64, } ) if response.status_code == 200: data = response.json() if data.get('success'): return data['data']['url'] except: pass # Method 2: Try 0x0.st (simple and reliable) try: buffered = BytesIO() image.save(buffered, format="PNG") buffered.seek(0) files = {'file': ('image.png', buffered, 'image/png')} response = requests.post("https://0x0.st", files=files) if response.status_code == 200: return response.text.strip() except: pass # Method 3: Fallback to base64 buffered = BytesIO() image.save(buffered, format="PNG") buffered.seek(0) img_base64 = base64.b64encode(buffered.getvalue()).decode() return f"data:image/png;base64,{img_base64}" # =========================== # Image Generation with google/nano-banana # =========================== def process_images(prompt, image1, image2=None): """Process images using google/nano-banana model - exact same logic as example""" if not image1: return None, "Please upload at least one image", None if not check_api_token(): return None, "⚠️ Please set REPLICATE_API_TOKEN in Space settings", None try: image_urls = [] # Upload images url1 = upload_image_to_hosting(image1) image_urls.append(url1) if image2: url2 = upload_image_to_hosting(image2) image_urls.append(url2) print(f"Running google/nano-banana with prompt: {prompt}") print(f"Image URLs: {image_urls}") # Run the model - exactly as in example output = replicate.run( "google/nano-banana", input={ "prompt": prompt, "image_input": image_urls } ) if output is None: return None, "No output received", None # Get the generated image - exact same handling as example img = None # Try method 1 try: if hasattr(output, 'read'): img_data = output.read() img = Image.open(BytesIO(img_data)) except: pass # Try method 2 if img is None: try: if hasattr(output, 'url'): output_url = output.url() response = requests.get(output_url, timeout=30) if response.status_code == 200: img = Image.open(BytesIO(response.content)) except: pass # Try method 3 if img is None: output_url = None if isinstance(output, str): output_url = output elif isinstance(output, list) and len(output) > 0: output_url = output[0] if output_url: response = requests.get(output_url, timeout=30) if response.status_code == 200: img = Image.open(BytesIO(response.content)) if img: return img, "✨ Image generated successfully! You can now create a video.", img else: return None, "Could not process output", None except Exception as e: error_msg = str(e) print(f"Error in process_images: {error_msg}") if "authentication" in error_msg.lower(): return None, "❌ Invalid API token. Please check your REPLICATE_API_TOKEN.", None elif "rate limit" in error_msg.lower(): return None, "⏳ Rate limit reached. Please try again later.", None else: return None, f"Error: {str(e)[:100]}", None # =========================== # Video Generation Functions # =========================== def resize_image_for_video(image: Image.Image, target_width=None, target_height=None): """Resize image for video generation while maintaining aspect ratio""" # Convert RGBA to RGB if image.mode == 'RGBA': background = Image.new('RGB', image.size, (255, 255, 255)) background.paste(image, mask=image.split()[3]) image = background elif image.mode != 'RGB': image = image.convert('RGB') # Get original dimensions orig_width, orig_height = image.size aspect_ratio = orig_width / orig_height # If no target dimensions specified, use original aspect ratio with constraints if target_width is None or target_height is None: # Determine if landscape or portrait if aspect_ratio > 1: # Landscape target_width = min(1024, orig_width) target_height = int(target_width / aspect_ratio) else: # Portrait or square target_height = min(1024, orig_height) target_width = int(target_height * aspect_ratio) # Ensure dimensions are divisible by 8 (required by many models) target_width = (target_width // 8) * 8 target_height = (target_height // 8) * 8 # Minimum size constraints target_width = max(256, target_width) target_height = max(256, target_height) # Resize image resized = image.resize((target_width, target_height), Image.LANCZOS) return resized, target_width, target_height @spaces.GPU(duration=60) def generate_video_gpu( input_image, prompt, steps, negative_prompt, duration_seconds, seed, randomize_seed, maintain_aspect_ratio ): """GPU-accelerated video generation""" try: # Clear GPU memory if torch.cuda.is_available(): torch.cuda.empty_cache() gc.collect() # Simulate processing time.sleep(2) return None, seed, "🎬 GPU test completed successfully" except Exception as e: return None, seed, f"GPU Error: {str(e)[:200]}" def generate_video_replicate( input_image, prompt, steps=30, negative_prompt="", duration_seconds=2.0, seed=42, randomize_seed=False, maintain_aspect_ratio=True ): """Generate video using Replicate API with aspect ratio preservation""" if not check_api_token(): return None, seed, "⚠️ Please set REPLICATE_API_TOKEN" if input_image is None: return None, seed, "Please provide an input image" try: # Get image dimensions while maintaining aspect ratio if maintain_aspect_ratio: resized_image, video_width, video_height = resize_image_for_video(input_image) print(f"Video dimensions: {video_width}x{video_height} (maintaining aspect ratio)") else: # Default landscape dimensions resized_image, video_width, video_height = resize_image_for_video(input_image, 768, 512) print(f"Video dimensions: {video_width}x{video_height} (fixed landscape)") # Upload image img_url = upload_image_to_hosting(resized_image) current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed) print("Generating video with Stable Video Diffusion...") # Use Stable Video Diffusion output = replicate.run( "stability-ai/stable-video-diffusion:3f0457e4619daac51203dedb472816fd4af51f3149fa7a9e0b5ffcf1b8172438", input={ "input_image": img_url, "frames_per_second": FIXED_FPS, "motion_bucket_id": 127, # Controls motion amount (0-255) "cond_aug": 0.02, # Conditioning augmentation "decoding_t": min(14, int(duration_seconds * 7)), # Number of frames "seed": current_seed, "sizing_strategy": "maintain_aspect_ratio" # Preserve aspect ratio } ) if output: # Download video video_url = output if isinstance(output, str) else str(output) response = requests.get(video_url, timeout=60) if response.status_code == 200: with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp_video: tmp_video.write(response.content) return tmp_video.name, current_seed, f"🎬 Video generated! ({video_width}x{video_height})" return None, seed, "Failed to generate video" except Exception as e: error_msg = str(e) if "authentication" in error_msg.lower(): return None, seed, "❌ Invalid API token" else: return None, seed, f"Error: {error_msg[:200]}" # =========================== # Enhanced CSS (same as example) # =========================== css = """ .gradio-container { background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; min-height: 100vh; } .header-container { background: linear-gradient(135deg, #ffd93d 0%, #ffb347 100%); padding: 2.5rem; border-radius: 24px; margin-bottom: 2.5rem; box-shadow: 0 20px 60px rgba(255, 179, 71, 0.25); } .logo-text { font-size: 3.5rem; font-weight: 900; color: #2d3436; text-align: center; margin: 0; letter-spacing: -2px; } .subtitle { color: #2d3436; text-align: center; font-size: 1.2rem; margin-top: 0.5rem; opacity: 0.9; font-weight: 600; } .main-content { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(20px); border-radius: 24px; padding: 2.5rem; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); margin-bottom: 2rem; } .gr-button-primary { background: linear-gradient(135deg, #ffd93d 0%, #ffb347 100%) !important; border: none !important; color: #2d3436 !important; font-weight: 700 !important; font-size: 1.1rem !important; padding: 1.2rem 2rem !important; border-radius: 14px !important; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; text-transform: uppercase; letter-spacing: 1px; width: 100%; margin-top: 1rem !important; } .gr-button-primary:hover { transform: translateY(-3px) !important; box-shadow: 0 15px 40px rgba(255, 179, 71, 0.35) !important; } .gr-button-secondary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; border: none !important; color: white !important; font-weight: 700 !important; font-size: 1.1rem !important; padding: 1.2rem 2rem !important; border-radius: 14px !important; } .section-title { font-size: 1.8rem; font-weight: 800; color: #2d3436; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 3px solid #ffd93d; } .status-text { font-family: 'SF Mono', 'Monaco', monospace; color: #00b894; font-size: 0.9rem; } .image-container { border-radius: 14px !important; overflow: hidden; border: 2px solid #e1e8ed !important; background: #fafbfc !important; } footer { display: none !important; } """ # =========================== # Gradio Interface # =========================== def create_interface(): with gr.Blocks(css=css, theme=gr.themes.Base()) as demo: # Shared state generated_image_state = gr.State(None) # Header with gr.Column(elem_classes="header-container"): gr.HTML("""
AI-Powered Image Style Transfer
""") # API Token Status with gr.Row(): gr.HTML(f"""