import os import sys import json import time import logging import tempfile from pathlib import Path from typing import Optional from datetime import datetime import requests import gradio as gr from PIL import Image, ImageDraw, ImageFont import numpy as np # Backend API URL BACKEND_URL = os.environ.get("BACKEND_URL", "http://localhost:8081") logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s') logger = logging.getLogger("LegionFrontend") # --- Dark Theme CSS --- LEGION_CSS = """ :root { --primary: #ff6b35; --primary-dark: #e55a2b; --bg-dark: #0a0a0f; --bg-card: #14141f; --bg-input: #1a1a2e; --text-primary: #e0e0e0; --text-secondary: #8888aa; --border-color: #2a2a3e; --accent: #00d4aa; } body { background-color: var(--bg-dark) !important; color: var(--text-primary) !important; } .gradio-container { background-color: var(--bg-dark) !important; max-width: 1200px !important; margin: 0 auto !important; padding: 20px !important; } h1, h2, h3 { font-family: 'Courier New', monospace !important; text-transform: uppercase !important; letter-spacing: 2px !important; } h1 { color: var(--primary) !important; text-align: center !important; font-size: 2.5em !important; text-shadow: 0 0 20px rgba(255, 107, 53, 0.3) !important; border-bottom: 2px solid var(--border-color) !important; padding-bottom: 15px !important; margin-bottom: 25px !important; } .tabs { background: var(--bg-card) !important; border: 1px solid var(--border-color) !important; border-radius: 12px !important; padding: 15px !important; } button { background: linear-gradient(135deg, var(--primary), var(--primary-dark)) !important; border: none !important; color: white !important; font-weight: bold !important; letter-spacing: 1px !important; transition: all 0.3s ease !important; } button:hover { transform: translateY(-2px) !important; box-shadow: 0 5px 20px rgba(255, 107, 53, 0.4) !important; } input, textarea, select, .dropdown { background: var(--bg-input) !important; border-radius: 8px !important; } .slider input[type="range"] { accent-color: var(--primary) !important; } .video-container { overflow: hidden !important; } .status-badge { background: var(--accent) !important; color: black !important; padding: 4px 12px !important; border-radius: 20px !important; font-size: 0.8em !important; } .footer { color: var(--text-secondary) !important; border-top: 1px solid var(--border-color) !important; margin-top: 30px !important; } """ # ============================================================ # API Helper Functions # ============================================================ def call_api_status() -> dict: try: resp = requests.get(f"{BACKEND_URL}/api/status", timeout=5) return resp.json() except Exception as e: return {"status": "error", "detail": str(e)} def call_api_models() -> list: try: resp = requests.get(f"{BACKEND_URL}/api/models", timeout=5) data = resp.json() return data.get("models", []) except Exception as e: return [{"name": f"Error: {e}", "type": "error"}] # ============================================================ # Generation Functions # ============================================================ def generate_text_video( prompt: str, negative_prompt: str, num_frames: int, width: int, height: int, inference_steps: int, guidance_scale: float, watermark_strength: float, progress=gr.Progress(), ) -> Optional[str]: try: progress(0, desc="Connecting to API...") payload = { "prompt": prompt, "negative_prompt": negative_prompt, "num_frames": num_frames, "width": width, "height": height, "num_inference_steps": inference_steps, "guidance_scale": guidance_scale, "watermark_strength": watermark_strength, } progress(0.2, desc="Generating video...") resp = requests.post( f"{BACKEND_URL}/api/generate/text", json=payload, timeout=600, ) if resp.status_code != 200: return f"Error: {resp.text}" timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_path = f"/tmp/legion_t2v_{timestamp}.mp4" with open(output_path, "wb") as f: f.write(resp.content) progress(1.0, desc="Done!") return output_path except Exception as e: return f"Error: {str(e)}" def generate_image_video( image_file, prompt: str, negative_prompt: str, num_frames: int, width: int, height: int, inference_steps: int, guidance_scale: float, watermark_strength: float, progress=gr.Progress(), ) -> Optional[str]: try: if image_file is None: return "Error: No image uploaded" progress(0.2, desc="Uploading image and generating...") with open(image_file, "rb") as f: files = {"file": f} data = { "prompt": prompt, "negative_prompt": negative_prompt, "num_frames": str(num_frames), "width": str(width), "height": str(height), "num_inference_steps": str(inference_steps), "guidance_scale": str(guidance_scale), "watermark_strength": str(watermark_strength), } resp = requests.post( f"{BACKEND_URL}/api/generate/image", files=files, data=data, timeout=600, ) if resp.status_code != 200: return f"Error: {resp.text}" timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_path = f"/tmp/legion_i2v_{timestamp}.mp4" with open(output_path, "wb") as f: f.write(resp.content) progress(1.0, desc="Done!") return output_path except Exception as e: return f"Error: {str(e)}" def preview_watermark(text: str, position: str, font_size: int, opacity: float) -> Optional[str]: try: frame = Image.new("RGB", (480, 480), (20, 20, 35)) draw = ImageDraw.Draw(frame) for i in range(0, 480, 20): draw.line([(i, 0), (i, 480)], fill=(30, 30, 50), width=1) draw.line([(0, i), (480, i)], fill=(30, 30, 50), width=1) try: font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", font_size) except: font = ImageFont.load_default() w, h = frame.size bbox = draw.textbbox((0, 0), text, font=font) text_w = bbox[2] - bbox[0] text_h = bbox[3] - bbox[1] margin = 15 pos_map = { "top-left": (margin, margin), "top-right": (w - text_w - margin, margin), "bottom-left": (margin, h - text_h - margin), "center": ((w - text_w) // 2, (h - text_h) // 2), "bottom-right": (w - text_w - margin, h - text_h - margin), } x, y = pos_map.get(position, pos_map["bottom-right"]) draw.rectangle( [x - 10, y - 10, x + text_w + 10, y + text_h + 10], fill=(0, 0, 0, int(40 * opacity)) ) draw.text((x, y), text, font=font, fill=(255, 255, 255, int(255 * opacity))) preview_path = "/tmp/qwatermark_preview.png" frame.save(preview_path) return preview_path except Exception as e: return None # ============================================================ # BUILD GRADIO INTERFACE # ============================================================ with gr.Blocks( css=LEGION_CSS, title="LEGION VIDEO GENERATION", theme=gr.themes.Soft( primary_hue="orange", secondary_hue="blue", neutral_hue="slate", ), ) as app: # Header gr.HTML("""
The Ultimate AI Video Engine — Text-to-Video & Image-to-Video