Merge pull request #27 from Meisoftcoltd/fix-video-nodes-10941441744767870842

Arreglar nodos LoadVideoWithSourceAudio e IncrementalVideoStitcher
This commit is contained in:
Meisoft
2026-03-28 16:09:20 +01:00
committed by GitHub
5 changed files with 55 additions and 37 deletions
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+1
View File
@@ -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)
Binary file not shown.
+48 -31
View File
@@ -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)
# 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)
}