import os import torch import numpy as np from PIL import Image from typing import Tuple, Optional from VideoEngine_optimized import VideoEngine from TextProcessor import TextProcessor try: import spaces HAS_SPACES = True except ImportError: HAS_SPACES = False class spaces: @staticmethod def GPU(duration=120): def decorator(func): return func return decorator class FlowFacade: def __init__(self): self.is_spaces = os.environ.get('SPACE_ID') is not None self.video_engine = VideoEngine() self.text_processor = TextProcessor(resource_manager=None) print("✓ DeltaFlow initialized") @spaces.GPU(duration=300) def generate_video_from_image(self, image: Image.Image, user_instruction: str, duration_seconds: float = 3.0, num_inference_steps: int = 4, guidance_scale: float = 1.0, guidance_scale_2: float = 1.0, seed: int = 42, randomize_seed: bool = False, enable_prompt_expansion: bool = False, progress=None) -> Tuple[str, str, int]: if image is None: raise ValueError("No image provided") if not user_instruction or user_instruction.strip() == "": raise ValueError("Please provide a motion instruction") try: if randomize_seed: seed = np.random.randint(0, 2147483647) if enable_prompt_expansion: if progress: progress(0.1, desc="AI expanding your prompt...") final_prompt = self.text_processor.process(user_instruction, auto_unload=True) else: final_prompt = user_instruction if progress: progress(0.2, desc="Preparing GPU memory...") if not self.video_engine.is_loaded: import gc gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() torch.cuda.ipc_collect() if progress: progress(0.25, desc="Loading video generation model...") self.video_engine.load_model() gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() if progress: progress(0.3, desc=f"Generating video ({num_inference_steps} steps)...") video_path = self.video_engine.generate_video( image=image, prompt=final_prompt, duration_seconds=duration_seconds, num_inference_steps=num_inference_steps, guidance_scale=guidance_scale, guidance_scale_2=guidance_scale_2, seed=seed ) if progress: progress(1.0, desc="Complete!") return video_path, final_prompt, seed except Exception as e: import traceback print(f"\n✗ Generation error: {type(e).__name__}: {str(e)}") if os.environ.get('DEBUG'): print(traceback.format_exc()) raise RuntimeError(f"Generation failed: {type(e).__name__}: {str(e)}") def cleanup(self) -> None: try: if hasattr(self.text_processor, 'is_loaded') and self.text_processor.is_loaded: self.text_processor.unload_model() torch.cuda.empty_cache() except Exception as e: if os.environ.get('DEBUG'): print(f"⚠ Cleanup warning: {str(e)}") def get_system_info(self) -> dict: quantization_type = "None" if torch.cuda.is_available(): cuda_cap = torch.cuda.get_device_capability() fp8_supported = cuda_cap[0] > 8 or (cuda_cap[0] == 8 and cuda_cap[1] >= 9) quantization_type = "FP8" if fp8_supported else "INT8" return { "device": self.video_engine.device, "video_model": VideoEngine.MODEL_ID, "text_model": TextProcessor.MODEL_ID, "lightning_lora": "Enabled", "quantization": quantization_type, "optimizations": [ "Lightning LoRA (4-8 steps)", f"{quantization_type} Quantization" ] } def validate_image(self, image: Image.Image) -> bool: if image is None: return False min_dim, max_dim = 256, 4096 if image.width < min_dim or image.height < min_dim: print(f"⚠ Image too small: {image.width}x{image.height}") return False if image.width > max_dim or image.height > max_dim: print(f"⚠ Image too large: {image.width}x{image.height}") return False return True def __del__(self): try: self.cleanup() except: pass