import os import glob from typing import Any, Dict, List import gradio as gr import spaces from core.pipelines.workflow_executor import WorkflowExecutor from core.model_manager import model_manager def _extract_required_models(workflow: Dict[str, Any]) -> List[str]: required_models = [] MODEL_KEYS = {'unet_name', 'clip_name', 'vae_name', 'model_name', 'ckpt_name', 'clip_name1', 'clip_name2'} MODEL_EXTENSIONS = ('.safetensors', '.ckpt', '.pt', '.bin', '.pth') for node_id, node_data in workflow.items(): if node_data.get('class_type') == 'LoraLoader': continue inputs = node_data.get('inputs', {}) for k, v in inputs.items(): if isinstance(v, str) and v and k != 'lora_name': if k in MODEL_KEYS or any(v.lower().endswith(ext) for ext in MODEL_EXTENSIONS): if v not in required_models and not v.startswith('[') and not v.endswith(']'): required_models.append(v) return required_models def _extract_video_paths(out: Any) -> List[str]: video_files = [] def process_item(item): if item is None: return if isinstance(item, (list, tuple)): for sub in item: process_item(sub) return if isinstance(item, str) and os.path.exists(item): if item.lower().endswith(('.mp4', '.webm', '.gif', '.mov', '.mkv')): video_files.append(item) return for attr in ['path', 'saved_path', 'filepath', 'filename', 'full_path']: val = getattr(item, attr, None) if isinstance(val, str) and os.path.exists(val): video_files.append(val) return elif isinstance(val, str): try: import folder_paths possible = os.path.join(folder_paths.get_output_directory(), val) if os.path.exists(possible): video_files.append(possible) return except Exception: pass if hasattr(item, '__dict__'): for k, v in item.__dict__.items(): if isinstance(v, str) and (v.endswith('.mp4') or v.endswith('.webm') or v.endswith('.gif')): if os.path.exists(v): video_files.append(v) return try: import folder_paths possible = os.path.join(folder_paths.get_output_directory(), v) if os.path.exists(possible): video_files.append(possible) return except Exception: pass if isinstance(item, dict): ui_info = item.get("ui", {}) for key in ["images", "videos", "video"]: for sub in ui_info.get(key, []): if isinstance(sub, dict) and "filename" in sub: fn = sub["filename"] subfolder = sub.get("subfolder", "") try: import folder_paths full = os.path.join(folder_paths.get_output_directory(), subfolder, fn) if subfolder else os.path.join(folder_paths.get_output_directory(), fn) if os.path.exists(full): video_files.append(full) return except Exception: pass process_item(out) if not video_files: try: import folder_paths out_dir = folder_paths.get_output_directory() mp4_files = glob.glob(os.path.join(out_dir, "**", "*.mp4"), recursive=True) if mp4_files: mp4_files.sort(key=os.path.getmtime, reverse=True) video_files.append(mp4_files[0]) except Exception: pass return video_files def _execute_ltx_workflow_gpu(workflow: Dict[str, Any]): initial_objects = {} return WorkflowExecutor.execute_workflow(workflow, initial_objects=initial_objects) def generate_ltx_video_wrapper(process_inputs_func, ui_inputs: dict, progress=gr.Progress(track_tqdm=True)): progress(0.1, desc="Assembling LTX-2.5 Video Workflow...") batch_count = int(ui_inputs.get('batch_count', 1)) all_video_files = [] for b_idx in range(batch_count): batch_msg = f" (Batch {b_idx + 1}/{batch_count})" if batch_count > 1 else "" current_ui_inputs = ui_inputs.copy() orig_seed = int(current_ui_inputs.get('seed', -1)) if orig_seed != -1 and b_idx > 0: current_ui_inputs['seed'] = orig_seed + b_idx try: workflow, extra_data = process_inputs_func(current_ui_inputs, progress=progress) except TypeError: workflow, extra_data = process_inputs_func(current_ui_inputs) required_models = _extract_required_models(workflow) if required_models: progress(0.2, desc=f"Ensuring models are downloaded: {len(required_models)} file(s)...") model_manager.ensure_models_downloaded(required_models, progress=progress) zero_gpu_duration = current_ui_inputs.get('zero_gpu_duration', 60) try: duration = int(zero_gpu_duration) except (ValueError, TypeError): duration = 60 if duration <= 0: duration = 60 progress(0.4, desc=f"Executing LTX-2.5 Workflow on GPU{batch_msg} (ZeroGPU {duration}s)...") gpu_runner = spaces.GPU(duration=duration)(_execute_ltx_workflow_gpu) raw_output = gpu_runner(workflow) batch_videos = _extract_video_paths(raw_output) if batch_videos: all_video_files.extend(batch_videos) print(f"✅ LTX-2.5 Video generated{batch_msg}") progress(1.0, desc="LTX-2.5 Video Generation Complete!") if not all_video_files: return None return all_video_files[0] if batch_count == 1 else all_video_files # Alias for backwards compatibility generate_h3_video_wrapper = generate_ltx_video_wrapper