Merge pull request #12 from Meisoftcoltd/ffmpeg-video-stitcher-3348528563198334568

feat: Add FFmpeg Video Stitcher for Sequential Batcher
This commit is contained in:
Meisoft
2026-03-21 15:34:37 +01:00
committed by GitHub
11 changed files with 88 additions and 1 deletions
+7
View File
@@ -34,6 +34,13 @@ Video models produce many frames that can easily exceed 24GB VRAM.
1. **Split**: Use `Latent Batch To List` to turn your video latent into a list of single frames.
2. **Process**: Connect to your KSampler/VAE Decoder. ComfyUI will process Frame 1, then Frame 2, then Frame 3...
3. **Gather**: Use `Latent List To Batch` (or `Image List To Batch` if you decoded first) to reconstruct the full video batch for saving.
4. **Stitch (VHS Combine)**: To stitch heavy video chunks sequentially:
- *Option A (Math)*: `Range` -> `MakeBatch` (attribute: "skip_frames") -> `BatchToList` -> `GetAttributeInt` (name: "skip_frames") connected to `VHS_LoadVideo`.
- *Option B (CSV)*: `LoadCSV` (with a `skip_frames` column) -> `BatchToList` -> `GetAttributeInt`.
- Connect the output of `VHS_VideoCombine` (specifically the `VHS_FILENAMES` output) to the **FFmpeg Video Stitcher** node (`video_paths`). The Stitcher will wait for all chunks to finish and combine them automatically!
> [!WARNING]
> **OOM Prevention (Memory Warning):** While `BatchToList` solves the data passing issue, you must still manage PyTorch VRAM. It is highly recommended to use the **easy cleanGpuUsed** node (or similar VRAM clearing nodes) after the VAE Decoder in each cycle of the Batch. Without manual VRAM clearing in each iteration, the sequential loop will accumulate garbage in your graphics card and eventually cause an Out of Memory error.
---
+7
View File
@@ -34,6 +34,13 @@ Los modelos de vídeo generan muchos fotogramas que pueden superar fácilmente l
1. **Split (Dividir)**: Usa `Latent Batch To List` para convertir tu latente de vídeo en una lista de fotogramas individuales.
2. **Process (Procesar)**: Conecta a tu KSampler o VAE Decoder. ComfyUI procesará el Fotograma 1, luego el 2, luego el 3... ahorrando memoria.
3. **Gather (Reunir)**: Usa `Latent List To Batch` (o `Image List To Batch` si decodificaste primero) para reconstruir el lote completo de vídeo para guardarlo.
4. **Stitch (Unir con VHS)**: Para unir chunks pesados de vídeo secuencialmente:
- *Opción A (Matemática)*: `Range` -> `MakeBatch` (atributo: "skip_frames") -> `BatchToList` -> `GetAttributeInt` (nombre: "skip_frames") conectado a `VHS_LoadVideo`.
- *Opción B (Por CSV)*: `LoadCSV` (con una columna `skip_frames`) -> `BatchToList` -> `GetAttributeInt`.
- Conecta la salida de `VHS_VideoCombine` (específicamente la salida `VHS_FILENAMES`) al nodo **FFmpeg Video Stitcher** (`video_paths`). ¡El Stitcher esperará a que terminen todos los chunks y los unirá automáticamente!
> [!WARNING]
> **Prevención OOM (Aviso de Memoria):** Aunque `BatchToList` soluciona el paso de datos, a nivel de VRAM de PyTorch, se debe seguir gestionando la memoria. Es altamente recomendable usar el nodo **easy cleanGpuUsed** (o similar) después del decodificador (VAE) en cada ciclo del Batch. Sin la liberación manual de VRAM en cada iteración, el bucle secuencial acumulará basura en la tarjeta gráfica y eventualmente provocará un error de "Out of Memory".
---
+4 -1
View File
@@ -12,7 +12,7 @@ def register_node(c, display_name=None):
return c
from . import sequence, paths, batch, image, debug, loop
from . import sequence, paths, batch, image, debug, loop, video
# Display name mappings with emojis
NODE_DISPLAY_NAME_MAPPINGS.update({
@@ -53,6 +53,9 @@ NODE_DISPLAY_NAME_MAPPINGS.update({
"LatentBatchToList": "🎞️ Latent Batch To List",
"LatentListToBatch": "🎞️ Latent List To Batch",
# Video
"FFmpegVideoStitcher": "🎞️ FFmpeg Video Stitcher",
# Debug
"Stringify": "🧵 Stringify",
"Interact": "⌨️ Interact",
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+70
View File
@@ -0,0 +1,70 @@
import os
import shutil
import subprocess
import folder_paths
from . import register_node
@register_node
class FFmpegVideoStitcher:
"""Toma una lista de rutas de vídeo generadas en un Batch secuencial, crea un TXT/CSV y los une con FFmpeg."""
@classmethod
def INPUT_TYPES(s):
return {
"required": {
# VHS_FILENAMES proviene del nodo de guardado de vídeo
"video_paths": ("VHS_FILENAMES", ),
"output_filename": ("STRING", {"default": "Video_SCAIL_Completo.mp4"}),
},
}
RETURN_TYPES = ("STRING", )
RETURN_NAMES = ("final_video_path", )
# Al ser INPUT_IS_LIST = True, ComfyUI acumulará los resultados de todos los
# pasos del Batch y ejecutará esto al final del todo.
INPUT_IS_LIST = True
FUNCTION = "stitch_videos"
CATEGORY = "🔁 Sequential Batcher/Video"
def stitch_videos(self, video_paths, output_filename):
# Aplanar la lista de rutas, ya que VHS_FILENAMES suele venir anidado
flat_paths = []
for path_group in video_paths:
for p in path_group:
if isinstance(p, str) and p.endswith(".mp4"):
flat_paths.append(p)
# Como INPUT_IS_LIST es True, output_filename viene como lista, tomamos el primero
out_name = output_filename[0] if isinstance(output_filename, list) else output_filename
if not flat_paths:
return ("Error: No se encontraron vídeos para unir en el Batch.", )
if not shutil.which("ffmpeg"):
raise FileNotFoundError("FFmpeg is not installed or not found in system PATH. Please install FFmpeg to stitch videos.")
# 1. Crear el archivo de lista de concatenación en el directorio de salida estándar
output_dir = folder_paths.get_output_directory()
list_file_path = os.path.join(output_dir, "batch_concat_list.txt")
with open(list_file_path, 'w', encoding='utf-8') as f:
for video in sorted(flat_paths): # Mantener el orden de los chunks
f.write(f"file '{os.path.abspath(video)}'\n")
# 2. Definir ruta final
final_output = os.path.join(output_dir, out_name)
# 3. FFmpeg en modo copia (rápido y sin recodificar)
ffmpeg_cmd = [
"ffmpeg", "-y", "-f", "concat", "-safe", "0",
"-i", list_file_path, "-c", "copy", final_output
]
try:
subprocess.run(ffmpeg_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(f"[Sequential Batcher] Vídeo final ensamblado exitosamente: {final_output}")
except subprocess.CalledProcessError as e:
print(f"[Sequential Batcher] Error de FFmpeg: {e.stderr.decode()}")
raise e
return (final_output, )