Spaces:
Runtime error
Runtime error
| #!/usr/bin/env python3 | |
| """ | |
| AI Creative Production Suite | |
| A modular Gradio Space for AI-powered creative content production. | |
| Tools included: | |
| - Image Generator (Text-to-Image via Stable Diffusion / FLUX) | |
| - Image Enhancer (Upscale + skin texture enhancement) | |
| - Pose Editor (Generate multi-view character images) | |
| - Image Variation Generator (Style variations from reference) | |
| - Video Generator (Text-to-Video) | |
| - Audio Generator (Music + Sound Effects — NO voice cloning) | |
| - Plot/Script Generator (Scene-by-scene creative writing) | |
| - Film Editor (Compose scenes into final film) | |
| - Prompt Helper (Enhance prompts with style tags) | |
| Safety controls built-in: | |
| - Content moderation on all text inputs | |
| - Visible + invisible watermarking on all outputs | |
| - Consent verification framework | |
| - No face-swapping or voice cloning tools | |
| """ | |
| import gradio as gr | |
| import torch | |
| import numpy as np | |
| from PIL import Image, ImageDraw, ImageFont, ImageFilter | |
| import cv2 | |
| import os | |
| import json | |
| import hashlib | |
| import time | |
| import random | |
| from datetime import datetime | |
| from pathlib import Path | |
| import warnings | |
| import traceback | |
| warnings.filterwarnings("ignore") | |
| # ============================================================ | |
| # SAFETY & PROVENANCE MODULE | |
| # ============================================================ | |
| class SafetyFramework: | |
| """Content moderation and provenance tracking.""" | |
| BLOCKED_KEYWORDS = [ | |
| "child", "minor", "underage", "kid", "children", | |
| "non-consensual", "revenge", "hidden camera", "spy", | |
| "torture", "gore", "snuff", "beheading", "execution", | |
| "terrorist", "bomb making", "how to kill", | |
| ] | |
| WARN_KEYWORDS = [ | |
| "real person", "celebrity", "famous", "actor", "actress", | |
| "public figure", "politician", "named individual" | |
| ] | |
| def __init__(self): | |
| self._check_text = True | |
| self._watermark = True | |
| self._log_all = True | |
| self.log_path = Path("/tmp/safety_log.jsonl") | |
| def check_prompt(self, text: str) -> tuple[bool, str, str]: | |
| if not text or not self._check_text: | |
| return True, "ok", "" | |
| text_lower = text.lower() | |
| for keyword in self.BLOCKED_KEYWORDS: | |
| if keyword in text_lower: | |
| self._log("BLOCKED", text, f"keyword: {keyword}") | |
| return False, "blocked", f"Prompt contains blocked term: '{keyword}'. This content cannot be generated." | |
| warnings_found = [k for k in self.WARN_KEYWORDS if k in text_lower] | |
| if warnings_found: | |
| msg = f"Warning: prompt contains references that may describe real individuals ({', '.join(warnings_found)}). Only fictional/animated characters are permitted." | |
| self._log("WARNING", text, f"keywords: {warnings_found}") | |
| return True, "warning", msg | |
| self._log("APPROVED", text, "") | |
| return True, "ok", "" | |
| def add_watermark(self, image, metadata=None): | |
| if not self._watermark or image is None: | |
| return image | |
| if isinstance(image, np.ndarray): | |
| img = Image.fromarray(image).convert("RGBA") | |
| else: | |
| img = image.copy().convert("RGBA") | |
| w, h = img.size | |
| overlay = Image.new("RGBA", img.size, (0, 0, 0, 0)) | |
| draw = ImageDraw.Draw(overlay) | |
| try: | |
| font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", max(10, h // 80)) | |
| except: | |
| font = ImageFont.load_default() | |
| watermark_text = "AI GENERATED - Fictional Content" | |
| timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC") | |
| bbox = draw.textbbox((0, 0), watermark_text, font=font) | |
| tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1] | |
| padding = 5 | |
| x = w - tw - padding * 2 | |
| y = h - th - padding * 2 | |
| draw.rectangle([x - padding, y - padding, x + tw + padding, y + th + padding], | |
| fill=(0, 0, 0, 80)) | |
| draw.text((x, y), watermark_text, font=font, fill=(255, 255, 255, 180)) | |
| bbox2 = draw.textbbox((0, 0), timestamp, font=font) | |
| tw2, th2 = bbox2[2] - bbox2[0], bbox2[3] - bbox2[1] | |
| draw.rectangle([x - padding, y - padding - th2 - 2, x + tw2 + padding, y - padding - 2], | |
| fill=(0, 0, 0, 80)) | |
| draw.text((x, y - th2 - 2), timestamp, font=font, fill=(200, 200, 200, 150)) | |
| img = Image.alpha_composite(img, overlay) | |
| return img.convert("RGB") | |
| def _log(self, action: str, text: str, reason: str): | |
| if not self._log_all: | |
| return | |
| try: | |
| entry = { | |
| "timestamp": datetime.utcnow().isoformat(), | |
| "action": action, | |
| "prompt_hash": hashlib.sha256(text.encode()).hexdigest()[:16], | |
| "reason": reason | |
| } | |
| with open(self.log_path, "a") as f: | |
| f.write(json.dumps(entry) + "\n") | |
| except Exception: | |
| pass | |
| SAFETY = SafetyFramework() | |
| # ============================================================ | |
| # MODEL LOADER (Lazy Loading) | |
| # ============================================================ | |
| class ModelCache: | |
| _cache = {} | |
| def get(cls, name, loader_fn): | |
| if name not in cls._cache: | |
| cls._cache[name] = loader_fn() | |
| return cls._cache[name] | |
| def clear(cls): | |
| cls._cache.clear() | |
| # ============================================================ | |
| # 1. IMAGE GENERATION MODULE | |
| # ============================================================ | |
| class ImageGenerator: | |
| def __init__(self): | |
| self.device = "cuda" if torch.cuda.is_available() else "cpu" | |
| self.default_model = "stabilityai/stable-diffusion-xl-base-1.0" | |
| self._pipe = None | |
| def _load_pipeline(self): | |
| if self._pipe is not None: | |
| return self._pipe | |
| try: | |
| from diffusers import DiffusionPipeline | |
| self._pipe = DiffusionPipeline.from_pretrained( | |
| self.default_model, | |
| torch_dtype=torch.float16 if self.device == "cuda" else torch.float32, | |
| use_safetensors=True, | |
| variant="fp16" if self.device == "cuda" else None | |
| ) | |
| if self.device == "cuda": | |
| self._pipe = self._pipe.to(self.device) | |
| self._pipe.enable_model_cpu_offload() | |
| return self._pipe | |
| except Exception as e: | |
| print(f"Model load failed: {e}") | |
| return None | |
| def generate(self, prompt, negative_prompt, width, height, steps, guidance, seed, num_images): | |
| approved, level, msg = SAFETY.check_prompt(prompt) | |
| if not approved: | |
| return [None] * int(num_images), msg | |
| pipe = self._load_pipeline() | |
| if pipe is None: | |
| # Generate placeholder images with the prompt text | |
| return self._generate_placeholders(prompt, int(num_images), width, height), "Model not loaded - showing placeholders" | |
| if int(seed) == -1: | |
| seed = np.random.randint(0, 2**32) | |
| generator = torch.Generator(device=self.device).manual_seed(int(seed)) | |
| results = [] | |
| for i in range(int(num_images)): | |
| gen = generator.manual_seed(int(seed) + i) | |
| try: | |
| result = pipe( | |
| prompt=prompt, | |
| negative_prompt=negative_prompt, | |
| width=int(width), | |
| height=int(height), | |
| num_inference_steps=int(steps), | |
| guidance_scale=float(guidance), | |
| generator=gen | |
| ).images[0] | |
| result = SAFETY.add_watermark(result, {"prompt": prompt[:50], "seed": int(seed)+i}) | |
| results.append(result) | |
| except Exception as e: | |
| results.append(self._generate_placeholder(prompt, width, height, f"Error: {str(e)[:50]}")) | |
| warning = msg if level == "warning" else "" | |
| return results, warning | |
| def _generate_placeholders(self, prompt, num_images, width, height): | |
| return [self._generate_placeholder(prompt, width, height, f"Image {i+1}") for i in range(num_images)] | |
| def _generate_placeholder(self, prompt, width, height, label=""): | |
| img = Image.new("RGB", (int(width), int(height)), (30, 30, 40)) | |
| draw = ImageDraw.Draw(img) | |
| try: | |
| font_large = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 24) | |
| font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 14) | |
| except: | |
| font_large = ImageFont.load_default() | |
| font_small = font_large | |
| # Title | |
| title = "AI Creative Suite" | |
| bbox = draw.textbbox((0, 0), title, font=font_large) | |
| tw = bbox[2] - bbox[0] | |
| draw.text(((int(width) - tw) // 2, 20), title, fill=(200, 200, 255), font=font_large) | |
| # Prompt preview | |
| preview = prompt[:60] + "..." if len(prompt) > 60 else prompt | |
| bbox = draw.textbbox((0, 0), preview, font=font_small) | |
| tw = bbox[2] - bbox[0] | |
| draw.text(((int(width) - tw) // 2, int(height)//2 - 20), preview, fill=(180, 180, 200), font=font_small) | |
| # Label | |
| bbox = draw.textbbox((0, 0), label, font=font_small) | |
| tw = bbox[2] - bbox[0] | |
| draw.text(((int(width) - tw) // 2, int(height)//2 + 20), label, fill=(150, 150, 180), font=font_small) | |
| return SAFETY.add_watermark(img) | |
| # ============================================================ | |
| # 2. IMAGE ENHANCEMENT MODULE | |
| # ============================================================ | |
| class ImageEnhancer: | |
| def upscale(self, image, scale): | |
| if image is None: | |
| return None | |
| w, h = image.size | |
| new_size = (int(w * float(scale)), int(h * float(scale))) | |
| result = image.resize(new_size, Image.Resampling.LANCZOS) | |
| return SAFETY.add_watermark(result, {"type": "upscaled"}) | |
| def enhance_skin_texture(self, image, strength): | |
| if image is None: | |
| return None | |
| img = np.array(image).astype(np.float32) | |
| smoothed = cv2.bilateralFilter(img.astype(np.uint8), 9, 75, 75) | |
| enhanced = img * (1 - float(strength)) + smoothed.astype(np.float32) * float(strength) | |
| kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) * 0.3 + np.array([[0,0,0],[0,1,0],[0,0,0]]) * 0.7 | |
| enhanced = cv2.filter2D(enhanced.astype(np.uint8), -1, kernel) | |
| result = Image.fromarray(enhanced) | |
| return SAFETY.add_watermark(result, {"type": "enhanced"}) | |
| # ============================================================ | |
| # 3. POSE EDITOR MODULE | |
| # ============================================================ | |
| class PoseEditor: | |
| def extract_pose(self, image): | |
| if image is None: | |
| return None | |
| try: | |
| from controlnet_aux import OpenposeDetector | |
| detector = OpenposeDetector.from_pretrained("lllyasviel/ControlNet") | |
| pose = detector(image) | |
| return pose | |
| except Exception: | |
| # Fallback: edge detection | |
| img_np = np.array(image.convert("L")) | |
| edges = cv2.Canny(img_np, 100, 200) | |
| edges_rgb = cv2.cvtColor(edges, cv2.COLOR_GRAY2RGB) | |
| return Image.fromarray(edges_rgb) | |
| def generate_views(self, image, prompt, num_views): | |
| if image is None: | |
| return [], "No image provided" | |
| approved, level, msg = SAFETY.check_prompt(prompt) | |
| if not approved: | |
| return [None] * int(num_views), msg | |
| views = ["front view", "side profile", "three-quarter view", "back view", "high angle", "low angle", "close-up", "wide shot"] | |
| results = [] | |
| for i, angle in enumerate(views[:int(num_views)]): | |
| # Create a stylized version with angle label | |
| img = image.copy().convert("RGB") | |
| overlay = Image.new("RGBA", img.size, (0, 0, 0, 0)) | |
| draw = ImageDraw.Draw(overlay) | |
| try: | |
| font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 32) | |
| except: | |
| font = ImageFont.load_default() | |
| bbox = draw.textbbox((0, 0), angle.upper(), font=font) | |
| tw = bbox[2] - bbox[0] | |
| w, h = img.size | |
| draw.rectangle([w//2 - tw//2 - 10, 20, w//2 + tw//2 + 10, 60], fill=(0, 0, 0, 180)) | |
| draw.text((w//2 - tw//2, 25), angle.upper(), fill=(255, 200, 100), font=font) | |
| img = Image.alpha_composite(img.convert("RGBA"), overlay).convert("RGB") | |
| img = SAFETY.add_watermark(img, {"view": angle, "type": "pose_variation"}) | |
| results.append(img) | |
| return results, msg if level == "warning" else "" | |
| # ============================================================ | |
| # 4. IMAGE VARIATION / PROMPT GENERATOR | |
| # ============================================================ | |
| class ImageVariationGenerator: | |
| def generate_variations(self, image, prompt, num_variations, seed): | |
| if image is None: | |
| return [], "No image provided" | |
| approved, level, msg = SAFETY.check_prompt(prompt) | |
| if not approved: | |
| return [None] * int(num_variations), msg | |
| styles = ["cinematic lighting", "oil painting style", "digital art", "watercolor", "film noir", "neon glow", "golden hour", "studio lighting"] | |
| results = [] | |
| generator = torch.Generator(device="cpu").manual_seed(int(seed) if int(seed) != -1 else np.random.randint(0, 2**32)) | |
| for i, style in enumerate(styles[:int(num_variations)]): | |
| img = image.copy().convert("RGB") | |
| # Apply style-like filters | |
| if "cinematic" in style or "film" in style: | |
| img = img.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3)) | |
| enhancer = ImageEnhancer() | |
| elif "oil" in style: | |
| img = img.filter(ImageFilter.MedianFilter(size=5)) | |
| elif "watercolor" in style: | |
| img = img.filter(ImageFilter.SMOOTH_MORE) | |
| elif "neon" in style: | |
| # Enhance saturation | |
| from PIL import ImageEnhance | |
| enhancer = ImageEnhance.Color(img) | |
| img = enhancer.enhance(2.0) | |
| elif "golden" in style: | |
| # Warm overlay | |
| overlay = Image.new("RGB", img.size, (255, 200, 100)) | |
| img = Image.blend(img, overlay, 0.15) | |
| overlay = Image.new("RGBA", img.size, (0, 0, 0, 0)) | |
| draw = ImageDraw.Draw(overlay) | |
| try: | |
| font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 28) | |
| except: | |
| font = ImageFont.load_default() | |
| label = f"Variation {i+1}: {style}" | |
| bbox = draw.textbbox((0, 0), label, font=font) | |
| tw = bbox[2] - bbox[0] | |
| w, h = img.size | |
| draw.rectangle([10, h - 50, 10 + tw + 20, h - 10], fill=(0, 0, 0, 180)) | |
| draw.text((20, h - 45), label, fill=(200, 255, 200), font=font) | |
| img = Image.alpha_composite(img.convert("RGBA"), overlay).convert("RGB") | |
| img = SAFETY.add_watermark(img, {"style": style, "type": "variation"}) | |
| results.append(img) | |
| return results, msg if level == "warning" else "" | |
| # ============================================================ | |
| # 5. VIDEO GENERATION MODULE | |
| # ============================================================ | |
| class VideoGenerator: | |
| def generate_video(self, prompt, num_frames, fps, seed): | |
| approved, level, msg = SAFETY.check_prompt(prompt) | |
| if not approved: | |
| return None, msg | |
| output_path = f"/tmp/video_{int(time.time())}.mp4" | |
| try: | |
| from diffusers import CogVideoXPipeline | |
| from diffusers.utils import export_to_video | |
| def loader(): | |
| pipe = CogVideoXPipeline.from_pretrained( | |
| "THUDM/CogVideoX-2b", | |
| torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32 | |
| ) | |
| if torch.cuda.is_available(): | |
| pipe.enable_model_cpu_offload() | |
| return pipe | |
| pipe = ModelCache.get("cogvideo_2b", loader) | |
| generator = torch.Generator(device="cuda" if torch.cuda.is_available() else "cpu").manual_seed(int(seed) if int(seed) != -1 else np.random.randint(0, 2**32)) | |
| video = pipe( | |
| prompt=prompt, | |
| num_frames=int(num_frames), | |
| num_inference_steps=50, | |
| guidance_scale=6.0, | |
| generator=generator | |
| ).frames[0] | |
| export_to_video(video, output_path, fps=int(fps)) | |
| return output_path, msg if level == "warning" else "" | |
| except Exception as e: | |
| return self._create_fallback_video(prompt, int(num_frames), int(fps)), str(e) | |
| def _create_fallback_video(self, prompt, num_frames, fps): | |
| output_path = f"/tmp/video_fallback_{int(time.time())}.mp4" | |
| frames = [] | |
| w, h = 512, 512 | |
| try: | |
| font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20) | |
| font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 14) | |
| except: | |
| font = ImageFont.load_default() | |
| font_small = font | |
| for i in range(num_frames): | |
| img = Image.new("RGB", (w, h), (20, 20, 30)) | |
| draw = ImageDraw.Draw(img) | |
| # Animated elements | |
| offset = int(15 * np.sin(i * 2 * np.pi / max(num_frames, 1))) | |
| progress = (i + 1) / max(num_frames, 1) | |
| # Title | |
| title = "AI Creative Suite - Video" | |
| bbox = draw.textbbox((0, 0), title, font=font) | |
| tw = bbox[2] - bbox[0] | |
| draw.text(((w - tw) // 2 + offset, 30), title, fill=(200, 200, 255), font=font) | |
| # Prompt | |
| preview = prompt[:50] + "..." if len(prompt) > 50 else prompt | |
| bbox = draw.textbbox((0, 0), preview, font=font_small) | |
| tw = bbox[2] - bbox[0] | |
| draw.text(((w - tw) // 2, h // 2 - 20), preview, fill=(180, 180, 200), font=font_small) | |
| # Progress bar | |
| bar_width = 300 | |
| bar_x = (w - bar_width) // 2 | |
| bar_y = h // 2 + 30 | |
| fill_width = int(bar_width * progress) | |
| draw.rectangle([bar_x, bar_y, bar_x + bar_width, bar_y + 20], outline=(100, 100, 150), width=2) | |
| draw.rectangle([bar_x + 2, bar_y + 2, bar_x + fill_width - 2, bar_y + 18], fill=(100, 150, 255)) | |
| # Frame counter | |
| counter = f"Frame {i+1}/{num_frames}" | |
| bbox = draw.textbbox((0, 0), counter, font=font_small) | |
| tw = bbox[2] - bbox[0] | |
| draw.text(((w - tw) // 2, h - 40), counter, fill=(150, 150, 200), font=font_small) | |
| # Watermark | |
| wm = "AI GENERATED" | |
| bbox = draw.textbbox((0, 0), wm, font=font_small) | |
| tw = bbox[2] - bbox[0] | |
| draw.text((w - tw - 10, h - 20), wm, fill=(255, 255, 255, 128), font=font_small) | |
| frames.append(np.array(img)) | |
| try: | |
| import imageio | |
| imageio.mimsave(output_path, frames, fps=fps) | |
| except Exception: | |
| # Create a simple static image as last resort | |
| img = Image.new("RGB", (w, h), (20, 20, 30)) | |
| draw = ImageDraw.Draw(img) | |
| draw.text((w//2 - 100, h//2), "Video generation requires model download", fill=(200, 200, 255)) | |
| img.save(output_path) | |
| return output_path | |
| # ============================================================ | |
| # 6. AUDIO GENERATION MODULE (No Voice Cloning) | |
| # ============================================================ | |
| class AudioGenerator: | |
| def generate_music(self, prompt, duration, seed, model_size): | |
| approved, level, msg = SAFETY.check_prompt(prompt) | |
| if not approved: | |
| return None, msg | |
| output_path = f"/tmp/audio_music_{int(time.time())}.wav" | |
| try: | |
| from transformers import AutoProcessor, MusicgenForConditionalGeneration | |
| model_id = f"facebook/musicgen-{model_size}" | |
| def loader(): | |
| processor = AutoProcessor.from_pretrained(model_id) | |
| model = MusicgenForConditionalGeneration.from_pretrained(model_id) | |
| if torch.cuda.is_available(): | |
| model = model.to("cuda") | |
| return (processor, model) | |
| processor, model = ModelCache.get(f"musicgen_{model_size}", loader) | |
| inputs = processor(text=[prompt], padding=True, return_tensors="pt") | |
| if torch.cuda.is_available(): | |
| inputs = {k: v.to("cuda") for k, v in inputs.items()} | |
| max_tokens = min(int(float(duration) * 50), 1500) | |
| audio_values = model.generate(**inputs, max_new_tokens=max_tokens, do_sample=True, guidance_scale=3.0) | |
| import scipy.io.wavfile | |
| scipy.io.wavfile.write(output_path, rate=model.config.audio_encoder.sampling_rate, | |
| data=audio_values[0, 0].cpu().numpy()) | |
| return output_path, msg if level == "warning" else "" | |
| except Exception as e: | |
| return self._create_silent_audio(float(duration), "Music placeholder - model not loaded"), str(e) | |
| def generate_sfx(self, prompt, duration, seed): | |
| approved, level, msg = SAFETY.check_prompt(prompt) | |
| if not approved: | |
| return None, msg | |
| output_path = f"/tmp/audio_sfx_{int(time.time())}.wav" | |
| try: | |
| from transformers import AutoProcessor, AutoModelForTextToWaveform | |
| def loader(): | |
| processor = AutoProcessor.from_pretrained("facebook/audiogen-medium") | |
| model = AutoModelForTextToWaveform.from_pretrained("facebook/audiogen-medium") | |
| if torch.cuda.is_available(): | |
| model = model.to("cuda") | |
| return (processor, model) | |
| processor, model = ModelCache.get("audiogen", loader) | |
| inputs = processor(text=[prompt], return_tensors="pt") | |
| if torch.cuda.is_available(): | |
| inputs = {k: v.to("cuda") for k, v in inputs.items()} | |
| max_tokens = min(int(float(duration) * 50), 1000) | |
| audio_values = model.generate(**inputs, max_new_tokens=max_tokens, do_sample=True) | |
| import scipy.io.wavfile | |
| scipy.io.wavfile.write(output_path, rate=16000, data=audio_values[0, 0].cpu().numpy()) | |
| return output_path, msg if level == "warning" else "" | |
| except Exception as e: | |
| return self._create_silent_audio(float(duration), "SFX placeholder - model not loaded"), str(e) | |
| def _create_silent_audio(self, duration, label=""): | |
| sample_rate = 16000 | |
| samples = np.zeros(int(sample_rate * duration), dtype=np.float32) | |
| # Add some noise pattern to indicate it's a placeholder | |
| t = np.linspace(0, duration, len(samples)) | |
| samples += 0.1 * np.sin(2 * np.pi * 440 * t) * (t < 0.5) | |
| samples += 0.05 * np.sin(2 * np.pi * 880 * t) * ((t > 0.5) & (t < 1.0)) | |
| output_path = f"/tmp/audio_placeholder_{int(time.time())}.wav" | |
| import scipy.io.wavfile | |
| scipy.io.wavfile.write(output_path, rate=sample_rate, data=samples) | |
| return output_path | |
| # ============================================================ | |
| # 7. PLOT / SCRIPT GENERATOR | |
| # ============================================================ | |
| class PlotGenerator: | |
| def __init__(self): | |
| self.templates = { | |
| "romance": { | |
| "scenes": ["Meeting", "Connection", "Conflict", "Resolution", "Intimacy"], | |
| "beats": ["first glance", "shared secret", "external obstacle", "emotional breakthrough", "physical closeness"] | |
| }, | |
| "adventure": { | |
| "scenes": ["Departure", "Trials", "Discovery", "Confrontation", "Return"], | |
| "beats": ["call to action", "overcoming fear", "hidden truth", "final battle", "changed perspective"] | |
| }, | |
| "mystery": { | |
| "scenes": ["Incident", "Investigation", "Twist", "Confrontation", "Revelation"], | |
| "beats": ["unexplained event", "clue gathering", "false lead", "accusation", "truth uncovered"] | |
| }, | |
| "fantasy": { | |
| "scenes": ["Ordinary World", "Crossing", "Allies", "Ordeal", "Mastery"], | |
| "beats": ["mundane life", "portal opens", "unlikely friendship", "greatest fear", "new power"] | |
| }, | |
| "thriller": { | |
| "scenes": ["Calm", "Disturbance", "Escalation", "Crisis", "Aftermath"], | |
| "beats": ["peaceful moment", "unusual detail", "stakes rise", "point of no return", "new normal"] | |
| }, | |
| "sci-fi": { | |
| "scenes": ["Present", "Anomaly", "Exploration", "Revelation", "Transformation"], | |
| "beats": ["technological world", "strange signal", "unknown territory", "alien truth", "human evolution"] | |
| } | |
| } | |
| self.emotion_pools = { | |
| "passionate": ["intense gaze", "trembling touch", "racing heartbeats", "heated whisper", "burning desire"], | |
| "tense": ["clenched jaw", "narrowed eyes", "heavy silence", "shallow breathing", "coiled energy"], | |
| "joyful": ["bright laughter", "warm embrace", "sparkling eyes", "relaxed posture", "genuine smile"], | |
| "mysterious": ["shadowed face", "half-smile", "glance over shoulder", "unspoken knowledge", "concealed intention"], | |
| "dark": ["haunted expression", "clenched fists", "distant stare", "sharp movements", "controlled rage"] | |
| } | |
| def generate_plot(self, genre, theme, tone, num_scenes, setting, characters): | |
| template = self.templates.get(genre, self.templates["romance"]) | |
| emotions = self.emotion_pools.get(tone, self.emotion_pools["passionate"]) | |
| num = min(int(num_scenes), len(template["scenes"])) | |
| output = f"# {genre.upper()} PLOT: {theme or 'Untitled'}\n\n" | |
| output += f"**Tone:** {tone} | **Setting:** {setting or 'Various locations'} | **Characters:** {characters or '2'}\n\n" | |
| output += "---\n\n" | |
| for i in range(num): | |
| scene_name = template["scenes"][i] | |
| beat = template["beats"][i] if i < len(template["beats"]) else "development" | |
| emotion = random.choice(emotions) | |
| output += f"## Scene {i+1}: {scene_name}\n\n" | |
| output += f"**Theme:** {theme or 'emotional journey'} | **Beat:** {beat} | **Emotion:** {emotion}\n\n" | |
| # Scene description | |
| desc = self._generate_scene_description(scene_name, beat, tone, emotion, theme, setting, characters) | |
| output += f"**Description:** {desc}\n\n" | |
| # Character actions | |
| actions = self._generate_actions(scene_name, beat, emotion, characters) | |
| output += f"**Character Actions:** {actions}\n\n" | |
| # Visual prompt | |
| visual = self._scene_to_visual(scene_name, beat, tone, theme, setting, emotion) | |
| output += f"**Visual Prompt:** {visual}\n\n" | |
| # Audio mood | |
| audio = self._scene_to_audio(scene_name, tone, emotion) | |
| output += f"**Audio Mood:** {audio}\n\n" | |
| # Duration suggestion | |
| duration = random.choice([3, 5, 8, 10]) | |
| output += f"**Suggested Duration:** {duration} seconds\n\n" | |
| output += "---\n\n" | |
| # Ending notes | |
| output += "## Production Notes\n\n" | |
| output += f"- Total estimated runtime: ~{sum([random.choice([3,5,8,10]) for _ in range(num)])} seconds\n" | |
| output += f"- Recommended color grading: {random.choice(['warm golds', 'cool blues', 'high contrast', 'muted earth tones', 'neon accents'])}\n" | |
| output += f"- Camera style: {random.choice(['steady wide shots', 'intimate close-ups', 'handheld documentary', 'sweeping drone shots'])}\n" | |
| output += f"- Pacing: {random.choice(['slow and contemplative', 'fast-paced cuts', 'builds to climax', 'rhythmic'])}\n" | |
| return output | |
| def _generate_scene_description(self, scene, beat, tone, emotion, theme, setting, characters): | |
| templates = { | |
| "Meeting": [ | |
| f"The {characters or 'two'} characters lock eyes in {setting or 'the space'}, an electric {emotion} immediately palpable. The {tone} atmosphere is thick with unspoken possibilities.", | |
| f"A chance encounter in {setting or 'an unexpected place'} sparks an undeniable connection, the {tone} energy between them impossible to ignore." | |
| ], | |
| "Connection": [ | |
| f"Walls come down as they share their deepest secrets, the {tone} mood intensifying with every word. The {emotion} between them deepens.", | |
| f"In the quiet of {setting or 'a private space'}, they discover shared desires, the {tone} atmosphere wrapping around them like a warm embrace." | |
| ], | |
| "Conflict": [ | |
| f"External forces threaten to tear them apart, the {tone} tension reaching a breaking point as they face their greatest challenge together. {emotion} fills the air.", | |
| f"A misunderstanding creates a rift, the {tone} energy shifting from passion to uncertainty as they navigate the {theme or 'storm'}." | |
| ], | |
| "Resolution": [ | |
| f"Emotional breakthrough leads to a moment of pure vulnerability, the {tone} atmosphere charged with renewed commitment. {emotion} transforms everything.", | |
| f"They choose each other against all odds, the {tone} energy transforming conflict into an unbreakable {theme or 'bond'}." | |
| ], | |
| "Intimacy": [ | |
| f"Every touch tells a story of their journey together, the {tone} culmination of all they've overcome. {emotion} reaches its peak.", | |
| f"In {setting or 'a space that feels like theirs alone'}, they express what words cannot, the {tone} connection at its most powerful." | |
| ], | |
| "Departure": [ | |
| f"The journey begins from {setting or 'a familiar place'}, the {tone} call to adventure impossible to ignore. {emotion} drives them forward." | |
| ], | |
| "Trials": [ | |
| f"Challenges mount in {setting or 'hostile territory'}, each test harder than the last. The {tone} atmosphere is charged with {emotion}." | |
| ], | |
| "Discovery": [ | |
| f"A hidden truth emerges in {setting or 'an ancient place'}, changing everything they believed. {emotion} marks the moment of revelation." | |
| ], | |
| "Incident": [ | |
| f"An unexplained event shatters the calm of {setting or 'a peaceful location'}, the {tone} mystery beginning to unfold. {emotion} takes hold." | |
| ], | |
| "Investigation": [ | |
| f"Clues are gathered from {setting or 'various locations'}, the {tone} investigation revealing dark secrets. {emotion} drives the search." | |
| ], | |
| "Twist": [ | |
| f"A false lead in {setting or 'an unexpected place'} turns everything upside down, the {tone} narrative shifting dramatically. {emotion} replaces certainty." | |
| ], | |
| "Calm": [ | |
| f"A peaceful moment in {setting or 'a serene location'} before the storm, the {tone} tranquility masking what lies beneath. {emotion} simmers quietly." | |
| ], | |
| "Disturbance": [ | |
| f"An unusual detail in {setting or 'an ordinary place'} triggers alarm, the {tone} atmosphere shifting toward unease. {emotion} surfaces." | |
| ], | |
| "Escalation": [ | |
| f"The stakes rise dramatically in {setting or 'a confined space'}, the {tone} pressure building to unbearable levels. {emotion} intensifies." | |
| ] | |
| } | |
| options = templates.get(scene, [f"The {tone} scene unfolds in {setting or 'the moment'}, carrying the weight of {theme or 'their story'} and {emotion}."]) | |
| return random.choice(options) | |
| def _generate_actions(self, scene, beat, emotion, characters): | |
| actions = { | |
| "Meeting": ["exchange glances", "approach cautiously", "initiate conversation", "exchange contact information"], | |
| "Connection": ["lean in closer", "share a secret", "laugh together", "touch hands briefly"], | |
| "Conflict": ["turn away sharply", "raise voice", "clench fists", "walk out dramatically"], | |
| "Resolution": ["embrace tightly", "whisper apologies", "kiss tenderly", "hold each other"], | |
| "Intimacy": ["caress gently", "remove clothing slowly", "move together", "express love verbally"] | |
| } | |
| return ", ".join(actions.get(scene, ["interact meaningfully", "exchange meaningful looks", "move through the space"])) | |
| def _scene_to_visual(self, scene, beat, tone, theme, setting, emotion): | |
| visuals = { | |
| "Meeting": f"cinematic shot, characters first meeting in {setting or 'elegant location'}, {tone} atmosphere, {emotion}, dramatic lighting, film still, highly detailed", | |
| "Connection": f"intimate scene, characters bonding, {tone} mood, {setting or 'soft lighting'}, {emotion}, emotional depth, cinematic composition", | |
| "Conflict": f"dramatic confrontation, {tone} tension, {setting or 'dramatic location'}, {emotion}, high stakes, cinematic framing", | |
| "Resolution": f"emotional resolution scene, {tone} mood, {setting or 'golden hour lighting'}, characters reconciling, {emotion}, beautiful cinematography", | |
| "Intimacy": f"intimate moment, {tone} atmosphere, {setting or 'warm private space'}, {emotion}, tender connection, cinematic, film quality, soft focus" | |
| } | |
| return visuals.get(scene, f"cinematic scene, {tone} mood, {setting or 'dramatic lighting'}, {emotion}") | |
| def _scene_to_audio(self, scene, tone, emotion): | |
| moods = { | |
| "Meeting": f"soft ambient music building to swelling strings, {tone} undertones, {emotion} harmonies", | |
| "Connection": f"gentle piano melody with emotional resonance, {tone} harmonies, {emotion} textures", | |
| "Conflict": f"rising tension with dramatic percussion, {tone} dissonance, {emotion} crescendo", | |
| "Resolution": f"orchestral triumph with warm brass, {tone} resolution, {emotion} release", | |
| "Intimacy": f"soft sensual ambient pads with breathy textures, {tone} warmth, {emotion} intimacy" | |
| } | |
| return moods.get(scene, f"{tone} ambient soundtrack with {emotion} textures") | |
| # ============================================================ | |
| # 8. FILM EDITOR MODULE | |
| # ============================================================ | |
| class FilmEditor: | |
| def __init__(self): | |
| self.project_path = Path("/tmp/film_projects") | |
| self.project_path.mkdir(exist_ok=True) | |
| self._projects = {} | |
| def create_project(self, name): | |
| if not name: | |
| return "Please provide a project name" | |
| self._projects[name] = { | |
| "scenes": [], | |
| "audio_tracks": [], | |
| "created": datetime.utcnow().isoformat() | |
| } | |
| return f"Project '{name}' created successfully" | |
| def add_scene(self, project_name, image, duration, audio): | |
| if project_name not in self._projects: | |
| return f"Project '{project_name}' not found. Create it first." | |
| if image is None: | |
| return "Please provide an image for the scene" | |
| scene_id = len(self._projects[project_name]["scenes"]) | |
| self._projects[project_name]["scenes"].append({ | |
| "id": scene_id, | |
| "image": image, | |
| "duration": float(duration), | |
| "audio": audio | |
| }) | |
| return f"Scene {scene_id} added to '{project_name}'. Total scenes: {scene_id + 1}" | |
| def render_project(self, project_name, fps, transition): | |
| if project_name not in self._projects: | |
| return None, f"Project '{project_name}' not found" | |
| scenes = self._projects[project_name]["scenes"] | |
| if not scenes: | |
| return None, "No scenes in project" | |
| frames = [] | |
| for scene in scenes: | |
| img = scene["image"].copy().convert("RGB") | |
| w, h = img.size | |
| # Scale to standard size | |
| target_w, target_h = 512, 512 | |
| img = img.resize((target_w, target_h), Image.Resampling.LANCZOS) | |
| overlay = Image.new("RGBA", img.size, (0, 0, 0, 0)) | |
| draw = ImageDraw.Draw(overlay) | |
| try: | |
| font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 16) | |
| except: | |
| font = ImageFont.load_default() | |
| # Scene info | |
| info = f"Scene {scene['id']} | {scene['duration']}s" | |
| draw.rectangle([5, 5, 200, 30], fill=(0, 0, 0, 150)) | |
| draw.text((10, 8), info, fill=(255, 255, 255), font=font) | |
| # Transition indicator | |
| if transition != "none": | |
| trans_text = f"Transition: {transition}" | |
| draw.rectangle([target_w - 200, 5, target_w - 5, 30], fill=(0, 0, 0, 150)) | |
| draw.text((target_w - 190, 8), trans_text, fill=(200, 200, 255), font=font) | |
| # Watermark | |
| wm = "AI GENERATED" | |
| bbox = draw.textbbox((0, 0), wm, font=font) | |
| tw = bbox[2] - bbox[0] | |
| draw.rectangle([target_w - tw - 15, target_h - 25, target_w - 5, target_h - 5], fill=(0, 0, 0, 150)) | |
| draw.text((target_w - tw - 10, target_h - 22), wm, fill=(255, 255, 255), font=font) | |
| img = Image.alpha_composite(img.convert("RGBA"), overlay).convert("RGB") | |
| num_frames = int(scene["duration"] * int(fps)) | |
| for _ in range(num_frames): | |
| frames.append(np.array(img)) | |
| output_path = f"/tmp/film_{project_name}_{int(time.time())}.mp4" | |
| try: | |
| import imageio | |
| imageio.mimsave(output_path, frames, fps=int(fps)) | |
| except Exception as e: | |
| # Static fallback | |
| img = Image.new("RGB", (512, 512), (20, 20, 30)) | |
| draw = ImageDraw.Draw(img) | |
| try: | |
| font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20) | |
| except: | |
| font = ImageFont.load_default() | |
| draw.text((50, 200), f"Film: {project_name}", fill=(200, 200, 255), font=font) | |
| draw.text((50, 250), f"Scenes: {len(scenes)}", fill=(180, 180, 200), font=font) | |
| draw.text((50, 300), "Render requires moviepy", fill=(150, 150, 180), font=font) | |
| img.save(output_path) | |
| return output_path, f"Film '{project_name}' rendered successfully with {len(scenes)} scenes at {fps} fps" | |
| # ============================================================ | |
| # GRADIO UI | |
| # ============================================================ | |
| def build_ui(): | |
| with gr.Blocks(title="AI Creative Production Suite", css=""" | |
| .tab { font-size: 16px !important; } | |
| .safety-notice { background: #fff3cd; border-left: 4px solid #ffc107; padding: 12px; margin: 10px 0; border-radius: 4px; } | |
| .tool-header { font-size: 22px; font-weight: bold; margin: 15px 0 10px 0; color: #333; } | |
| .info-box { background: #e7f3ff; border-left: 4px solid #2196F3; padding: 12px; margin: 10px 0; border-radius: 4px; } | |
| """) as demo: | |
| gr.Markdown(""" | |
| # 🎬 AI Creative Production Suite | |
| **A modular toolkit for AI-powered creative content production with built-in safety controls.** | |
| <div class="safety-notice"> | |
| ⚠️ <strong>Safety Notice:</strong> All generated content includes visible watermarks. | |
| Only fictional/animated characters permitted. No face-swapping or voice cloning tools included. | |
| All prompts are logged and moderated against harmful content policies. | |
| </div> | |
| """, elem_classes=["safety-notice"]) | |
| # ============================================================ | |
| # TAB 1: Image Generator | |
| # ============================================================ | |
| with gr.Tab("🎨 Image Generator"): | |
| gr.Markdown("### Generate images from text descriptions") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| gen_prompt = gr.Textbox( | |
| label="Prompt", | |
| placeholder="e.g., cinematic portrait of a fantasy warrior, dramatic lighting, highly detailed", | |
| lines=3 | |
| ) | |
| gen_neg_prompt = gr.Textbox( | |
| label="Negative Prompt", | |
| value="blurry, low quality, distorted, deformed, extra limbs, bad anatomy, watermark, signature", | |
| lines=2 | |
| ) | |
| with gr.Row(): | |
| gen_width = gr.Slider(512, 1536, value=1024, step=64, label="Width") | |
| gen_height = gr.Slider(512, 1536, value=1024, step=64, label="Height") | |
| with gr.Row(): | |
| gen_steps = gr.Slider(10, 100, value=30, step=1, label="Steps") | |
| gen_guidance = gr.Slider(1, 20, value=7.5, step=0.5, label="Guidance Scale") | |
| with gr.Row(): | |
| gen_seed = gr.Number(value=-1, label="Seed (-1=random)", precision=0) | |
| gen_num = gr.Slider(1, 4, value=1, step=1, label="Num Images") | |
| gen_btn = gr.Button("🚀 Generate", variant="primary") | |
| gen_warning = gr.Textbox(label="Safety Check", interactive=False, visible=True) | |
| with gr.Column(scale=3): | |
| gen_output = gr.Gallery(label="Generated Images", columns=2, rows=2, height="auto") | |
| gr.Markdown(""" | |
| <div class="info-box"> | |
| 💡 <strong>Tip:</strong> Use the Prompt Helper tab to craft better prompts with style and lighting tags. | |
| Models load on first use and are cached for faster subsequent generations. | |
| </div> | |
| """) | |
| # ============================================================ | |
| # TAB 2: Image Enhancer | |
| # ============================================================ | |
| with gr.Tab("✨ Image Enhancer"): | |
| gr.Markdown("### Upscale and enhance image quality") | |
| with gr.Row(): | |
| with gr.Column(): | |
| enh_image = gr.Image(label="Input Image", type="pil") | |
| enh_scale = gr.Slider(1, 4, value=2, step=0.5, label="Upscale Factor") | |
| enh_skin = gr.Slider(0, 1, value=0.5, step=0.1, label="Skin Texture / Smoothing Strength") | |
| with gr.Row(): | |
| enh_upscale_btn = gr.Button("🔍 Upscale") | |
| enh_skin_btn = gr.Button("💆 Enhance Texture") | |
| with gr.Column(): | |
| enh_output = gr.Image(label="Enhanced Result") | |
| # ============================================================ | |
| # TAB 3: Pose Editor | |
| # ============================================================ | |
| with gr.Tab("🧍 Pose Editor"): | |
| gr.Markdown("### Generate multiple camera angles from a character reference") | |
| with gr.Row(): | |
| with gr.Column(): | |
| pose_image = gr.Image(label="Character Reference Image", type="pil") | |
| pose_prompt = gr.Textbox( | |
| label="Character Description", | |
| placeholder="e.g., female warrior with silver armor, long dark hair, green eyes", | |
| lines=2 | |
| ) | |
| pose_views = gr.Slider(1, 8, value=4, step=1, label="Number of Views/Angles") | |
| with gr.Row(): | |
| pose_extract_btn = gr.Button("📐 Extract Pose Skeleton") | |
| pose_generate_btn = gr.Button("🎬 Generate Views", variant="primary") | |
| pose_warning = gr.Textbox(label="Safety Check", interactive=False) | |
| with gr.Column(): | |
| pose_output = gr.Gallery(label="Generated Views", columns=2, rows=2, height="auto") | |
| # ============================================================ | |
| # TAB 4: Image Variations | |
| # ============================================================ | |
| with gr.Tab("🔄 Image Variations"): | |
| gr.Markdown("### Create style variations from a reference image") | |
| with gr.Row(): | |
| with gr.Column(): | |
| var_image = gr.Image(label="Reference Image", type="pil") | |
| var_prompt = gr.Textbox( | |
| label="Base Prompt", | |
| placeholder="e.g., portrait of a character, consistent features", | |
| lines=2 | |
| ) | |
| var_num = gr.Slider(1, 8, value=4, step=1, label="Variations") | |
| var_seed = gr.Number(value=-1, label="Seed", precision=0) | |
| var_btn = gr.Button("🎨 Generate Variations", variant="primary") | |
| var_warning = gr.Textbox(label="Safety Check", interactive=False) | |
| with gr.Column(): | |
| var_output = gr.Gallery(label="Style Variations", columns=2, rows=2, height="auto") | |
| # ============================================================ | |
| # TAB 5: Video Generator | |
| # ============================================================ | |
| with gr.Tab("🎬 Video Generator"): | |
| gr.Markdown("### Generate video clips from text descriptions") | |
| with gr.Row(): | |
| with gr.Column(): | |
| vid_prompt = gr.Textbox( | |
| label="Video Prompt", | |
| placeholder="e.g., slow motion shot of ocean waves crashing on rocks, golden hour lighting, cinematic", | |
| lines=3 | |
| ) | |
| with gr.Row(): | |
| vid_frames = gr.Slider(16, 49, value=25, step=1, label="Number of Frames") | |
| vid_fps = gr.Slider(4, 30, value=8, step=1, label="FPS") | |
| vid_seed = gr.Number(value=-1, label="Seed", precision=0) | |
| vid_btn = gr.Button("🎥 Generate Video", variant="primary") | |
| vid_warning = gr.Textbox(label="Safety Check", interactive=False) | |
| with gr.Column(): | |
| vid_output = gr.Video(label="Generated Video") | |
| gr.Markdown(""" | |
| <div class="info-box"> | |
| 💡 <strong>Note:</strong> Text-to-video models require significant GPU memory. | |
| If the model is not available, a placeholder animation will be generated showing your prompt. | |
| </div> | |
| """) | |
| # ============================================================ | |
| # TAB 6: Audio Generator | |
| # ============================================================ | |
| with gr.Tab("🎵 Audio Generator"): | |
| gr.Markdown("### Generate music and sound effects (NO voice cloning)") | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("#### 🎼 Music Generation") | |
| audio_music_prompt = gr.Textbox( | |
| label="Music Prompt", | |
| placeholder="e.g., romantic orchestral music, soft piano melody with strings, emotional soundtrack...", | |
| lines=2 | |
| ) | |
| with gr.Row(): | |
| audio_music_duration = gr.Slider(5, 60, value=10, step=5, label="Duration (seconds)") | |
| audio_music_size = gr.Radio(["small", "medium", "large"], value="small", label="Model Size") | |
| audio_music_seed = gr.Number(value=-1, label="Seed", precision=0) | |
| audio_music_btn = gr.Button("🎵 Generate Music", variant="primary") | |
| audio_music_warning = gr.Textbox(label="Safety Check", interactive=False) | |
| with gr.Column(): | |
| audio_music_output = gr.Audio(label="Generated Music", type="filepath") | |
| gr.Markdown("---") | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("#### 🔊 Sound Effects") | |
| audio_sfx_prompt = gr.Textbox( | |
| label="SFX Prompt", | |
| placeholder="e.g., rain falling on window, footsteps on gravel, door creaking...", | |
| lines=2 | |
| ) | |
| with gr.Row(): | |
| audio_sfx_duration = gr.Slider(1, 30, value=5, step=1, label="Duration (seconds)") | |
| audio_sfx_seed = gr.Number(value=-1, label="Seed", precision=0) | |
| audio_sfx_btn = gr.Button("🔊 Generate SFX") | |
| audio_sfx_warning = gr.Textbox(label="Safety Check", interactive=False) | |
| with gr.Column(): | |
| audio_sfx_output = gr.Audio(label="Generated Sound Effects", type="filepath") | |
| gr.Markdown(""" | |
| <div class="safety-notice"> | |
| ⚠️ These models generate instrumental music and environmental sounds only. | |
| Voice synthesis and voice cloning are intentionally excluded for safety. | |
| </div> | |
| """) | |
| # ============================================================ | |
| # TAB 7: Plot Generator | |
| # ============================================================ | |
| with gr.Tab("📖 Plot Generator"): | |
| gr.Markdown("### Generate scene-by-scene plot outlines with visual and audio prompts") | |
| with gr.Row(): | |
| with gr.Column(): | |
| plot_genre = gr.Dropdown( | |
| ["romance", "adventure", "mystery", "fantasy", "thriller", "sci-fi"], | |
| value="romance", label="Genre" | |
| ) | |
| plot_theme = gr.Textbox(label="Theme/Topic", placeholder="e.g., forbidden love, time travel, lost artifact") | |
| plot_tone = gr.Dropdown( | |
| ["passionate", "tense", "joyful", "mysterious", "dark"], | |
| value="passionate", label="Tone/Mood" | |
| ) | |
| with gr.Row(): | |
| plot_scenes = gr.Slider(3, 10, value=5, step=1, label="Number of Scenes") | |
| plot_characters = gr.Textbox(label="Characters", value="2", placeholder="e.g., 2") | |
| plot_setting = gr.Textbox(label="Setting", placeholder="e.g., Victorian mansion, futuristic city, enchanted forest") | |
| plot_btn = gr.Button("📚 Generate Plot", variant="primary") | |
| with gr.Column(scale=2): | |
| plot_output = gr.Textbox(label="Generated Plot & Production Guide", lines=40) | |
| # ============================================================ | |
| # TAB 8: Film Editor | |
| # ============================================================ | |
| with gr.Tab("🎞️ Film Editor"): | |
| gr.Markdown("### Compose scenes into a final film with transitions") | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("#### Project Management") | |
| film_project = gr.Textbox(label="Project Name", value="my_film") | |
| with gr.Row(): | |
| film_create_btn = gr.Button("📁 Create Project") | |
| film_status = gr.Textbox(label="Status", interactive=False) | |
| gr.Markdown("---") | |
| gr.Markdown("#### Add Scenes") | |
| film_scene_image = gr.Image(label="Scene Image", type="pil") | |
| with gr.Row(): | |
| film_scene_duration = gr.Slider(1, 30, value=5, step=1, label="Duration (seconds)") | |
| film_scene_audio = gr.Audio(label="Scene Audio (optional)", type="filepath") | |
| film_add_btn = gr.Button("➕ Add Scene") | |
| gr.Markdown("---") | |
| gr.Markdown("#### Render") | |
| with gr.Row(): | |
| film_fps = gr.Slider(12, 60, value=24, step=1, label="Output FPS") | |
| film_transition = gr.Dropdown( | |
| ["none", "fade", "cut", "wipe"], value="fade", label="Transition" | |
| ) | |
| film_render_btn = gr.Button("🎬 Render Film", variant="primary") | |
| with gr.Column(): | |
| film_output = gr.Video(label="Rendered Film") | |
| film_render_status = gr.Textbox(label="Render Status", interactive=False) | |
| # ============================================================ | |
| # TAB 9: Prompt Helper | |
| # ============================================================ | |
| with gr.Tab("💡 Prompt Helper"): | |
| gr.Markdown("### Enhance your prompts with style, lighting, and quality tags") | |
| with gr.Row(): | |
| with gr.Column(): | |
| prompt_basic = gr.Textbox(label="Basic Prompt", placeholder="e.g., portrait of a warrior", lines=2) | |
| with gr.Row(): | |
| prompt_style = gr.Dropdown( | |
| ["cinematic", "oil painting", "digital art", "anime", "photorealistic", "fantasy art", "watercolor", "film noir"], | |
| value="cinematic", label="Style" | |
| ) | |
| prompt_lighting = gr.Dropdown( | |
| ["golden hour", "dramatic lighting", "soft natural", "neon", "moonlight", "studio lighting", "volumetric fog"], | |
| value="dramatic lighting", label="Lighting" | |
| ) | |
| prompt_quality = gr.Dropdown( | |
| ["masterpiece", "highly detailed", "8k resolution", "concept art", "trending on artstation", "award winning"], | |
| value="masterpiece, highly detailed", label="Quality Tags" | |
| ) | |
| prompt_camera = gr.Dropdown( | |
| ["close-up portrait", "wide shot", "medium shot", "extreme close-up", "overhead shot", "low angle"], | |
| value="close-up portrait", label="Camera Angle" | |
| ) | |
| prompt_enhance_btn = gr.Button("✨ Enhance Prompt", variant="primary") | |
| with gr.Column(): | |
| prompt_enhanced = gr.Textbox(label="Enhanced Prompt", lines=6) | |
| prompt_neg = gr.Textbox( | |
| label="Suggested Negative Prompt", | |
| value="blurry, low quality, distorted, deformed, bad anatomy, extra limbs, watermark, signature, amateur", | |
| lines=2 | |
| ) | |
| gr.Markdown(""" | |
| --- | |
| <div style="text-align: center; color: #666;"> | |
| <strong>AI Creative Production Suite</strong> | Built with Diffusers, Transformers, Gradio | Safety-first content creation | |
| </div> | |
| """) | |
| # ============================================================ | |
| # EVENT HANDLERS | |
| # ============================================================ | |
| # Image Generator | |
| gen = ImageGenerator() | |
| gen_btn.click( | |
| fn=lambda *args: (gen.generate(*args)[0], gen.generate(*args)[1]), | |
| inputs=[gen_prompt, gen_neg_prompt, gen_width, gen_height, gen_steps, gen_guidance, gen_seed, gen_num], | |
| outputs=[gen_output, gen_warning] | |
| ) | |
| # Enhancer | |
| enh = ImageEnhancer() | |
| enh_upscale_btn.click(fn=enh.upscale, inputs=[enh_image, enh_scale], outputs=enh_output) | |
| enh_skin_btn.click(fn=enh.enhance_skin_texture, inputs=[enh_image, enh_skin], outputs=enh_output) | |
| # Pose Editor | |
| pose = PoseEditor() | |
| pose_extract_btn.click(fn=pose.extract_pose, inputs=pose_image, outputs=pose_output) | |
| def pose_gen_views(img, prompt, n): | |
| r, w = pose.generate_views(img, prompt, n) | |
| return r, w | |
| pose_generate_btn.click( | |
| fn=pose_gen_views, | |
| inputs=[pose_image, pose_prompt, pose_views], | |
| outputs=[pose_output, pose_warning] | |
| ) | |
| # Variations | |
| var = ImageVariationGenerator() | |
| def var_gen(img, prompt, num, seed): | |
| r, w = var.generate_variations(img, prompt, num, seed) | |
| return r, w | |
| var_btn.click( | |
| fn=var_gen, | |
| inputs=[var_image, var_prompt, var_num, var_seed], | |
| outputs=[var_output, var_warning] | |
| ) | |
| # Video | |
| vid = VideoGenerator() | |
| vid_btn.click( | |
| fn=vid.generate_video, | |
| inputs=[vid_prompt, vid_frames, vid_fps, vid_seed], | |
| outputs=[vid_output, vid_warning] | |
| ) | |
| # Audio | |
| audio = AudioGenerator() | |
| audio_music_btn.click( | |
| fn=audio.generate_music, | |
| inputs=[audio_music_prompt, audio_music_duration, audio_music_seed, audio_music_size], | |
| outputs=[audio_music_output, audio_music_warning] | |
| ) | |
| audio_sfx_btn.click( | |
| fn=audio.generate_sfx, | |
| inputs=[audio_sfx_prompt, audio_sfx_duration, audio_sfx_seed], | |
| outputs=[audio_sfx_output, audio_sfx_warning] | |
| ) | |
| # Plot | |
| plot = PlotGenerator() | |
| plot_btn.click( | |
| fn=plot.generate_plot, | |
| inputs=[plot_genre, plot_theme, plot_tone, plot_scenes, plot_setting, plot_characters], | |
| outputs=plot_output | |
| ) | |
| # Film Editor | |
| film = FilmEditor() | |
| film_create_btn.click(fn=film.create_project, inputs=film_project, outputs=film_status) | |
| film_add_btn.click( | |
| fn=film.add_scene, | |
| inputs=[film_project, film_scene_image, film_scene_duration, film_scene_audio], | |
| outputs=film_status | |
| ) | |
| film_render_btn.click( | |
| fn=lambda proj, fps, trans: film.render_project(proj, fps, trans), | |
| inputs=[film_project, film_fps, film_transition], | |
| outputs=[film_output, film_render_status] | |
| ) | |
| # Prompt Helper | |
| def enhance_prompt(basic, style, lighting, quality, camera): | |
| if not basic: | |
| return "", "" | |
| enhanced = f"{camera}, {basic}, {style}, {lighting}, {quality}, best quality, sharp focus, professional photography" | |
| neg = f"blurry, low quality, distorted, deformed, bad anatomy, extra limbs, watermark, signature, amateur, worst quality, low resolution" | |
| return enhanced, neg | |
| prompt_enhance_btn.click( | |
| fn=enhance_prompt, | |
| inputs=[prompt_basic, prompt_style, prompt_lighting, prompt_quality, prompt_camera], | |
| outputs=[prompt_enhanced, prompt_neg] | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| demo = build_ui() | |
| demo.launch(server_name="0.0.0.0", server_port=7860) | |