Spaces:
Running
Running
| import gradio as gr | |
| import torch | |
| import numpy as np | |
| import cv2 | |
| from PIL import Image | |
| import json | |
| import os | |
| from typing import List, Dict, Any | |
| import tempfile | |
| import subprocess | |
| from pathlib import Path | |
| import spaces | |
| import gc | |
| from huggingface_hub import hf_hub_download | |
| import threading | |
| import datetime | |
| import time | |
| # ZeroGPU-compatible imports | |
| from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline | |
| from diffusers import ( | |
| StableDiffusionPipeline, | |
| DDIMScheduler, | |
| DPMSolverMultistepScheduler | |
| ) | |
| import soundfile as sf | |
| import requests | |
| # ZeroGPU compatibility - disable GPU-specific optimizations | |
| FLASH_ATTN_AVAILABLE = False | |
| TRITON_AVAILABLE = False | |
| print("โ ๏ธ ZeroGPU mode - using CPU-optimized operations") | |
| # Global lock to prevent concurrent generations | |
| generation_lock = threading.Lock() | |
| class ProfessionalCartoonFilmGenerator: | |
| def __init__(self): | |
| # ZeroGPU compatibility - force CPU usage | |
| self.device = "cpu" | |
| self.dtype = torch.float32 # Use float32 for CPU compatibility | |
| # Use /tmp directory for Hugging Face Spaces storage | |
| self.output_dir = "/tmp" | |
| print(f"๐ Using Hugging Face temp directory: {self.output_dir}") | |
| # Model configurations for ZeroGPU optimization | |
| self.models_loaded = False | |
| self.flux_available = False | |
| self.flux_pipe = None | |
| self.sd_pipe = None | |
| self.script_model = None | |
| self.script_tokenizer = None | |
| def load_models(self): | |
| """Load ZeroGPU-compatible models for professional generation""" | |
| try: | |
| print("๐ Loading ZeroGPU-compatible models...") | |
| # Clear memory | |
| gc.collect() | |
| print(f"๐ฎ Using device: {self.device} with dtype: {self.dtype}") | |
| # Load Stable Diffusion (CPU optimized) | |
| print("๐ Loading Stable Diffusion (CPU optimized)...") | |
| from diffusers import StableDiffusionPipeline, DDIMScheduler | |
| self.sd_pipe = StableDiffusionPipeline.from_pretrained( | |
| "CompVis/stable-diffusion-v1-4", | |
| torch_dtype=self.dtype, | |
| safety_checker=None, | |
| requires_safety_checker=False, | |
| device_map=None # Force CPU usage | |
| ) | |
| # Configure scheduler for better quality | |
| self.sd_pipe.scheduler = DDIMScheduler.from_config(self.sd_pipe.scheduler.config) | |
| # Force CPU usage for ZeroGPU | |
| self.sd_pipe = self.sd_pipe.to("cpu") | |
| self.sd_pipe.enable_sequential_cpu_offload() # Memory optimization | |
| print("โ Loaded Stable Diffusion v1.4 (CPU optimized)") | |
| # Load script enhancement model (CPU optimized) | |
| print("๐ Loading script enhancement model...") | |
| self.script_model = AutoModelForCausalLM.from_pretrained( | |
| "microsoft/DialoGPT-medium", | |
| torch_dtype=self.dtype, | |
| device_map=None # Force CPU usage | |
| ) | |
| self.script_tokenizer = AutoTokenizer.from_pretrained("microsoft/DialoGPT-medium") | |
| if self.script_tokenizer.pad_token is None: | |
| self.script_tokenizer.pad_token = self.script_tokenizer.eos_token | |
| # Force CPU usage | |
| self.script_model = self.script_model.to("cpu") | |
| print(f"Device set to use {self.device}") | |
| print("โ Script enhancer loaded (CPU optimized)") | |
| print("๐ฌ All ZeroGPU-compatible models loaded!") | |
| return True | |
| except Exception as e: | |
| print(f"โ Model loading failed: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return False | |
| def clear_gpu_memory(self): | |
| """Clear memory (CPU-focused for ZeroGPU)""" | |
| gc.collect() | |
| def optimize_prompt_for_clip(self, prompt: str, max_tokens: int = 70) -> str: | |
| """Optimize prompt to fit within CLIP token limit""" | |
| try: | |
| # Simple word-based token estimation (CLIP uses ~1.3 words per token) | |
| words = prompt.split() | |
| if len(words) <= max_tokens: | |
| return prompt | |
| # Truncate to fit within token limit | |
| optimized_words = words[:max_tokens] | |
| optimized_prompt = " ".join(optimized_words) | |
| print(f"๐ Prompt optimized: {len(words)} words โ {len(optimized_words)} words") | |
| return optimized_prompt | |
| except Exception as e: | |
| print(f"โ ๏ธ Prompt optimization failed: {e}") | |
| # Fallback: return first 50 words | |
| words = prompt.split() | |
| return " ".join(words[:50]) | |
| def create_download_url(self, file_path: str, file_type: str = "file") -> str: | |
| """Create download info for generated content""" | |
| try: | |
| file_name = os.path.basename(file_path) | |
| file_size = os.path.getsize(file_path) / (1024*1024) | |
| # Note: Temp files cannot be accessed via direct URLs in Hugging Face Spaces | |
| download_info = f"๐ฅ Generated {file_type}: {file_name}" | |
| download_info += f"\n ๐ File size: {file_size:.1f} MB" | |
| download_info += f"\n โ ๏ธ Note: Use Gradio File output component to download" | |
| download_info += f"\n ๐ Internal path: {file_path}" | |
| return download_info | |
| except Exception as e: | |
| return f"๐ฅ Generated {file_type} (download info unavailable: {e})" | |
| def generate_professional_script(self, user_input: str) -> Dict[str, Any]: | |
| """Generate a professional cartoon script with detailed character development""" | |
| # Advanced script analysis | |
| words = user_input.lower().split() | |
| # Character analysis | |
| main_character = self._analyze_main_character(words) | |
| setting = self._analyze_setting(words) | |
| theme = self._analyze_theme(words) | |
| genre = self._analyze_genre(words) | |
| mood = self._analyze_mood(words) | |
| # Generate sophisticated character profiles | |
| characters = self._create_detailed_characters(main_character, theme, genre) | |
| # Create professional story structure (8 scenes for perfect pacing) | |
| scenes = self._create_cinematic_scenes(characters, setting, theme, genre, mood, user_input) | |
| return { | |
| "title": f"The {theme.title()}: A {genre.title()} Adventure", | |
| "genre": genre, | |
| "mood": mood, | |
| "theme": theme, | |
| "characters": characters, | |
| "scenes": scenes, | |
| "setting": setting, | |
| "style": f"Professional 2D cartoon animation in {genre} style with cinematic lighting and expressive character animation", | |
| "color_palette": self._generate_color_palette(mood, genre), | |
| "animation_notes": f"Focus on {mood} expressions, smooth character movement, and detailed background art" | |
| } | |
| def _analyze_main_character(self, words): | |
| """Sophisticated character analysis""" | |
| if any(word in words for word in ['girl', 'woman', 'princess', 'heroine', 'daughter', 'sister']): | |
| return "brave young heroine" | |
| elif any(word in words for word in ['boy', 'man', 'hero', 'prince', 'son', 'brother']): | |
| return "courageous young hero" | |
| elif any(word in words for word in ['robot', 'android', 'cyborg', 'machine', 'ai']): | |
| return "friendly robot character" | |
| elif any(word in words for word in ['cat', 'dog', 'fox', 'bear', 'wolf', 'animal']): | |
| return "adorable animal protagonist" | |
| elif any(word in words for word in ['dragon', 'fairy', 'wizard', 'witch', 'magic']): | |
| return "magical creature" | |
| elif any(word in words for word in ['alien', 'space', 'star', 'galaxy']): | |
| return "curious alien visitor" | |
| else: | |
| return "charming protagonist" | |
| def _analyze_setting(self, words): | |
| """Advanced setting analysis""" | |
| if any(word in words for word in ['forest', 'woods', 'trees', 'jungle', 'nature']): | |
| return "enchanted forest with mystical atmosphere" | |
| elif any(word in words for word in ['city', 'town', 'urban', 'street', 'building']): | |
| return "vibrant bustling city with colorful architecture" | |
| elif any(word in words for word in ['space', 'stars', 'planet', 'galaxy', 'cosmic']): | |
| return "spectacular cosmic landscape with nebulae and distant planets" | |
| elif any(word in words for word in ['ocean', 'sea', 'underwater', 'beach', 'water']): | |
| return "beautiful underwater world with coral reefs" | |
| elif any(word in words for word in ['mountain', 'cave', 'valley', 'cliff']): | |
| return "majestic mountain landscape with dramatic vistas" | |
| elif any(word in words for word in ['castle', 'kingdom', 'palace', 'medieval']): | |
| return "magical kingdom with towering castle spires" | |
| elif any(word in words for word in ['school', 'classroom', 'library', 'study']): | |
| return "charming school environment with warm lighting" | |
| else: | |
| return "wonderfully imaginative fantasy world" | |
| def _analyze_theme(self, words): | |
| """Identify story themes""" | |
| if any(word in words for word in ['friend', 'friendship', 'help', 'together', 'team']): | |
| return "power of friendship" | |
| elif any(word in words for word in ['treasure', 'find', 'search', 'discover', 'quest']): | |
| return "epic treasure quest" | |
| elif any(word in words for word in ['save', 'rescue', 'protect', 'danger', 'hero']): | |
| return "heroic rescue mission" | |
| elif any(word in words for word in ['magic', 'magical', 'spell', 'wizard', 'enchant']): | |
| return "magical discovery" | |
| elif any(word in words for word in ['learn', 'grow', 'change', 'journey']): | |
| return "journey of self-discovery" | |
| elif any(word in words for word in ['family', 'home', 'parent', 'love']): | |
| return "importance of family" | |
| else: | |
| return "heartwarming adventure" | |
| def _analyze_genre(self, words): | |
| """Determine animation genre""" | |
| if any(word in words for word in ['adventure', 'quest', 'journey', 'explore']): | |
| return "adventure" | |
| elif any(word in words for word in ['funny', 'comedy', 'laugh', 'silly', 'humor']): | |
| return "comedy" | |
| elif any(word in words for word in ['magic', 'fantasy', 'fairy', 'wizard', 'enchant']): | |
| return "fantasy" | |
| elif any(word in words for word in ['space', 'robot', 'future', 'sci-fi', 'technology']): | |
| return "sci-fi" | |
| elif any(word in words for word in ['mystery', 'secret', 'solve', 'detective']): | |
| return "mystery" | |
| else: | |
| return "family-friendly" | |
| def _analyze_mood(self, words): | |
| """Determine overall mood""" | |
| if any(word in words for word in ['happy', 'joy', 'fun', 'celebrate', 'party']): | |
| return "joyful" | |
| elif any(word in words for word in ['exciting', 'thrill', 'adventure', 'fast']): | |
| return "exciting" | |
| elif any(word in words for word in ['peaceful', 'calm', 'gentle', 'quiet']): | |
| return "peaceful" | |
| elif any(word in words for word in ['mysterious', 'secret', 'hidden', 'unknown']): | |
| return "mysterious" | |
| elif any(word in words for word in ['brave', 'courage', 'strong', 'bold']): | |
| return "inspiring" | |
| else: | |
| return "heartwarming" | |
| def _create_detailed_characters(self, main_char, theme, genre): | |
| """Create detailed character profiles""" | |
| characters = [] | |
| # Main character with detailed description | |
| main_desc = f"Professional cartoon-style {main_char} with large expressive eyes, detailed facial features, vibrant clothing, Disney-Pixar quality design, {genre} aesthetic, highly detailed" | |
| characters.append({ | |
| "name": main_char, | |
| "description": main_desc, | |
| "personality": f"brave, kind, determined, optimistic, perfect for {theme}", | |
| "role": "protagonist", | |
| "animation_style": "lead character quality with detailed expressions" | |
| }) | |
| # Supporting character | |
| support_desc = f"Charming cartoon companion with warm personality, detailed character design, complementary colors to main character, {genre} style, supporting role" | |
| characters.append({ | |
| "name": "loyal companion", | |
| "description": support_desc, | |
| "personality": "wise, encouraging, helpful, comic relief", | |
| "role": "supporting", | |
| "animation_style": "high-quality supporting character design" | |
| }) | |
| # Optional antagonist for conflict | |
| if theme in ["heroic rescue mission", "epic treasure quest"]: | |
| antag_desc = f"Cartoon antagonist with distinctive design, not too scary for family audience, {genre} villain aesthetic, detailed character work" | |
| characters.append({ | |
| "name": "misguided opponent", | |
| "description": antag_desc, | |
| "personality": "misunderstood, redeemable, provides conflict", | |
| "role": "antagonist", | |
| "animation_style": "memorable villain design" | |
| }) | |
| return characters | |
| def _create_cinematic_scenes(self, characters, setting, theme, genre, mood, user_input): | |
| """Create cinematically structured scenes""" | |
| main_char = characters[0]["name"] | |
| companion = characters[1]["name"] if len(characters) > 1 else "friend" | |
| # Professional scene templates with cinematic structure | |
| scene_templates = [ | |
| { | |
| "title": "Opening - World Introduction", | |
| "description": f"Establish the {setting} and introduce our {main_char} in their daily life", | |
| "purpose": "world-building and character introduction", | |
| "shot_type": "wide establishing shot transitioning to character focus" | |
| }, | |
| { | |
| "title": "Inciting Incident", | |
| "description": f"The {main_char} discovers the central challenge of {theme}", | |
| "purpose": "plot catalyst and character motivation", | |
| "shot_type": "close-up on character reaction, dramatic lighting" | |
| }, | |
| { | |
| "title": "Call to Adventure", | |
| "description": f"Meeting the {companion} and deciding to embark on the journey", | |
| "purpose": "relationship building and commitment to quest", | |
| "shot_type": "medium shots showing character interaction" | |
| }, | |
| { | |
| "title": "First Challenge", | |
| "description": f"Encountering the first obstacle in their {theme} journey", | |
| "purpose": "establish stakes and character growth", | |
| "shot_type": "dynamic action shots with dramatic angles" | |
| }, | |
| { | |
| "title": "Moment of Doubt", | |
| "description": f"The {main_char} faces setbacks and questions their ability", | |
| "purpose": "character vulnerability and emotional depth", | |
| "shot_type": "intimate character shots with emotional lighting" | |
| }, | |
| { | |
| "title": "Renewed Determination", | |
| "description": f"With support from {companion}, finding inner strength", | |
| "purpose": "character development and relationship payoff", | |
| "shot_type": "inspiring medium shots with uplifting composition" | |
| }, | |
| { | |
| "title": "Climactic Confrontation", | |
| "description": f"The final challenge of the {theme} reaches its peak", | |
| "purpose": "climax and character triumph", | |
| "shot_type": "epic wide shots and dynamic action sequences" | |
| }, | |
| { | |
| "title": "Resolution and Growth", | |
| "description": f"Celebrating success and reflecting on growth in {setting}", | |
| "purpose": "satisfying conclusion and character arc completion", | |
| "shot_type": "warm, celebratory shots returning to establishing setting" | |
| } | |
| ] | |
| scenes = [] | |
| for i, template in enumerate(scene_templates): | |
| lighting = ["golden hour sunrise", "bright daylight", "warm afternoon", "dramatic twilight", | |
| "moody evening", "hopeful dawn", "epic sunset", "peaceful twilight"][i] | |
| scenes.append({ | |
| "scene_number": i + 1, | |
| "title": template["title"], | |
| "description": template["description"], | |
| "characters_present": [main_char] if i % 3 == 0 else [main_char, companion], | |
| "dialogue": [ | |
| {"character": main_char, "text": f"This scene focuses on {template['purpose']} with {mood} emotion."} | |
| ], | |
| "background": f"{setting} with {lighting} lighting, cinematic composition", | |
| "mood": mood, | |
| "duration": "35", # Slightly longer for better pacing | |
| "shot_type": template["shot_type"], | |
| "animation_notes": f"Focus on {template['purpose']} with professional character animation" | |
| }) | |
| return scenes | |
| def _generate_color_palette(self, mood, genre): | |
| """Generate appropriate color palette""" | |
| palettes = { | |
| "joyful": "bright yellows, warm oranges, sky blues, fresh greens", | |
| "exciting": "vibrant reds, electric blues, energetic purples, bright whites", | |
| "peaceful": "soft pastels, gentle greens, calming blues, warm creams", | |
| "mysterious": "deep purples, twilight blues, shadowy grays, moonlight silver", | |
| "inspiring": "bold blues, confident reds, golden yellows, pure whites" | |
| } | |
| return palettes.get(mood, "balanced warm and cool tones") | |
| def generate_professional_character_images(self, characters: List[Dict]) -> Dict[str, str]: | |
| """Generate professional character images with consistency (ZeroGPU compatible)""" | |
| character_images = {} | |
| print(f"๐ญ Generating {len(characters)} professional character designs...") | |
| # Check if we have Stable Diffusion pipeline available | |
| if not hasattr(self, 'sd_pipe') or self.sd_pipe is None: | |
| print("โ Stable Diffusion not loaded - please call load_models() first") | |
| return character_images | |
| pipeline = self.sd_pipe | |
| model_name = "Stable Diffusion (CPU)" | |
| print(f"๐จ Using {model_name} for character generation") | |
| for character in characters: | |
| character_name = character['name'] | |
| print(f"\n๐จ Generating character: {character_name}") | |
| try: | |
| # Build comprehensive character prompt for CPU generation | |
| base_prompt = f"Professional cartoon character design, {character['name']}, {character['description']}" | |
| # CPU-optimized prompt | |
| prompt = f"{base_prompt}, anime style, cartoon character, clean background, high quality, detailed, 2D animation style, character sheet, simple design" | |
| # Optimize prompt for CLIP | |
| prompt = self.optimize_prompt_for_clip(prompt, max_tokens=60) # Shorter for CPU | |
| print(f"๐ Character prompt: {prompt}") | |
| # CPU-optimized generation settings | |
| image = pipeline( | |
| prompt=prompt, | |
| width=512, # Smaller for CPU | |
| height=512, | |
| num_inference_steps=20, # Fewer steps for CPU | |
| guidance_scale=7.5, | |
| generator=torch.Generator(device="cpu").manual_seed(42) | |
| ).images[0] | |
| # Upscale for better quality | |
| image = image.resize((1024, 1024), Image.Resampling.LANCZOS) | |
| # Save character image | |
| char_path = f"{self.output_dir}/char_{character['name'].replace(' ', '_')}.png" | |
| image.save(char_path) | |
| # Verify file was created | |
| if os.path.exists(char_path): | |
| file_size = os.path.getsize(char_path) | |
| character_images[character_name] = char_path | |
| # Create download URL | |
| download_info = self.create_download_url(char_path, f"character_{character['name']}") | |
| print(f"๐ฅ Generated character_{character['name']}: char_{character['name'].replace(' ', '_')}.png") | |
| print(f" ๐ File size: {file_size / (1024*1024):.1f} MB") | |
| print(f" ๐ Internal path: {char_path}") | |
| print(download_info) | |
| # Clear memory after each generation | |
| gc.collect() | |
| else: | |
| print(f"โ Failed to save character image: {char_path}") | |
| except Exception as e: | |
| print(f"โ Error generating character {character_name}: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| # Continue with next character | |
| continue | |
| print(f"\n๐ Character generation summary:") | |
| print(f" - Characters requested: {len(characters)}") | |
| print(f" - Characters generated: {len(character_images)}") | |
| print(f" - Success rate: {len(character_images)/len(characters)*100:.1f}%") | |
| return character_images | |
| def generate_cinematic_backgrounds(self, scenes: List[Dict], color_palette: str) -> Dict[int, str]: | |
| """Generate professional cinematic backgrounds for each scene (ZeroGPU compatible)""" | |
| background_images = {} | |
| print(f"๐๏ธ Generating {len(scenes)} cinematic backgrounds...") | |
| # Check if we have Stable Diffusion pipeline available | |
| if not hasattr(self, 'sd_pipe') or self.sd_pipe is None: | |
| print("โ Stable Diffusion not loaded - please call load_models() first") | |
| return background_images | |
| pipeline = self.sd_pipe | |
| model_name = "Stable Diffusion (CPU)" | |
| print(f"๐จ Using {model_name} for background generation") | |
| for scene in scenes: | |
| scene_num = scene['scene_number'] | |
| print(f"\n๐ Generating background for scene {scene_num}") | |
| try: | |
| # Build cinematic background prompt for CPU generation | |
| background_desc = scene['background'] | |
| mood = scene.get('mood', 'neutral') | |
| shot_type = scene.get('shot_type', 'medium shot') | |
| lighting = scene.get('lighting', 'natural lighting') | |
| base_prompt = f"Cinematic background scene, {background_desc}, {mood} atmosphere, {lighting}" | |
| # CPU-optimized prompt | |
| prompt = f"{base_prompt}, anime style background, detailed landscape, high quality, cinematic, {color_palette} color palette, no people, simple design" | |
| # Optimize for CLIP | |
| prompt = self.optimize_prompt_for_clip(prompt, max_tokens=60) # Shorter for CPU | |
| print(f"๐ Background prompt: {prompt}") | |
| # CPU-optimized generation settings | |
| image = pipeline( | |
| prompt=prompt, | |
| width=512, # Smaller for CPU | |
| height=384, # 4:3 aspect ratio | |
| num_inference_steps=20, # Fewer steps for CPU | |
| guidance_scale=7.5, | |
| generator=torch.Generator(device="cpu").manual_seed(scene_num * 10) | |
| ).images[0] | |
| # Upscale for better quality | |
| image = image.resize((1024, 768), Image.Resampling.LANCZOS) | |
| # Save background image | |
| bg_path = f"{self.output_dir}/bg_scene_{scene_num}.png" | |
| image.save(bg_path) | |
| # Verify file was created | |
| if os.path.exists(bg_path): | |
| file_size = os.path.getsize(bg_path) | |
| background_images[scene_num] = bg_path | |
| # Create download URL | |
| download_info = self.create_download_url(bg_path, f"background_scene_{scene_num}") | |
| print(f"๐ฅ Generated background_scene_{scene_num}: bg_scene_{scene_num}.png") | |
| print(f" ๐ File size: {file_size / (1024*1024):.1f} MB") | |
| print(f" ๐ Internal path: {bg_path}") | |
| print(download_info) | |
| # Clear memory after each generation | |
| gc.collect() | |
| else: | |
| print(f"โ Failed to save background image: {bg_path}") | |
| except Exception as e: | |
| print(f"โ Error generating background for scene {scene['scene_number']}: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| # Continue with next scene | |
| continue | |
| print(f"\n๐ Background generation summary:") | |
| print(f" - Scenes requested: {len(scenes)}") | |
| print(f" - Backgrounds generated: {len(background_images)}") | |
| print(f" - Success rate: {len(background_images)/len(scenes)*100:.1f}%") | |
| return background_images | |
| def setup_opensora_for_video(self): | |
| """Setup Open-Sora for professional video generation""" | |
| try: | |
| print("๐ฌ Setting up Open-Sora 2.0 for video generation...") | |
| # Import torch here to avoid the UnboundLocalError | |
| import torch | |
| # Check available GPU memory | |
| if torch.cuda.is_available(): | |
| gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3) | |
| print(f"๐ฎ Available GPU memory: {gpu_memory:.1f} GB") | |
| if gpu_memory < 16: | |
| print("โ ๏ธ Warning: Open-Sora requires 16GB+ GPU memory for stable operation") | |
| # Check if we're already in the right directory | |
| current_dir = os.getcwd() | |
| opensora_dir = os.path.join(current_dir, "Open-Sora") | |
| # Clone Open-Sora repository if it doesn't exist | |
| if not os.path.exists(opensora_dir): | |
| print("๐ฅ Cloning Open-Sora repository...") | |
| try: | |
| result = subprocess.run([ | |
| "git", "clone", "https://github.com/hpcaitech/Open-Sora.git" | |
| ], check=True, capture_output=True, text=True, timeout=120) | |
| print("โ Repository cloned successfully") | |
| except subprocess.TimeoutExpired: | |
| print("โ Repository cloning timed out") | |
| return False | |
| except subprocess.CalledProcessError as e: | |
| print(f"โ Repository cloning failed: {e.stderr}") | |
| return False | |
| # Check if the repository was cloned successfully | |
| if not os.path.exists(opensora_dir): | |
| print("โ Failed to clone Open-Sora repository") | |
| return False | |
| # Check for required scripts | |
| script_path = os.path.join(opensora_dir, "scripts/diffusion/inference.py") | |
| config_path = os.path.join(opensora_dir, "configs/diffusion/inference/t2i2v_256px.py") | |
| print(f"๐ Checking for script: {script_path}") | |
| print(f"๐ Checking for config: {config_path}") | |
| if not os.path.exists(script_path): | |
| print(f"โ Required script not found: {script_path}") | |
| # List available files for debugging | |
| scripts_dir = os.path.join(opensora_dir, "scripts") | |
| if os.path.exists(scripts_dir): | |
| print(f"๐ Available in scripts/: {os.listdir(scripts_dir)}") | |
| return False | |
| if not os.path.exists(config_path): | |
| print(f"โ Required config not found: {config_path}") | |
| # List available configs for debugging | |
| configs_dir = os.path.join(opensora_dir, "configs") | |
| if os.path.exists(configs_dir): | |
| print(f"๐ Available in configs/: {os.listdir(configs_dir)}") | |
| return False | |
| # Check if model weights exist | |
| ckpts_dir = os.path.join(opensora_dir, "ckpts") | |
| if not os.path.exists(ckpts_dir): | |
| print("๐ฅ Downloading Open-Sora 2.0 model...") | |
| try: | |
| # Use smaller timeout and check if huggingface-cli is available | |
| result = subprocess.run([ | |
| "huggingface-cli", "download", "hpcai-tech/Open-Sora-v2", | |
| "--local-dir", ckpts_dir | |
| ], check=True, capture_output=True, text=True, timeout=300) | |
| print("โ Model downloaded successfully") | |
| except subprocess.TimeoutExpired: | |
| print("โ Model download timed out (5 minutes)") | |
| return False | |
| except subprocess.CalledProcessError as e: | |
| print(f"โ Model download failed: {e.stderr}") | |
| return False | |
| except FileNotFoundError: | |
| print("โ huggingface-cli not found - cannot download model") | |
| return False | |
| else: | |
| print("โ Model weights already exist") | |
| # Check dependencies | |
| try: | |
| import torch.distributed | |
| print("โ torch.distributed available") | |
| except ImportError: | |
| print("โ torch.distributed not available") | |
| return False | |
| # Test if torchrun is available | |
| try: | |
| result = subprocess.run(["torchrun", "--help"], | |
| capture_output=True, text=True, timeout=10) | |
| if result.returncode == 0: | |
| print("โ torchrun available") | |
| else: | |
| print("โ torchrun not working properly") | |
| return False | |
| except (subprocess.TimeoutExpired, FileNotFoundError): | |
| print("โ torchrun not found") | |
| return False | |
| print("โ Open-Sora setup completed") | |
| return True | |
| except Exception as e: | |
| print(f"โ Open-Sora setup failed: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return False | |
| def generate_professional_videos(self, scenes: List[Dict], character_images: Dict, background_images: Dict) -> List[str]: | |
| """Generate professional videos using Open-Sora 2.0""" | |
| scene_videos = [] | |
| print(f"๐ฅ Starting video generation for {len(scenes)} scenes...") | |
| print(f"๐ Background images available: {list(background_images.keys())}") | |
| # Try to use Open-Sora for professional video generation | |
| opensora_available = self.setup_opensora_for_video() | |
| print(f"๐ฌ Open-Sora available: {opensora_available}") | |
| for scene in scenes: | |
| scene_num = scene['scene_number'] | |
| print(f"\n๐ฌ Processing scene {scene_num}...") | |
| try: | |
| if opensora_available: | |
| print(f"๐ฌ Attempting Open-Sora generation for scene {scene_num}...") | |
| video_path = self._generate_opensora_video(scene, character_images, background_images) | |
| if video_path: | |
| print(f"โ Open-Sora video generated for scene {scene_num}") | |
| else: | |
| print(f"โ Open-Sora failed for scene {scene_num}, trying lightweight animation...") | |
| video_path = self._create_lightweight_animated_video(scene, character_images, background_images) | |
| if not video_path: | |
| print(f"๐ Lightweight animation failed, trying static video...") | |
| video_path = self._create_professional_static_video(scene, background_images) | |
| # If professional video fails, try simple video | |
| if not video_path: | |
| print(f"๐ All methods failed, trying simple video for scene {scene_num}...") | |
| video_path = self._create_simple_static_video(scene, background_images) | |
| else: | |
| print(f"๐ฌ Open-Sora not available, using lightweight animation for scene {scene_num}...") | |
| # First try lightweight animation, then fallback to static | |
| video_path = self._create_lightweight_animated_video(scene, character_images, background_images) | |
| if not video_path: | |
| print(f"๐ Lightweight animation failed, using static video fallback...") | |
| video_path = self._create_professional_static_video(scene, background_images) | |
| if video_path and os.path.exists(video_path): | |
| scene_videos.append(video_path) | |
| # Create download URL for video | |
| download_info = self.create_download_url(video_path, f"video_scene_{scene_num}") | |
| print(f"โ Generated professional video for scene {scene_num}") | |
| print(download_info) | |
| else: | |
| print(f"โ No video generated for scene {scene_num}") | |
| except Exception as e: | |
| print(f"โ Error in scene {scene_num}: {e}") | |
| # Create fallback video | |
| if scene_num in background_images: | |
| print(f"๐ Creating emergency fallback for scene {scene_num}...") | |
| try: | |
| video_path = self._create_professional_static_video(scene, background_images) | |
| if video_path and os.path.exists(video_path): | |
| scene_videos.append(video_path) | |
| print(f"โ Emergency fallback video created for scene {scene_num}") | |
| except Exception as e2: | |
| print(f"โ Emergency fallback also failed for scene {scene_num}: {e2}") | |
| print(f"\n๐ Video generation summary:") | |
| print(f" - Scenes processed: {len(scenes)}") | |
| print(f" - Videos generated: {len(scene_videos)}") | |
| print(f" - Videos list: {scene_videos}") | |
| return scene_videos | |
| def _generate_opensora_video(self, scene: Dict, character_images: Dict, background_images: Dict) -> str: | |
| """Generate video using Open-Sora 2.0""" | |
| try: | |
| characters_text = ", ".join(scene['characters_present']) | |
| # Professional prompt for Open-Sora (optimized for CLIP token limit) | |
| characters_text = characters_text[:60] # Limit character text | |
| background_desc = scene['background'][:60] | |
| mood = scene['mood'][:20] | |
| shot_type = scene.get('shot_type', 'medium shot')[:15] | |
| animation_notes = scene.get('animation_notes', 'high-quality animation')[:30] | |
| prompt = f"Professional 2D cartoon animation, {characters_text} in {background_desc}, {mood} mood, {shot_type}, smooth animation, Disney quality, cinematic lighting, {animation_notes}" | |
| # Use the optimization function to ensure CLIP compatibility | |
| prompt = self.optimize_prompt_for_clip(prompt) | |
| print(f"๐ฌ Open-Sora prompt: {prompt}") | |
| video_path = f"{self.output_dir}/video_scene_{scene['scene_number']}.mp4" | |
| # Get the correct Open-Sora directory | |
| current_dir = os.getcwd() | |
| opensora_dir = os.path.join(current_dir, "Open-Sora") | |
| if not os.path.exists(opensora_dir): | |
| print("โ Open-Sora directory not found") | |
| return None | |
| # Check for required files | |
| script_path = os.path.join(opensora_dir, "scripts/diffusion/inference.py") | |
| config_path = os.path.join(opensora_dir, "configs/diffusion/inference/t2i2v_256px.py") | |
| if not os.path.exists(script_path): | |
| print(f"โ Open-Sora script not found: {script_path}") | |
| return None | |
| if not os.path.exists(config_path): | |
| print(f"โ Open-Sora config not found: {config_path}") | |
| return None | |
| # Run Open-Sora inference | |
| cmd = [ | |
| "torchrun", "--nproc_per_node", "1", "--standalone", | |
| "scripts/diffusion/inference.py", | |
| "configs/diffusion/inference/t2i2v_256px.py", | |
| "--save-dir", self.output_dir, | |
| "--prompt", prompt, | |
| "--num_frames", "25", # ~1 second at 25fps | |
| "--aspect_ratio", "4:3", | |
| "--motion-score", "6" # High motion for dynamic scenes | |
| ] | |
| print(f"๐ฌ Running Open-Sora command: {' '.join(cmd)}") | |
| result = subprocess.run(cmd, capture_output=True, text=True, cwd=opensora_dir, timeout=300) | |
| print(f"๐ฌ Open-Sora return code: {result.returncode}") | |
| if result.stdout: | |
| print(f"๐ฌ Open-Sora stdout: {result.stdout}") | |
| if result.stderr: | |
| print(f"โ Open-Sora stderr: {result.stderr}") | |
| if result.returncode == 0: | |
| # Find generated video file | |
| for file in os.listdir(self.output_dir): | |
| if file.endswith('.mp4') and 'scene' not in file: | |
| src_path = os.path.join(self.output_dir, file) | |
| os.rename(src_path, video_path) | |
| print(f"โ Open-Sora video generated: {video_path}") | |
| return video_path | |
| print("โ Open-Sora completed but no video file found") | |
| return None | |
| else: | |
| print(f"โ Open-Sora failed with return code: {result.returncode}") | |
| return None | |
| except subprocess.TimeoutExpired: | |
| print("โ Open-Sora generation timed out (5 minutes)") | |
| return None | |
| except Exception as e: | |
| print(f"โ Open-Sora generation failed: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return None | |
| def _create_professional_static_video(self, scene: Dict, background_images: Dict) -> str: | |
| """Create professional static video with advanced effects""" | |
| scene_num = scene['scene_number'] | |
| if scene_num not in background_images: | |
| print(f"โ No background image for scene {scene_num}") | |
| return None | |
| video_path = f"{self.output_dir}/video_scene_{scene_num}.mp4" | |
| try: | |
| print(f"๐ฌ Creating static video for scene {scene_num}...") | |
| # Load background image | |
| bg_path = background_images[scene_num] | |
| print(f"๐ Loading background from: {bg_path}") | |
| if not os.path.exists(bg_path): | |
| print(f"โ Background file not found: {bg_path}") | |
| return None | |
| image = Image.open(bg_path) | |
| img_array = np.array(image.resize((1024, 768))) # 4:3 aspect ratio | |
| img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR) | |
| print(f"๐ Image size: {img_array.shape}") | |
| # Professional video settings | |
| fourcc = cv2.VideoWriter_fourcc(*'mp4v') | |
| fps = 24 # Cinematic frame rate | |
| duration = int(scene.get('duration', 35)) | |
| total_frames = duration * fps | |
| print(f"๐ฌ Video settings: {fps}fps, {duration}s duration, {total_frames} frames") | |
| out = cv2.VideoWriter(video_path, fourcc, fps, (1024, 768)) | |
| if not out.isOpened(): | |
| print(f"โ Failed to open video writer for {video_path}") | |
| return None | |
| # Advanced animation effects based on scene mood and type | |
| print(f"๐ฌ Generating {total_frames} frames...") | |
| for i in range(total_frames): | |
| if i % 100 == 0: # Progress update every 100 frames | |
| print(f" Frame {i}/{total_frames} ({i/total_frames*100:.1f}%)") | |
| frame = img_array.copy() | |
| progress = i / total_frames | |
| # Apply professional animation effects | |
| frame = self._apply_cinematic_effects(frame, scene, progress) | |
| out.write(frame) | |
| print(f"โ All {total_frames} frames generated") | |
| out.release() | |
| if os.path.exists(video_path): | |
| file_size = os.path.getsize(video_path) | |
| print(f"โ Static video created: {video_path} ({file_size / (1024*1024):.1f} MB)") | |
| return video_path | |
| else: | |
| print(f"โ Video file not created: {video_path}") | |
| return None | |
| except Exception as e: | |
| print(f"โ Professional static video creation failed for scene {scene_num}: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return None | |
| def _apply_cinematic_effects(self, frame, scene, progress): | |
| """Apply professional cinematic effects""" | |
| try: | |
| h, w = frame.shape[:2] | |
| # Choose effect based on scene mood and type | |
| mood = scene.get('mood', 'heartwarming') | |
| shot_type = scene.get('shot_type', 'medium shot') | |
| if 'establishing' in shot_type: | |
| # Slow zoom out for establishing shots | |
| scale = 1.15 - progress * 0.1 | |
| center_x, center_y = w // 2, h // 2 | |
| M = cv2.getRotationMatrix2D((center_x, center_y), 0, scale) | |
| frame = cv2.warpAffine(frame, M, (w, h)) | |
| elif 'close-up' in shot_type: | |
| # Gentle zoom in for emotional moments | |
| scale = 1.0 + progress * 0.08 | |
| center_x, center_y = w // 2, h // 2 | |
| M = cv2.getRotationMatrix2D((center_x, center_y), 0, scale) | |
| frame = cv2.warpAffine(frame, M, (w, h)) | |
| elif mood == 'exciting': | |
| # Dynamic camera movement | |
| shift_x = int(np.sin(progress * 4 * np.pi) * 8) | |
| shift_y = int(np.cos(progress * 2 * np.pi) * 4) | |
| M = np.float32([[1, 0, shift_x], [0, 1, shift_y]]) | |
| frame = cv2.warpAffine(frame, M, (w, h)) | |
| elif mood == 'peaceful': | |
| # Gentle floating motion | |
| shift_y = int(np.sin(progress * 2 * np.pi) * 6) | |
| M = np.float32([[1, 0, 0], [0, 1, shift_y]]) | |
| frame = cv2.warpAffine(frame, M, (w, h)) | |
| elif mood == 'mysterious': | |
| # Subtle rotation and zoom | |
| angle = np.sin(progress * np.pi) * 2 | |
| scale = 1.0 + np.sin(progress * np.pi) * 0.05 | |
| center_x, center_y = w // 2, h // 2 | |
| M = cv2.getRotationMatrix2D((center_x, center_y), angle, scale) | |
| frame = cv2.warpAffine(frame, M, (w, h)) | |
| else: | |
| # Default: gentle zoom for heartwarming scenes | |
| scale = 1.0 + progress * 0.03 | |
| center_x, center_y = w // 2, h // 2 | |
| M = cv2.getRotationMatrix2D((center_x, center_y), 0, scale) | |
| frame = cv2.warpAffine(frame, M, (w, h)) | |
| return frame | |
| except Exception as e: | |
| print(f"โ ๏ธ Cinematic effect failed: {e}, using original frame") | |
| return frame | |
| def _create_simple_static_video(self, scene: Dict, background_images: Dict) -> str: | |
| """Create a simple static video without complex effects""" | |
| scene_num = scene['scene_number'] | |
| if scene_num not in background_images: | |
| print(f"โ No background image for scene {scene_num}") | |
| return None | |
| video_path = f"{self.output_dir}/video_simple_scene_{scene_num}.mp4" | |
| try: | |
| print(f"๐ฌ Creating simple video for scene {scene_num}...") | |
| # Load background image | |
| bg_path = background_images[scene_num] | |
| print(f"๐ Loading background from: {bg_path}") | |
| if not os.path.exists(bg_path): | |
| print(f"โ Background file not found: {bg_path}") | |
| return None | |
| image = Image.open(bg_path) | |
| img_array = np.array(image.resize((1024, 768))) # 4:3 aspect ratio | |
| img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR) | |
| print(f"๐ Image size: {img_array.shape}") | |
| # Simple video settings | |
| fourcc = cv2.VideoWriter_fourcc(*'mp4v') | |
| fps = 24 | |
| duration = 10 # Shorter duration for simple video | |
| total_frames = duration * fps | |
| print(f"๐ฌ Simple video settings: {fps}fps, {duration}s duration, {total_frames} frames") | |
| out = cv2.VideoWriter(video_path, fourcc, fps, (1024, 768)) | |
| if not out.isOpened(): | |
| print(f"โ Failed to open simple video writer for {video_path}") | |
| return None | |
| # Simple static video - just repeat the same frame | |
| print(f"๐ฌ Generating {total_frames} simple frames...") | |
| for i in range(total_frames): | |
| if i % 50 == 0: # Progress update every 50 frames | |
| print(f" Frame {i}/{total_frames} ({i/total_frames*100:.1f}%)") | |
| # Just use the same frame without effects | |
| out.write(img_array) | |
| print(f"โ All {total_frames} simple frames generated") | |
| out.release() | |
| if os.path.exists(video_path): | |
| file_size = os.path.getsize(video_path) | |
| print(f"โ Simple video created: {video_path} ({file_size / (1024*1024):.1f} MB)") | |
| return video_path | |
| else: | |
| print(f"โ Simple video file not created: {video_path}") | |
| return None | |
| except Exception as e: | |
| print(f"โ Simple video creation failed for scene {scene_num}: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return None | |
| def _create_emergency_fallback_video(self, script_data: Dict) -> str: | |
| """Create emergency fallback video when all else fails""" | |
| try: | |
| print("๐ Creating emergency fallback video...") | |
| width, height = 1024, 768 | |
| background_color = (100, 150, 200) # Blue-ish color | |
| # Create video | |
| video_path = f"{self.output_dir}/video_emergency_fallback.mp4" | |
| fourcc = cv2.VideoWriter_fourcc(*'mp4v') | |
| fps = 24 | |
| duration = 30 # 30 seconds | |
| total_frames = duration * fps | |
| out = cv2.VideoWriter(video_path, fourcc, fps, (width, height)) | |
| if not out.isOpened(): | |
| print("โ Failed to open emergency video writer") | |
| return None | |
| # Create simple animated background | |
| for i in range(total_frames): | |
| # Create frame with proper uint8 type | |
| frame = np.full((height, width, 3), background_color, dtype=np.uint8) | |
| # Add simple animation (color shift) with proper clamping | |
| progress = i / total_frames | |
| color_shift = int(50 * np.sin(progress * 2 * np.pi)) | |
| # Ensure all values stay within uint8 bounds (0-255) | |
| new_blue = np.clip(frame[:, :, 0].astype(np.int16) + color_shift, 0, 255).astype(np.uint8) | |
| frame[:, :, 0] = new_blue | |
| # Add text | |
| font = cv2.FONT_HERSHEY_SIMPLEX | |
| text = f"Cartoon Film: {script_data.get('title', 'Adventure')}" | |
| text_size = cv2.getTextSize(text, font, 1, 2)[0] | |
| text_x = (width - text_size[0]) // 2 | |
| text_y = height // 2 | |
| cv2.putText(frame, text, (text_x, text_y), font, 1, (255, 255, 255), 2) | |
| out.write(frame) | |
| out.release() | |
| if os.path.exists(video_path): | |
| print(f"โ Emergency fallback video created: {video_path}") | |
| return video_path | |
| else: | |
| print("โ Emergency fallback video file not created") | |
| return None | |
| except Exception as e: | |
| print(f"โ Emergency fallback video creation failed: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return None | |
| def merge_professional_film(self, scene_videos: List[str], script_data: Dict) -> str: | |
| """Merge videos into professional cartoon film""" | |
| if not scene_videos: | |
| print("โ No videos to merge") | |
| return None | |
| final_video_path = f"{self.output_dir}/video_professional_cartoon_film.mp4" | |
| try: | |
| print("๐๏ธ Creating professional cartoon film...") | |
| # Create concat file | |
| concat_file = f"{self.output_dir}/concat_list.txt" | |
| with open(concat_file, 'w') as f: | |
| for video in scene_videos: | |
| if os.path.exists(video): | |
| f.write(f"file '{os.path.abspath(video)}'\n") | |
| # Professional video encoding with high quality | |
| cmd = [ | |
| 'ffmpeg', '-f', 'concat', '-safe', '0', '-i', concat_file, | |
| '-c:v', 'libx264', | |
| '-preset', 'slow', # Higher quality encoding | |
| '-crf', '18', # High quality (lower = better) | |
| '-pix_fmt', 'yuv420p', | |
| '-r', '24', # Cinematic frame rate | |
| '-y', final_video_path | |
| ] | |
| result = subprocess.run(cmd, capture_output=True, text=True) | |
| if result.returncode == 0: | |
| print("โ Professional cartoon film created successfully") | |
| return final_video_path | |
| else: | |
| print(f"โ FFmpeg error: {result.stderr}") | |
| return None | |
| except Exception as e: | |
| print(f"โ Video merging failed: {e}") | |
| return None | |
| def generate_professional_cartoon_film(self, script: str) -> tuple: | |
| """Main function to generate professional-quality cartoon film (ZeroGPU compatible)""" | |
| try: | |
| print("๐ฌ Starting professional cartoon film generation...") | |
| # Step 0: Load models first (critical!) | |
| print("๐ Loading AI models...") | |
| models_loaded = self.load_models() | |
| if not models_loaded: | |
| print("โ Failed to load models - cannot generate content") | |
| error_info = { | |
| "error": True, | |
| "message": "Failed to load AI models", | |
| "characters": [], | |
| "scenes": [], | |
| "style": "Model loading failed" | |
| } | |
| return None, error_info, "โ Failed to load AI models", [], [], None, None, [] | |
| # Step 1: Generate professional script | |
| print("๐ Creating professional script structure...") | |
| script_data = self.generate_professional_script(script) | |
| print(f"โ Script generated with {len(script_data['scenes'])} scenes") | |
| # Save script to file | |
| print("๐ Saving script to file...") | |
| script_file_path = self.save_script_to_file(script_data, script) | |
| # Step 2: Generate high-quality characters | |
| print("๐ญ Creating professional character designs...") | |
| character_images = self.generate_professional_character_images(script_data['characters']) | |
| print(f"โ Characters generated: {list(character_images.keys())}") | |
| # Step 3: Generate cinematic backgrounds | |
| print("๐๏ธ Creating cinematic backgrounds...") | |
| background_images = self.generate_cinematic_backgrounds( | |
| script_data['scenes'], | |
| script_data['color_palette'] | |
| ) | |
| print(f"โ Backgrounds generated: {list(background_images.keys())}") | |
| # Step 4: Generate professional videos | |
| print("๐ฅ Creating professional animated scenes...") | |
| scene_videos = self.generate_professional_videos( | |
| script_data['scenes'], | |
| character_images, | |
| background_images | |
| ) | |
| print(f"โ Videos generated: {len(scene_videos)} videos") | |
| # Step 5: Merge into professional film | |
| if scene_videos: | |
| print("๐๏ธ Creating final professional cartoon film...") | |
| final_video = self.merge_professional_film(scene_videos, script_data) | |
| if final_video and os.path.exists(final_video): | |
| file_size = os.path.getsize(final_video) / (1024*1024) | |
| # Create download URL for final video | |
| download_info = self.create_download_url(final_video, "final_cartoon_film") | |
| print(f"โ Professional cartoon film generation complete!") | |
| print(download_info) | |
| # Prepare character and background files for galleries | |
| char_files = list(character_images.values()) if character_images else [] | |
| bg_files = list(background_images.values()) if background_images else [] | |
| # Create download links for all files | |
| all_files = {} | |
| if script_file_path: | |
| all_files["script"] = script_file_path | |
| if final_video: | |
| all_files["video"] = final_video | |
| all_files.update(character_images) | |
| all_files.update(background_images) | |
| download_links = self.create_download_links(all_files) | |
| script_file, video_file = self.get_download_files(all_files) | |
| return final_video, script_data, f"โ Professional cartoon film generated successfully! ({file_size:.1f} MB)", char_files, bg_files, script_file, video_file, download_links | |
| else: | |
| print("โ ๏ธ Video merging failed") | |
| return None, script_data, "โ ๏ธ Video merging failed", [], [], None, None, [] | |
| else: | |
| print("โ No videos to merge - video generation failed") | |
| print("๐ Creating emergency fallback video...") | |
| # Create at least one simple video as fallback | |
| try: | |
| emergency_video = self._create_emergency_fallback_video(script_data) | |
| if emergency_video and os.path.exists(emergency_video): | |
| file_size = os.path.getsize(emergency_video) / (1024*1024) | |
| # Create download URL for emergency video | |
| download_info = self.create_download_url(emergency_video, "emergency_fallback_video") | |
| print(f"โ Emergency fallback video created") | |
| print(download_info) | |
| # Create download links for emergency files | |
| all_files = {} | |
| if script_file_path: | |
| all_files["script"] = script_file_path | |
| if emergency_video: | |
| all_files["video"] = emergency_video | |
| all_files.update(character_images) | |
| all_files.update(background_images) | |
| download_links = self.create_download_links(all_files) | |
| script_file, video_file = self.get_download_files(all_files) | |
| return emergency_video, script_data, f"โ ๏ธ Emergency fallback video created ({file_size:.1f} MB)", [], [], script_file, video_file, download_links | |
| else: | |
| return None, script_data, "โ No videos generated - all methods failed", [], [], None, None, [] | |
| except Exception as e: | |
| print(f"โ Emergency fallback also failed: {e}") | |
| return None, script_data, "โ No videos generated - all methods failed", [], [], None, None, [] | |
| except Exception as e: | |
| print(f"โ Generation failed: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| error_info = { | |
| "error": True, | |
| "message": str(e), | |
| "characters": [], | |
| "scenes": [], | |
| "style": "Error occurred during generation" | |
| } | |
| return None, error_info, f"โ Generation failed: {str(e)}", [], [], None, None, [] | |
| def _create_lightweight_animated_video(self, scene: Dict, character_images: Dict, background_images: Dict) -> str: | |
| """Create lightweight animated video with character/background compositing""" | |
| scene_num = scene['scene_number'] | |
| if scene_num not in background_images: | |
| print(f"โ No background image for scene {scene_num}") | |
| return None | |
| video_path = f"{self.output_dir}/video_animated_scene_{scene_num}.mp4" | |
| try: | |
| print(f"๐ฌ Creating lightweight animated video for scene {scene_num}...") | |
| # Load background image | |
| bg_path = background_images[scene_num] | |
| print(f"๐ Loading background from: {bg_path}") | |
| if not os.path.exists(bg_path): | |
| print(f"โ Background file not found: {bg_path}") | |
| return None | |
| bg_image = Image.open(bg_path).resize((1024, 768)) | |
| bg_array = np.array(bg_image) | |
| bg_array = cv2.cvtColor(bg_array, cv2.COLOR_RGB2BGR) | |
| # Try to load character images for this scene | |
| scene_characters = scene.get('characters_present', []) | |
| character_overlays = [] | |
| for char_name in scene_characters: | |
| for char_key, char_path in character_images.items(): | |
| if char_name.lower() in char_key.lower(): | |
| if os.path.exists(char_path): | |
| char_img = Image.open(char_path).convert("RGBA") | |
| # Resize character to reasonable size (25% of background) | |
| char_w, char_h = char_img.size | |
| new_h = int(768 * 0.25) # 25% of background height | |
| new_w = int(char_w * (new_h / char_h)) | |
| char_img = char_img.resize((new_w, new_h)) | |
| character_overlays.append({ | |
| 'image': np.array(char_img), | |
| 'name': char_name, | |
| 'original_pos': (100 + len(character_overlays) * 200, 768 - new_h - 50) # Bottom positioning | |
| }) | |
| print(f"โ Loaded character: {char_name}") | |
| break | |
| print(f"๐ Background size: {bg_array.shape}") | |
| print(f"๐ญ Characters loaded: {len(character_overlays)}") | |
| # Professional video settings | |
| fourcc = cv2.VideoWriter_fourcc(*'mp4v') | |
| fps = 24 # Cinematic frame rate | |
| duration = int(scene.get('duration', 35)) | |
| total_frames = duration * fps | |
| print(f"๐ฌ Video settings: {fps}fps, {duration}s duration, {total_frames} frames") | |
| out = cv2.VideoWriter(video_path, fourcc, fps, (1024, 768)) | |
| if not out.isOpened(): | |
| print(f"โ Failed to open video writer for {video_path}") | |
| return None | |
| # Advanced animation with character movement | |
| print(f"๐ฌ Generating {total_frames} animated frames...") | |
| for i in range(total_frames): | |
| if i % 100 == 0: # Progress update every 100 frames | |
| print(f" Frame {i}/{total_frames} ({i/total_frames*100:.1f}%)") | |
| frame = bg_array.copy() | |
| progress = i / total_frames | |
| # Apply cinematic background effects | |
| frame = self._apply_cinematic_effects(frame, scene, progress) | |
| # Animate characters if available | |
| for j, char_data in enumerate(character_overlays): | |
| char_img = char_data['image'] | |
| char_name = char_data['name'] | |
| base_x, base_y = char_data['original_pos'] | |
| # Different animation patterns based on scene mood | |
| mood = scene.get('mood', 'heartwarming') | |
| if mood == 'exciting': | |
| # Bouncing animation | |
| offset_y = int(np.sin(progress * 8 * np.pi + j * np.pi/2) * 20) | |
| offset_x = int(np.sin(progress * 4 * np.pi + j * np.pi/3) * 15) | |
| elif mood == 'peaceful': | |
| # Gentle swaying | |
| offset_y = int(np.sin(progress * 2 * np.pi + j * np.pi/2) * 8) | |
| offset_x = int(np.sin(progress * 1.5 * np.pi + j * np.pi/3) * 12) | |
| elif mood == 'mysterious': | |
| # Subtle floating | |
| offset_y = int(np.sin(progress * 3 * np.pi + j * np.pi/2) * 15) | |
| offset_x = int(np.cos(progress * 2 * np.pi + j * np.pi/4) * 10) | |
| else: | |
| # Default: slight breathing animation | |
| scale_factor = 1.0 + np.sin(progress * 4 * np.pi + j * np.pi/2) * 0.02 | |
| offset_y = int(np.sin(progress * 3 * np.pi + j * np.pi/2) * 5) | |
| offset_x = 0 | |
| # Calculate final position | |
| final_x = base_x + offset_x | |
| final_y = base_y + offset_y | |
| # Overlay character on frame | |
| if char_img.shape[2] == 4: # Has alpha channel | |
| frame = self._overlay_character(frame, char_img, final_x, final_y) | |
| else: | |
| # Simple overlay without alpha | |
| char_rgb = cv2.cvtColor(char_img[:,:,:3], cv2.COLOR_RGB2BGR) | |
| h, w = char_rgb.shape[:2] | |
| if (final_y >= 0 and final_y + h < 768 and | |
| final_x >= 0 and final_x + w < 1024): | |
| frame[final_y:final_y+h, final_x:final_x+w] = char_rgb | |
| out.write(frame) | |
| print(f"โ All {total_frames} animated frames generated") | |
| out.release() | |
| if os.path.exists(video_path): | |
| file_size = os.path.getsize(video_path) | |
| print(f"โ Lightweight animated video created: {video_path} ({file_size / (1024*1024):.1f} MB)") | |
| return video_path | |
| else: | |
| print(f"โ Video file not created: {video_path}") | |
| return None | |
| except Exception as e: | |
| print(f"โ Lightweight animated video creation failed for scene {scene_num}: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return None | |
| def _overlay_character(self, background, character_rgba, x, y): | |
| """Overlay character with alpha transparency on background""" | |
| try: | |
| char_h, char_w = character_rgba.shape[:2] | |
| bg_h, bg_w = background.shape[:2] | |
| # Ensure the character fits within background bounds | |
| if x < 0 or y < 0 or x + char_w > bg_w or y + char_h > bg_h: | |
| return background | |
| # Extract RGB and alpha channels | |
| char_rgb = character_rgba[:, :, :3] | |
| char_alpha = character_rgba[:, :, 3] / 255.0 | |
| # Convert character to BGR for OpenCV | |
| char_bgr = cv2.cvtColor(char_rgb, cv2.COLOR_RGB2BGR) | |
| # Get the region of interest from background | |
| roi = background[y:y+char_h, x:x+char_w] | |
| # Blend character with background using alpha | |
| for c in range(3): | |
| roi[:, :, c] = (char_alpha * char_bgr[:, :, c] + | |
| (1 - char_alpha) * roi[:, :, c]) | |
| background[y:y+char_h, x:x+char_w] = roi | |
| return background | |
| except Exception as e: | |
| print(f"โ ๏ธ Character overlay failed: {e}") | |
| return background | |
| def save_script_to_file(self, script_data: Dict[str, Any], original_script: str) -> str: | |
| """Save script data to a JSON file in tmp folder""" | |
| try: | |
| # Create a comprehensive script file with all data | |
| script_file_data = { | |
| "original_script": original_script, | |
| "generated_script": script_data, | |
| "timestamp": str(datetime.datetime.now()), | |
| "version": "1.0" | |
| } | |
| # Save to tmp folder | |
| script_path = f"{self.output_dir}/cartoon_script_{int(time.time())}.json" | |
| with open(script_path, 'w', encoding='utf-8') as f: | |
| json.dump(script_file_data, f, indent=2, ensure_ascii=False) | |
| if os.path.exists(script_path): | |
| file_size = os.path.getsize(script_path) / 1024 # KB | |
| print(f"๐ Script saved: {script_path} ({file_size:.1f} KB)") | |
| return script_path | |
| else: | |
| print(f"โ Failed to save script: {script_path}") | |
| return None | |
| except Exception as e: | |
| print(f"โ Error saving script: {e}") | |
| return None | |
| def create_download_links(self, files_dict: Dict[str, str]) -> List[Dict[str, str]]: | |
| """Create download links for files""" | |
| download_links = [] | |
| for file_type, file_path in files_dict.items(): | |
| if os.path.exists(file_path): | |
| file_name = os.path.basename(file_path) | |
| file_size = os.path.getsize(file_path) / (1024*1024) # MB | |
| download_links.append({ | |
| "name": file_name, | |
| "path": file_path, | |
| "size": f"{file_size:.1f} MB", | |
| "type": file_type | |
| }) | |
| return download_links | |
| def get_download_files(self, files_dict: Dict[str, str]) -> tuple: | |
| """Get file objects for Gradio download components""" | |
| script_file = None | |
| video_file = None | |
| for file_type, file_path in files_dict.items(): | |
| if os.path.exists(file_path): | |
| if file_type == "script": | |
| script_file = file_path | |
| elif file_type == "video": | |
| video_file = file_path | |
| return script_file, video_file | |
| # Initialize professional generator | |
| generator = ProfessionalCartoonFilmGenerator() | |
| def create_professional_cartoon_film(script): | |
| """Gradio interface function for professional generation (ZeroGPU compatible)""" | |
| if not script.strip(): | |
| empty_response = { | |
| "error": True, | |
| "message": "No script provided", | |
| "characters": [], | |
| "scenes": [], | |
| "style": "Please enter a script" | |
| } | |
| return None, empty_response, "โ Please enter a script", [], [], None, None, [] | |
| # Check if another generation is in progress | |
| if not generation_lock.acquire(blocking=False): | |
| busy_response = { | |
| "error": True, | |
| "message": "Generation already in progress", | |
| "characters": [], | |
| "scenes": [], | |
| "style": "Please wait for current generation to complete" | |
| } | |
| return None, busy_response, "โณ Generation already in progress - please wait", [], [], None, None, [] | |
| try: | |
| return generator.generate_professional_cartoon_film(script) | |
| finally: | |
| generation_lock.release() | |
| # Professional Gradio Interface | |
| with gr.Blocks( | |
| title="๐ฌ Professional AI Cartoon Film Generator", | |
| theme=gr.themes.Soft(), | |
| css=""" | |
| .gradio-container { | |
| max-width: 1400px !important; | |
| } | |
| .hero-section { | |
| text-align: center; | |
| padding: 2rem; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border-radius: 10px; | |
| margin-bottom: 2rem; | |
| } | |
| """ | |
| ) as demo: | |
| with gr.Column(elem_classes="hero-section"): | |
| gr.Markdown(""" | |
| # ๐ฌ Professional AI Cartoon Film Generator | |
| ## **FLUX + LoRA + Open-Sora 2.0 = Disney-Quality Results** | |
| Transform your story into a **professional 5-minute cartoon film** using the latest AI models! | |
| """) | |
| gr.Markdown(""" | |
| ## ๐ **Revolutionary Upgrade - Professional Quality** | |
| **๐ฅ Latest AI Models:** | |
| - **FLUX + LoRA** - Disney-Pixar quality character generation | |
| - **Open-Sora 2.0** - State-of-the-art video generation (11B parameters) | |
| - **Professional Script Generation** - Cinematic story structure | |
| - **Cinematic Animation** - Professional camera movements and effects | |
| **โจ Features:** | |
| - **8 professionally structured scenes** with cinematic pacing | |
| - **High-resolution characters** (1024x1024) with consistent design | |
| - **Cinematic backgrounds** with professional lighting | |
| - **Advanced animation effects** based on scene mood | |
| - **4K video output** with 24fps cinematic quality | |
| - **๐ Script downloads** - Full JSON with story analysis | |
| - **๐ File management** - All files saved in /tmp with download links | |
| **๐ฏ Perfect for:** | |
| - Content creators seeking professional results | |
| - Filmmakers prototyping animated concepts | |
| - Educators creating engaging educational content | |
| - Anyone wanting Disney-quality cartoon films | |
| --- | |
| **โ ๏ธ Current Status:** | |
| - โ **Storage System:** Fixed for Hugging Face Spaces (/tmp folder) | |
| - โ **Script Downloads:** JSON files with complete story analysis | |
| - โ **File Downloads:** Direct download buttons for all generated content | |
| - โ ๏ธ **FLUX Models:** Require authentication token (using Stable Diffusion fallback) | |
| - โ ๏ธ **Open-Sora:** Using static video fallback for stability | |
| **๐ก To unlock full FLUX quality:** | |
| 1. Get token from [Hugging Face Settings](https://huggingface.co/settings/tokens) | |
| 2. Accept [FLUX License](https://huggingface.co/black-forest-labs/FLUX.1-dev) | |
| 3. Add token as Space secret: `HF_TOKEN` | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| script_input = gr.Textbox( | |
| label="๐ Your Story Script", | |
| placeholder="""Enter your story idea! Be descriptive for best results: | |
| Examples: | |
| โข A brave young girl discovers a magical forest where talking animals need her help to save their home from an evil wizard who has stolen all the colors from their world. | |
| โข A curious robot living in a futuristic city learns about human emotions when it befriends a lonely child and together they solve the mystery of the disappearing laughter. | |
| โข Two unlikely friends - a shy dragon and a brave knight - must work together to protect their kingdom from a misunderstood monster while learning that appearances can be deceiving. | |
| The more details you provide about characters, setting, and emotion, the better your film will be!""", | |
| lines=8, | |
| max_lines=12 | |
| ) | |
| generate_btn = gr.Button( | |
| "๐ฌ Generate Professional Cartoon Film", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| gr.Markdown(""" | |
| **โฑ๏ธ Processing Time:** 8-12 minutes | |
| **๐ฅ Output:** 5-minute professional MP4 film | |
| **๐ฑ Quality:** Disney-Pixar level animation | |
| **๐๏ธ Resolution:** 1024x768 (4:3 cinematic) | |
| """) | |
| with gr.Column(scale=1): | |
| gr.Markdown(""" | |
| **โ ๏ธ Important Notes:** | |
| - Only **ONE generation at a time** - multiple clicks will be queued | |
| - **Processing takes 8-12 minutes** - please be patient | |
| - **Files saved in /tmp folder** with download links below | |
| - **Script saved as JSON** with full story analysis | |
| - **Images and videos** available for download | |
| """) | |
| video_output = gr.Video( | |
| label="๐ฌ Professional Cartoon Film", | |
| height=500 | |
| ) | |
| # Add file galleries for generated content | |
| with gr.Accordion("๐ Generated Files (Click to Download)", open=False): | |
| character_gallery = gr.Gallery( | |
| label="๐ญ Character Images", | |
| columns=2, | |
| height=200, | |
| allow_preview=True | |
| ) | |
| background_gallery = gr.Gallery( | |
| label="๐๏ธ Background Images", | |
| columns=2, | |
| height=200, | |
| allow_preview=True | |
| ) | |
| # Add download buttons for scripts and other files | |
| script_download = gr.File( | |
| label="๐ Download Script (JSON)", | |
| file_types=[".json"], | |
| visible=True | |
| ) | |
| video_download = gr.File( | |
| label="๐ฌ Download Video (MP4)", | |
| file_types=[".mp4"], | |
| visible=True | |
| ) | |
| # Download links display | |
| download_links_output = gr.JSON( | |
| label="๐ฅ Download Links", | |
| visible=True | |
| ) | |
| status_output = gr.Textbox( | |
| label="๐ Generation Status", | |
| lines=3 | |
| ) | |
| script_details = gr.JSON( | |
| label="๐ Professional Script Analysis", | |
| visible=True | |
| ) | |
| # Event handlers | |
| generate_btn.click( | |
| fn=create_professional_cartoon_film, | |
| inputs=[script_input], | |
| outputs=[video_output, script_details, status_output, character_gallery, background_gallery, script_download, video_download, download_links_output], | |
| show_progress=True | |
| ) | |
| # Professional example scripts | |
| gr.Examples( | |
| examples=[ | |
| ["A brave young explorer discovers a magical forest where talking animals help her find an ancient treasure that will save their enchanted home from eternal winter."], | |
| ["Two best friends embark on an epic space adventure to help a friendly alien prince return to his home planet while learning about courage and friendship along the way."], | |
| ["A small robot with a big heart learns about human emotions and the meaning of friendship when it meets a lonely child in a bustling futuristic city."], | |
| ["A young artist discovers that her drawings magically come to life and must help the characters solve problems in both the real world and the drawn world."], | |
| ["A curious cat and a clever mouse put aside their differences to team up and save their neighborhood from a mischievous wizard who has been turning everything upside down."], | |
| ["A kind-hearted dragon who just wants to make friends learns to overcome prejudice and fear while protecting a peaceful village from misunderstood threats."], | |
| ["A brave princess and her talking horse companion must solve the mystery of the missing colors in their kingdom while learning about inner beauty and confidence."], | |
| ["Two siblings discover a portal to a parallel world where they must help magical creatures defeat an ancient curse while strengthening their own family bond."] | |
| ], | |
| inputs=[script_input], | |
| label="๐ก Try these professional example stories:" | |
| ) | |
| gr.Markdown(""" | |
| --- | |
| ## ๐ ๏ธ **Professional Technology Stack** | |
| **๐จ Image Generation:** | |
| - **FLUX.1-dev** - State-of-the-art diffusion model | |
| - **Anime/Cartoon LoRA** - Specialized character training | |
| - **Professional prompting** - Disney-quality character sheets | |
| **๐ฌ Video Generation:** | |
| - **Open-Sora 2.0** - 11B parameter video model | |
| - **Cinematic camera movements** - Professional animation effects | |
| - **24fps output** - Industry-standard frame rate | |
| **๐ Script Enhancement:** | |
| - **Advanced story analysis** - Character, setting, theme detection | |
| - **Cinematic structure** - Professional 8-scene format | |
| - **Character development** - Detailed personality profiles | |
| **๐ฏ Quality Features:** | |
| - **Consistent character design** - Using LoRA fine-tuning | |
| - **Professional color palettes** - Mood-appropriate schemes | |
| - **Cinematic composition** - Shot types and camera angles | |
| - **High-resolution output** - 4K-ready video files | |
| ## ๐ญ **Character & Scene Quality** | |
| **Characters:** | |
| - Disney-Pixar quality design | |
| - Consistent appearance across scenes | |
| - Expressive facial features | |
| - Professional character sheets | |
| **Backgrounds:** | |
| - Cinematic lighting and composition | |
| - Detailed environment art | |
| - Mood-appropriate color schemes | |
| - Professional background painting quality | |
| **Animation:** | |
| - Smooth camera movements | |
| - Scene-appropriate effects | |
| - Professional timing and pacing | |
| - Cinematic transitions | |
| **๐ Completely free and open source!** Using only the latest and best AI models. | |
| """) | |
| if __name__ == "__main__": | |
| demo.queue(max_size=3).launch() | |