diff --git a/README.md b/README.md index 3e3f682..aff808d 100644 --- a/README.md +++ b/README.md @@ -74,15 +74,15 @@ Video models produce many frames that can easily exceed 24GB VRAM. - **🎞️ Latent Batch To List**: Splits video latents frame-by-frame for VRAM-safe processing. - **🎞️ Latent List To Batch**: Merges individual frames back into a video latent batch. - **⏳ Progress Bar**: Generates a visual progress indicator. -- **📤 Session Image Sender** & **📥 Session Image Receiver**: A powerful Sender/Receiver pattern designed to pass the *last frame* of an image batch into the *first frame* of the next Auto Queue cycle. This avoids ComfyUI's cyclic dependency errors, allowing for infinite, continuous video generation loops. +- **📤 Session Image Sender** & **📥 Session Image Receiver**: A powerful Sender/Receiver pattern designed to pass the *last frame* of an image batch into the *first frame* of the next Auto Queue cycle. This avoids ComfyUI's cyclic dependency errors, allowing for infinite, continuous video generation loops. The Receiver now accepts the `current_loop_index` and automatically clears its session memory when the loop resets to `0`. ### 🎞️ Video Category (`🔁 Sequential Batcher/Video`) - **🛡️ Wan Frame Validator**: Ensures video frame counts comply with Wan's strict 4k+1 architecture to prevent skipped or duplicated keyframes. - **🎞️ FFmpeg Video Stitcher**: Final node in a video loop. Waits for the entire sequential batch to finish and stitches the video chunks together using FFmpeg without re-encoding. - *Input*: `video_paths` (List of VHS_FILENAMES), `output_filename` (String). - *Output*: `final_video_path` (Path to the stitched video). -- **🎞️ Incremental Auto-Stitcher**: Incremental stitching node designed specifically for Auto Queue cycles. It runs immediately after each chunk is saved and extracts the `.mp4` absolute paths strictly from the `VHS_FILENAMES` output JSON. It maintains an active session memory (which clears on ComfyUI restart) to stitch *only* the videos generated during the current session, guaranteeing immunity to old/garbage files in the output directory. - - *Input*: `trigger` (VHS_FILENAMES list/JSON), `output_filename` (String), `reset_list` (Boolean: toggle True once to manually flush the session memory and start a fresh video). +- **🎞️ Incremental Auto-Stitcher**: Incremental stitching node designed specifically for Auto Queue cycles. It runs immediately after each chunk is saved and extracts the `.mp4` absolute paths strictly from the `VHS_FILENAMES` output JSON. It maintains an active session memory to stitch *only* the videos generated during the current loop cycle, guaranteeing immunity to old/garbage files in the output directory. + - *Input*: `trigger` (VHS_FILENAMES list/JSON), `output_filename` (String), `current_loop_index` (Integer: connects to the loop index to automatically flush the session memory on the first cycle). - *Output*: `final_video_path` (Path to the incrementally stitched final video). --- diff --git a/README_ES.md b/README_ES.md index c5d72d9..ba88f10 100644 --- a/README_ES.md +++ b/README_ES.md @@ -74,15 +74,15 @@ Los modelos de vídeo generan muchos fotogramas que pueden superar fácilmente l - **🎞️ Latent Batch To List**: Divide latentes de vídeo fotograma a fotograma para un procesamiento seguro en VRAM. - **🎞️ Latent List To Batch**: Une fotogramas individuales de nuevo en un lote latente de vídeo. - **⏳ Progress Bar**: Genera un indicador visual de progreso. -- **📤 Session Image Sender** y **📥 Session Image Receiver**: Un patrón Emisor/Receptor diseñado para enviar el *último fotograma* de un lote como el *primer fotograma* del siguiente ciclo de Auto Queue. Esto evita los errores de dependencias cíclicas en ComfyUI y permite bucles de generación de vídeo continuos e infinitos. +- **📤 Session Image Sender** y **📥 Session Image Receiver**: Un patrón Emisor/Receptor diseñado para enviar el *último fotograma* de un lote como el *primer fotograma* del siguiente ciclo de Auto Queue. Esto evita los errores de dependencias cíclicas en ComfyUI y permite bucles de generación de vídeo continuos e infinitos. El Receiver ahora acepta el `current_loop_index` y vacía automáticamente su memoria de sesión cuando el bucle vuelve a `0`. ### 🎞️ Categoría Vídeo (`🔁 Sequential Batcher/Video`) - **🛡️ Wan Frame Validator**: Garantiza que el recuento de fotogramas de vídeo cumpla estrictamente con la arquitectura 4k+1 de Wan, evitando fotogramas clave omitidos o duplicados. - **🎞️ FFmpeg Video Stitcher**: Nodo final en un bucle de vídeo. Espera a que termine todo el lote secuencial y une los fragmentos de vídeo utilizando FFmpeg sin recodificar. - *Entrada*: `video_paths` (Lista de VHS_FILENAMES), `output_filename` (Cadena de texto). - *Salida*: `final_video_path` (Ruta del vídeo ensamblado). -- **🎞️ Incremental Auto-Stitcher**: Nodo de ensamblaje incremental diseñado específicamente para ciclos de Auto Queue. Se ejecuta inmediatamente después de que cada fragmento es guardado y extrae de manera estricta las rutas `.mp4` del JSON de salida que genera `VHS_FILENAMES`. Mantiene una lista en la memoria de la sesión activa (que se vacía automáticamente al reiniciar ComfyUI) para ensamblar *únicamente* los vídeos generados durante la sesión actual, garantizando inmunidad a archivos viejos o "basura" en el directorio de salida. - - *Entrada*: `trigger` (JSON/lista de VHS_FILENAMES), `output_filename` (Cadena de texto), `reset_list` (Booleano: actívalo a True en un ciclo para vaciar manualmente la memoria de la sesión y empezar un vídeo nuevo). +- **🎞️ Incremental Auto-Stitcher**: Nodo de ensamblaje incremental diseñado específicamente para ciclos de Auto Queue. Se ejecuta inmediatamente después de que cada fragmento es guardado y extrae de manera estricta las rutas `.mp4` del JSON de salida que genera `VHS_FILENAMES`. Mantiene una lista en la memoria de la sesión activa para ensamblar *únicamente* los vídeos generados durante el ciclo de bucle actual, garantizando inmunidad a archivos viejos o "basura" en el directorio de salida. + - *Entrada*: `trigger` (JSON/lista de VHS_FILENAMES), `output_filename` (Cadena de texto), `current_loop_index` (Entero: conéctalo al índice del bucle para vaciar automáticamente la memoria de la sesión en el primer ciclo). - *Salida*: `final_video_path` (Ruta del vídeo ensamblado incremental). --- diff --git a/image.py b/image.py index 4fca734..268d069 100644 --- a/image.py +++ b/image.py @@ -279,19 +279,19 @@ def tensor_to_temp_image(tensor_image, prefix="session_img"): @register_node class SessionImageReceiver: - """Proporciona y MUESTRA la imagen inicial o la última generada del ciclo anterior.""" + """Proporciona la imagen inicial o la última generada, detectando el inicio automáticamente.""" @classmethod def INPUT_TYPES(s): return { "required": { "initial_image": ("IMAGE",), - "reset_session": ("BOOLEAN", {"default": False}), + "current_loop_index": ("INT", {"default": 0, "min": 0, "max": 10000, "step": 1}), }, } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("current_image",) - OUTPUT_NODE = True # Necesario para que la UI se actualice + OUTPUT_NODE = True FUNCTION = "get_image" CATEGORY = "🔁 Sequential Batcher/Image" @@ -299,19 +299,22 @@ class SessionImageReceiver: def IS_CHANGED(cls, **kwargs): return float("NaN") - def get_image(self, initial_image, reset_session): + def get_image(self, initial_image, current_loop_index): global global_session_image - if reset_session or global_session_image is None: + # Extracción segura por si en el futuro se usa INPUT_IS_LIST + loop_idx = current_loop_index[0] if isinstance(current_loop_index, list) else current_loop_index + + # LÓGICA NATIVA: Si es el primer ciclo (index 0), reiniciamos + is_first_cycle = (loop_idx == 0) + + if is_first_cycle or global_session_image is None: global_session_image = initial_image - print("[Sequential Batcher] Receiver: Iniciando sesión con la imagen original.") + print(f"[Sequential Batcher] Receiver: Ciclo {loop_idx}. Iniciando sesión con la imagen original.") else: - print("[Sequential Batcher] Receiver: Usando el último fotograma del ciclo anterior.") + print(f"[Sequential Batcher] Receiver: Ciclo {loop_idx}. Usando el último fotograma del ciclo anterior.") - # Generar vista previa para la UI ui_image = tensor_to_temp_image(global_session_image, "receiver") - - # Devolver el diccionario con la actualización de la UI y el resultado real return {"ui": {"images": [ui_image]}, "result": (global_session_image, )} diff --git a/video.py b/video.py index 2ac4da6..89976df 100644 --- a/video.py +++ b/video.py @@ -140,7 +140,7 @@ class IncrementalVideoStitcher: # Recibe la lista/JSON de VHS_VideoCombine "trigger": ("VHS_FILENAMES", ), "output_filename": ("STRING", {"default": "Pelicula_Final_Sesion.mp4"}), - "reset_list": ("BOOLEAN", {"default": False}), + "current_loop_index": ("INT", {"default": 0, "min": 0, "max": 10000, "step": 1}), }, } @@ -151,23 +151,26 @@ class IncrementalVideoStitcher: FUNCTION = "stitch_incremental" CATEGORY = "🔁 Sequential Batcher/Video" - def stitch_incremental(self, trigger, output_filename, reset_list): + def stitch_incremental(self, trigger, output_filename, current_loop_index): global session_video_list - # Extraer variables si llegan en lista por INPUT_IS_LIST=True - do_reset = reset_list[0] if isinstance(reset_list, list) else reset_list - - if do_reset: - session_video_list.clear() - print("[Sequential Batcher] Memoria de sesión vaciada (reset_list=True).") - if not shutil.which("ffmpeg"): - print("[Sequential Batcher] Error: FFmpeg no está instalado en el sistema.") + print("[Sequential Batcher] Error de FFmpeg: No está instalado.") return ("", ) out_dir = folder_paths.get_output_directory() out_name = output_filename[0] if isinstance(output_filename, list) else output_filename + # Extracción segura obligatoria por INPUT_IS_LIST = True + loop_idx = current_loop_index[0] if isinstance(current_loop_index, list) else current_loop_index + + # LÓGICA NATIVA: Si es el primer ciclo, vaciamos la lista + is_first_cycle = (loop_idx == 0) + + if is_first_cycle: + print(f"[Sequential Batcher] Auto-Stitcher: Ciclo {loop_idx}. Vaciando memoria de la sesión.") + session_video_list.clear() + # 1. Extracción recursiva: buscar cualquier archivo .mp4 dentro del JSON/lista recibido current_mp4s = [] def extract_mp4s(data):