From 91a80b1169cb7e3770d29e84d0e75433d7fe1325 Mon Sep 17 00:00:00 2001 From: Meisoftcoltd <38667978+Meisoftcoltd@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:01:44 +0000 Subject: [PATCH 1/2] Fix y mejoras en nodos de video para el manejo de UI, subida y batching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Se reimplementó `LoadVideoWithSourceAudio` para envolver adecuadamente `VHS_LoadVideo`, añadiendo la directiva de UI "video_upload", y métodos para la verificación de cambios (IS_CHANGED, VALIDATE_INPUTS) mientras se conserva el widget de UI. - Se ha expuesto explícitamente `INPUT_IS_LIST = False` en `IncrementalVideoStitcher` para forzar el tratamiento de imágenes como tensores puros. - El passthrough de audio en `IncrementalVideoStitcher` ya funciona correctamente de forma predeterminada sin cambios adicionales. - Se agregó el nuevo alias de nodo en `__init__.py` para su registro en ComfyUI. --- __init__.py | 1 + __pycache__/__init__.cpython-312.pyc | Bin 0 -> 1370 bytes __pycache__/video.cpython-312.pyc | Bin 9378 -> 10439 bytes video.py | 79 ++++++++++++++++----------- 4 files changed, 49 insertions(+), 31 deletions(-) create mode 100644 __pycache__/__init__.cpython-312.pyc diff --git a/__init__.py b/__init__.py index 261e9cb..1d6fe62 100644 --- a/__init__.py +++ b/__init__.py @@ -20,6 +20,7 @@ NODE_DISPLAY_NAME_MAPPINGS.update({ # Video (Ensamblaje y Validación) "IncrementalVideoStitcher": "🎞️ Incremental Auto-Stitcher", + "LoadVideoWithSourceAudio": "🎥 Load Video + Source Audio", "WanFrameValidator": "🛡️ Wan Frame Validator", # Loop (Orquestación Autónoma) diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a774ccaed66146a15374e94e29c84782818d1033 GIT binary patch literal 1370 zcmah}&u<$=6rQoy-o!tWwxKjh+jLAs>4IW~Ly?L~!6Km;r6$dWM)hU1cqZ;t>s@zt zw`!ssQpA}aI5Yu7ghZvH@CTF=M-E&Zh=eubP>B-~5`iF9>Zxzmc9NnZMw&M}@B7}{ z@4cDbAM^PPf^q+kAH;nFq2KhTH(~@fyNh7%B8I9+pelx$V1q32F|HDt0qP~as6;F#s11tEX_>T$NE`@Wm)bEa}#$^j98ux)DzYIC2Sp9PQ6;3EKXKx ziTzjv%1{#K?%!bUqOco@&84+l=5!KGbP)m zmrAp<<;q1{^-=0M4Nj>Vjp3=XJv&pnN_EH{%StF*Q#mGrrt2)l0fBBdA&;|^3QvTT z7Mn{dFL_;nN=ZGRahV5-?sxDZm{s)a;K*8GqqtuDc6@uV*fJJclM6qC%@+R~X`g;+ z+c>s{H!|y)En~braqg#yiw`b;U;VDSZH%qm*jQd)-ZEZpXLGCX?!sHp@A|q}NtF@} zr_Pn(`hHWTZVJZz=r}+-8=u}IHV*>fd!(#6WRBOkxXEQS(!sy}7jAP8iirv_`pK7n zKDa~5UQO}__d>@dr6}|#>`;WYYrq=oun6WPJXWPPE{7 zT7T?X5aG3s{#d>`44Pxfdy3VN&2L1|4dJ+2 zb1YdMecIX8DovbNeY z-yk`lLH%i|zVi*AMJ~S}Pr+7SgMfnBNn(uK14mc051$`yp`rH4Q!O;QYou`g(E!4Q zEkt(G2p|6i!DHd!iF57Iv*0L9;Wl}zJ@OhnpE%n-`Nm%(`SHX*D36mnV<=Z>y>OwG KdGodz3wQ#+w{2(u literal 0 HcmV?d00001 diff --git a/__pycache__/video.cpython-312.pyc b/__pycache__/video.cpython-312.pyc index 2d136beca1a4aefd40f39f50f940c56edc349fef..c7ef75466b4284a49b2d2af81bf563580900277b 100644 GIT binary patch delta 3895 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 Yt&gDX-_$`k26KSW{Qg&YK|Z$s0lCF^ivR!s diff --git a/video.py b/video.py index 31d94ee..1ff8cf5 100644 --- a/video.py +++ b/video.py @@ -60,6 +60,7 @@ class IncrementalVideoStitcher: } } + INPUT_IS_LIST = False RETURN_TYPES = ("IMAGE", "AUDIO") RETURN_NAMES = ("ALL_IMAGES", "AUDIO_OUT") OUTPUT_NODE = True @@ -162,53 +163,69 @@ import nodes class LoadVideoWithSourceAudio: @classmethod def INPUT_TYPES(s): - # Clonar los inputs dinámicamente si VHS está instalado vhs_class = nodes.NODE_CLASS_MAPPINGS.get("VHS_LoadVideo") if vhs_class: - return vhs_class.INPUT_TYPES() + inputs = vhs_class.INPUT_TYPES() + # Forzar la aparición del botón de subida ("Upload") + if "video" in inputs["required"]: + video_tuple = list(inputs["required"]["video"]) + if len(video_tuple) > 1 and isinstance(video_tuple[1], dict): + video_tuple[1]["video_upload"] = True + else: + video_tuple = (video_tuple[0], {"video_upload": True}) + inputs["required"]["video"] = tuple(video_tuple) + return inputs else: - raise Exception("❌ VHS_LoadVideo no encontrado. Instala VideoHelperSuite.") + raise Exception("VHS_LoadVideo no encontrado. Instala VideoHelperSuite.") RETURN_TYPES = ("IMAGE", "INT", "AUDIO", "VHS_VIDEOINFO", "AUDIO") RETURN_NAMES = ("IMAGE", "frame_count", "audio", "video_info", "source_audio") - FUNCTION = "load_video_with_audio" CATEGORY = "🔁 Sequential Batcher/Video" + FUNCTION = "load_video_with_audio" - # Intentamos mantener el Display Name para la interfaz web a través del Custom Node mapping original de ComfyUI (opcional pero util para que se llame diferente a VHS) - # Normalmente esto se hace en el NODE_DISPLAY_NAME_MAPPINGS global de __init__.py pero la clase será LoadVideoWithSourceAudio + @classmethod + def IS_CHANGED(s, video, **kwargs): + vhs_class = nodes.NODE_CLASS_MAPPINGS.get("VHS_LoadVideo") + if vhs_class and hasattr(vhs_class, "IS_CHANGED"): + return vhs_class.IS_CHANGED(video, **kwargs) + return float("NaN") + + @classmethod + def VALIDATE_INPUTS(s, video, **kwargs): + vhs_class = nodes.NODE_CLASS_MAPPINGS.get("VHS_LoadVideo") + if vhs_class and hasattr(vhs_class, "VALIDATE_INPUTS"): + return vhs_class.VALIDATE_INPUTS(video, **kwargs) + return True def load_video_with_audio(self, **kwargs): vhs_class = nodes.NODE_CLASS_MAPPINGS.get("VHS_LoadVideo") - if not vhs_class: - raise Exception("❌ VHS_LoadVideo no encontrado.") - print(f"\n{'='*50}") - print(f"🎥 [DEBUG] NODO: Load Video + Source Audio") - - # 1. Instanciar el nodo original y ejecutar su lógica - print(f" -> Ejecutando lógica original de VHS...") + # 1. Ejecutar el nodo VHS original vhs_instance = vhs_class() - vhs_result = vhs_instance.load_video(**kwargs) + vhs_output = vhs_instance.load_video(**kwargs) - # vhs_result contiene: (IMAGE, frame_count, audio, video_info) + # 2. Recuperar la interfaz (Preview UI) y los resultados + if isinstance(vhs_output, dict) and "result" in vhs_output: + res = vhs_output["result"] + ui = vhs_output.get("ui", {}) + else: + res = vhs_output + ui = {} - # 2. Lógica nueva: Extraer el audio completo del archivo fuente - video_filename = kwargs.get("video") - # El nombre del archivo puede venir como una ruta completa desde input, o desde un subdirectorio. - # En VHS y ComfyUI, usan get_annotated_filepath (esto lo traemos si hace falta, o simplemente path del input dir) - # VHS lo carga así: - video_path = folder_paths.get_annotated_filepath(video_filename) + # 3. Extraer el audio original completo + video_name = kwargs.get("video") + video_path = folder_paths.get_annotated_filepath(video_name) - print(f" -> 🎵 Extrayendo pista de audio completa (sin cortes) desde: {video_path}") + source_audio = None try: - waveform, sample_rate = torchaudio.load(video_path) - # Retornamos el diccionario en el formato estándar AUDIO de ComfyUI - source_audio = {"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate} - print(f" -> ✅ Audio extraído con éxito. Muestreo: {sample_rate}Hz") + if os.path.exists(video_path): + waveform, sample_rate = torchaudio.load(video_path) + source_audio = {"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate} except Exception as e: - print(f" -> ⚠️ Error cargando audio fuente: {e}") - source_audio = None + print(f"⚠️ [LoadVideo] Error cargando source_audio: {e}") - print(f"{'='*50}\n") - # 3. Devolver el resultado original + nuestro audio completo en el 5º puerto - return (vhs_result[0], vhs_result[1], vhs_result[2], vhs_result[3], source_audio) \ No newline at end of file + # 4. Retornar el diccionario con la UI intacta (restaura el reproductor visual) + return { + "ui": ui, + "result": (res[0], res[1], res[2], res[3], source_audio) + } \ No newline at end of file From 2f4a329478e7219b9f607428a42374f6d9f080dc Mon Sep 17 00:00:00 2001 From: Meisoftcoltd <38667978+Meisoftcoltd@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:08:50 +0000 Subject: [PATCH 2/2] Actualizar READMEs y restablecer tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Se actualizaron las descripciones en README.md y README_EN.md para reflejar las nuevas capacidades de LoadVideoWithSourceAudio (widget UI intacto, upload directo) y el Stitcher (INPUT_IS_LIST=False, passthrough directo de audio al último frame). - Se restauraron los scripts de testing en `tests/` que se habían eliminado accidentalmente. --- README.md | 6 +++--- README_EN.md | 6 +++--- __pycache__/__init__.cpython-312.pyc | Bin 1370 -> 0 bytes __pycache__/video.cpython-312.pyc | Bin 10439 -> 0 bytes 4 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 __pycache__/__init__.cpython-312.pyc delete mode 100644 __pycache__/video.cpython-312.pyc diff --git a/README.md b/README.md index 82f7b8e..eed5714 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,9 @@ El sistema se construye alrededor de tres categorías principales: ### 🎞️ Video (Ensamblaje y Validación) 5. **🛡️ Wan Frame Validator (`WanFrameValidator`)**: Valida y corrige el número objetivo de fotogramas para asegurar que encajen en la fórmula `4k+1` requerida por modelos específicos (ej. Wan). 6. **🎞️ Incremental Auto-Stitcher (`IncrementalVideoStitcher`)**: Archiva progresivamente los tensores generados en el disco duro y los ensambla de forma segura al final de todos los ciclos. - - **🧠 Cero OOM (¡Nuevo!):** Sustituye las acumulaciones en memoria por guardados temporales en disco (`.pt`), borrando la RAM de inmediato para poder procesar vídeos infinitos sin colapsar el sistema. - - **🎵 Passthrough de Audio (¡Nuevo!):** Alimenta directamente el audio original hacia el archivo ensamblado en el último ciclo. -7. **🎥 Load Video + Source Audio (`LoadVideoWithSourceAudio`)**: (¡Nuevo!) Funciona exactamente igual que un cargador de vídeo de VHS, pero extrae y expone de manera segura la pista de audio original **completa** y sin recortes para asegurar que viaja inalterada a lo largo del proceso secuencial. + - **🧠 Cero OOM (¡Nuevo!):** Sustituye las acumulaciones en memoria por guardados temporales en disco (`.pt`), borrando la RAM de inmediato para poder procesar vídeos infinitos sin colapsar el sistema. Al desactivar `INPUT_IS_LIST`, maneja tensores puros eficientemente. + - **🎵 Passthrough de Audio (¡Nuevo!):** Alimenta directamente el audio original hacia el archivo ensamblado en el último ciclo (devolviendo `None` en los ciclos intermedios para ahorrar recursos). +7. **🎥 Load Video + Source Audio (`LoadVideoWithSourceAudio`)**: (¡Nuevo!) Funciona exactamente igual que un cargador de vídeo de VHS (incluyendo la vista previa en la UI y el botón de subida), pero extrae y expone de manera segura la pista de audio original **completa** y sin recortes para asegurar que viaja inalterada a lo largo del proceso secuencial. ## Configuración y Uso diff --git a/README_EN.md b/README_EN.md index 6379541..d30db3e 100644 --- a/README_EN.md +++ b/README_EN.md @@ -28,9 +28,9 @@ The system is built around three major categories: ### 🎞️ Video (Assembly and Validation) 5. **🛡️ Wan Frame Validator (`WanFrameValidator`)**: Validates and corrects the target number of frames to ensure they fit the `4k+1` formula required by specific models (e.g., Wan). 6. **🎞️ Incremental Auto-Stitcher (`IncrementalVideoStitcher`)**: Progressively archives generated tensors directly to the hard drive and safely assembles them at the end of all cycles. - - **🧠 Zero OOM (New!):** Replaces RAM accumulation with progressive temporary disk saves (`.pt`), clearing system memory immediately to enable infinite video processing without crashing the system. - - **🎵 Audio Passthrough (New!):** Feeds the original pure audio straight to the assembled output during the final loop iteration. -7. **🎥 Load Video + Source Audio (`LoadVideoWithSourceAudio`)**: (New!) Functions exactly like the standard VHS video loader, but extracts and safely exposes the **complete**, uncropped original audio track to ensure it travels unaltered throughout the sequential process. + - **🧠 Zero OOM (New!):** Replaces RAM accumulation with progressive temporary disk saves (`.pt`), clearing system memory immediately to enable infinite video processing without crashing the system. By disabling `INPUT_IS_LIST`, it handles raw tensors efficiently. + - **🎵 Audio Passthrough (New!):** Feeds the original pure audio straight to the assembled output during the final loop iteration (returning `None` during intermediate loops to save resources). +7. **🎥 Load Video + Source Audio (`LoadVideoWithSourceAudio`)**: (New!) Functions exactly like the standard VHS video loader (including UI preview widget and upload button), but extracts and safely exposes the **complete**, uncropped original audio track to ensure it travels unaltered throughout the sequential process. ## Setup & Usage diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index a774ccaed66146a15374e94e29c84782818d1033..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1370 zcmah}&u<$=6rQoy-o!tWwxKjh+jLAs>4IW~Ly?L~!6Km;r6$dWM)hU1cqZ;t>s@zt zw`!ssQpA}aI5Yu7ghZvH@CTF=M-E&Zh=eubP>B-~5`iF9>Zxzmc9NnZMw&M}@B7}{ z@4cDbAM^PPf^q+kAH;nFq2KhTH(~@fyNh7%B8I9+pelx$V1q32F|HDt0qP~as6;F#s11tEX_>T$NE`@Wm)bEa}#$^j98ux)DzYIC2Sp9PQ6;3EKXKx ziTzjv%1{#K?%!bUqOco@&84+l=5!KGbP)m zmrAp<<;q1{^-=0M4Nj>Vjp3=XJv&pnN_EH{%StF*Q#mGrrt2)l0fBBdA&;|^3QvTT z7Mn{dFL_;nN=ZGRahV5-?sxDZm{s)a;K*8GqqtuDc6@uV*fJJclM6qC%@+R~X`g;+ z+c>s{H!|y)En~braqg#yiw`b;U;VDSZH%qm*jQd)-ZEZpXLGCX?!sHp@A|q}NtF@} zr_Pn(`hHWTZVJZz=r}+-8=u}IHV*>fd!(#6WRBOkxXEQS(!sy}7jAP8iirv_`pK7n zKDa~5UQO}__d>@dr6}|#>`;WYYrq=oun6WPJXWPPE{7 zT7T?X5aG3s{#d>`44Pxfdy3VN&2L1|4dJ+2 zb1YdMecIX8DovbNeY z-yk`lLH%i|zVi*AMJ~S}Pr+7SgMfnBNn(uK14mc051$`yp`rH4Q!O;QYou`g(E!4Q zEkt(G2p|6i!DHd!iF57Iv*0L9;Wl}zJ@OhnpE%n-`Nm%(`SHX*D36mnV<=Z>y>OwG KdGodz3wQ#+w{2(u diff --git a/__pycache__/video.cpython-312.pyc b/__pycache__/video.cpython-312.pyc deleted file mode 100644 index c7ef75466b4284a49b2d2af81bf563580900277b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10439 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 XO)Z4Mq`Z@+VUdxN@`oDC62AT$Tzbig