From 0773b958cba9170051afc3c9269edb3a068b95dd Mon Sep 17 00:00:00 2001 From: Meisoftcoltd <38667978+Meisoftcoltd@users.noreply.github.com> Date: Sat, 21 Mar 2026 14:30:16 +0000 Subject: [PATCH] Add FFmpeg Video Stitcher node and update documentation - Created `video.py` introducing the `FFmpegVideoStitcher` node to concatenate video chunks generated via sequential batch loops. - Registered the node into ComfyUI's standard mapping dictionaries in `__init__.py`. - Added check for FFmpeg installation using `shutil.which` and raising a ComfyUI-friendly error if it's missing. - Updated node output file saving logic to utilize ComfyUI's native `folder_paths.get_output_directory()`. - Revised `README.md` and `README_ES.md` with explicit instructions on how to set up the workflow (VHS Combine integration) and provided warnings emphasizing the need for `easy cleanGpuUsed` after VAE decoders to prevent PyTorch OOM exceptions during iterative batching loops. --- README.md | 7 +++ README_ES.md | 7 +++ __init__.py | 5 +- __pycache__/__init__.cpython-312.pyc | Bin 3262 -> 0 bytes __pycache__/batch.cpython-312.pyc | Bin 13151 -> 0 bytes __pycache__/debug.cpython-312.pyc | Bin 4355 -> 0 bytes __pycache__/image.cpython-312.pyc | Bin 11359 -> 0 bytes __pycache__/loop.cpython-312.pyc | Bin 2107 -> 0 bytes __pycache__/paths.cpython-312.pyc | Bin 2472 -> 0 bytes __pycache__/sequence.cpython-312.pyc | Bin 5603 -> 0 bytes video.py | 70 +++++++++++++++++++++++++++ 11 files changed, 88 insertions(+), 1 deletion(-) delete mode 100644 __pycache__/__init__.cpython-312.pyc delete mode 100644 __pycache__/batch.cpython-312.pyc delete mode 100644 __pycache__/debug.cpython-312.pyc delete mode 100644 __pycache__/image.cpython-312.pyc delete mode 100644 __pycache__/loop.cpython-312.pyc delete mode 100644 __pycache__/paths.cpython-312.pyc delete mode 100644 __pycache__/sequence.cpython-312.pyc create mode 100644 video.py diff --git a/README.md b/README.md index 8d17a8d..1f2f9c7 100644 --- a/README.md +++ b/README.md @@ -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. --- diff --git a/README_ES.md b/README_ES.md index 6771055..afd856e 100644 --- a/README_ES.md +++ b/README_ES.md @@ -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". --- diff --git a/__init__.py b/__init__.py index 2315a32..a008bfe 100644 --- a/__init__.py +++ b/__init__.py @@ -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", diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 6e206bb4eb41e279ef71953826966bf575ea862c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3262 zcmai0TWlN06`hs1d{cZ{5BjyTEK{^CN^auVavUd;EXi^tMG@6FCW^2t&01NPzf&@NCF21E+4iNG+lE%mH4j%kPAmkRIq(D?s5a8QFr)bNY zLZOwmQcuZK77Jq8Tkw{B1z*`;@RtLHKsi_l3PfqS+|da4@K+^Nq~2A5`luf;K!boG z8U~EeHoz#20mf+pFiBH@yJ$OL2kivxqG`b0bPwQOx({$al>ocx0l*%55U`gXT7{j` z4DAD!rH28J(4&C;GzU092LX@K9{@f~wO)ce&H?Xhu-e&D{uq}eum79M4e}t>VY_uTmf;VbY02rQz?^WRq!3YbF@i4=U8UG^! z$&+l$%yRFTu50=!$uM=bvLs#7tO}LXieV};m9$HeX;n)~F6(1K)j(OUEGjHKH#Yyn zxtZep#o4huYlXR6(#-HQNXeNO&W;sFr-t+S;`H$B?BvWup81Q#idmQ>iPUaV+T z(IepJgRijwaGm@k+I72oJ$EPfY0tfAuI9N=8@%v$z)v_G|0Qca}FihwGv6^>Ytk74i-COxDZ%hVq)FU~EGZV&)?Q!xNaB?Frc^kIT(Yow_WJ4vk}ELZ zoTBM)KG(ALrsN7NJgSu!)QW7XTE(#9eBvjcO3f+@&nkM^GPn7xU6-0wCgw}(qQc{U z_5O`-Kf5V$fqBkrYQ=&(5;uBFLdHVVkn7U8TEQJTORl{waZz$q7MPZQqKx4AtpuO@ z-fu7_suGu3o3lm7Nii(EFzxkse}QhU$?;SS);3nL%8(h;4ViMb^O3ZryFtSfipj(0 zO{Hojx!da75~z-YggT3jYkFBWhfV1C1G%*ahMcu=!e#}E|$t>FJ zG+8k%yzZcakN8P_V9T<2v-fyOlT8aR!kzrNHOmssGxGdFwRS&diet{Q5KqLsHU-az zyKm?lE=uznw3cB)$7wPh&A(*fg9Gc|kWi37fu_-w*Hz`Gd_v>gzmidcRd zb>w05>ax;kjhnoV&L&Z35hr`|8rLkh(S8ek!!Jk38#l1ftgbETieZe%x)p=ueRcDX zkQ+yX^Xe?d9h+<56wEoZv*h+-I5Tjw^O}>+@6SLhR*&0RdZMA zg}0u5GzFo)@XH_Y01TXbWQCb*oP~E3#PB@%^CVg~s0&jObWC9j`GRuPPmH z4IK-w?*d?6t4bluI{y98_ajIM-3S?g-xD2kqT`*-{HMzrwMxnveFP}J7>r4HciF9i zAk^E^*F#^nch*R-LrJ<93X z1|8#c94PGlggwFOxdxr&bOtCSt=oQq(^;U=TCCfCk<*tPYybTVoW2Y+vQoZ(5!t^; zztC#`SRg`blcYCDx*kp4y!Oj$^*WnK-zSI9QLjZ^jR7 T#1GsLdH1yZn|Os*-fI5^#M8fc diff --git a/__pycache__/batch.cpython-312.pyc b/__pycache__/batch.cpython-312.pyc deleted file mode 100644 index a8016ebaae583b3e0e096672b3a8053c164b4da0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13151 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 diff --git a/__pycache__/debug.cpython-312.pyc b/__pycache__/debug.cpython-312.pyc deleted file mode 100644 index bbbbaaf12fa70c61a84834f6481bcfbdaa3319a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4355 zcmbssTWl29_1<~y?8|Gf-!_=B35ka$3x)I%8k`#A08O$6+bCkycC(%tn>Fj*&7E0n zuZ-kMw1UbNu7ZT!fLzo@#G+CasZ#&@RjEI=N`yQ3r>~FvEm?z2}~L&-4Bz91aj@-?;G8^jpyTCl0(ORyyTBL+2LJiB2;l;eKg?I%6)uIbS}( z>zvMKgefs0Qeuc4IYo5gD$&LJoXh8ag`qH6LBR)Jq8C zbbprWfkZ$L8i5aq9=gLPf)1ZB@Tv3mNQ5doM*v&zVXHfbXQ|ro8FG(*MggkS4l`pS zZCVCPW^=ls)$B|va+C760eg!W1kViTr3u~;bnYst@)dPHA?ZTGr;7<$mlA&6ca9{Ua7jXD8C*v&F7~T=ufTEG3nlRnpmf!BVW8Vp{AFGx98^p-5iCYr%iH4gg#} z#p_MeG#w$T5VvJ!ykAH&L$~P+oPr))#luk3L>|hQ;X|o>{*Z2r6((Z&SvzU1QjjWYUWt0;sOS@>WLnl!p&AV7+C&1r1 zXAC;Gh*iCVIeNd+w~+3%E!;RaLRM*?-x>; z3dNU9>NyS2K`oOq%_+mWn9~`m%MJ|>j-DTmyBA&^#rxkG9I@p$&&LNw&kTVM2l__` zPYn%UaO7k`Sm_Ah$}Zs$fpe-O(78(fF7$4Q^^ZAG43v2yHI%Sdsv}Lk^KWl7_5g@9 z&Y#|h#O6sVQ@J z6YT|tpt>sm$yxb0oQ?W|FMVsafYls_hGFsA#}Dn#OI>lis0gcn2X6jgLk`^tT@O8!JJ#in z2lAdJeI>J;dDwM$z3cF&T}K?7)2jM|El{ucFWUhAKRdZ1UJ-WJ)$ZE(A1pwaL(Fph^K}5A-p;O-W6Q^WYu+o~EiSfR^KEn=xVPu- zo{z2H&#lgF(`)2L_;=%L*098aI#c}7Z&X%kCw`0w#(yUUW552<`j@e4lhjn!j&klKBH%x$KTv35MWmF^ej`cwJZT}^^D|gy7A6ZJ_eN9r1P9=iic`SfNDyFYD$8-4=bvmQ&Guh zFu?X?!V zn4|eu9nsQIZJi`HsK%v<#{CG&;45?v>aP#D1)l(zD^%xiL;ay~)EP}^M_ZpMz=EFznk8pfTlQ{zl0$AWRFHEu@TWIM{^KjoHlvucf(RJ?;7O{W&tnNEYk zpS#0*&H(i=nWG<3tI?x-dp?WNh1=Xm^mn57+@M5ve%=!S9Ls0n%{Wb$ygd-VpL`RX zeHyfV0nUP$8U6y9rm7q-&IH`p{4Deoi5BS;;u8?Apx93_w7=&r8)dV~dyZ%KrquVI zKd;!lD~n=~yg=6*4>*EL*_Tq4Ksgmda)gC^&FDWlq>)CI{i6 z`0B`|bUu%(J##_vA>2;oEoIEmQU%jMk7ABHWpj#U&Bk1_4!Vi&AY7m=rOgyjoLpcF z8h9TYM7(SnGuAXqC$wHO_w){(Z={NpO1fTn0_(det5Piy%=z&rX zsgFM4NjSO|J^dhbW+T$@biarH(us(x{lOD{{!f1KMAR2oLpyWm9*~$}nsgovtP6Yl z5GV+`0ocKz^P~7`iw~U~bgpk_81BQV2#&+w{0;zoAq+S#1flvufL;z~On{ cQ%R<+j|qTMi^H;qMoR>M!}2i)Jx9X-0v`HmBme*a diff --git a/__pycache__/image.cpython-312.pyc b/__pycache__/image.cpython-312.pyc deleted file mode 100644 index 38c5754c7c72ef3a8fe69d25afb9de3f74f89164..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11359 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 YYuxc~;RL>fYlvewuHf5semJiG2iSyEyZ`_I diff --git a/__pycache__/sequence.cpython-312.pyc b/__pycache__/sequence.cpython-312.pyc deleted file mode 100644 index 289d03ea726ff1df707169056bd047356babbd5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5603 zcmb_gT}&I<6`o&vY)m`^X9EcYlQuLCY|>3>mo^dkAq3L0CW}p5oZWRNp22ZCc;JrN zB*@XSQX=h2)s}6mhDxZ-BPjd8V;+(E5~+O{m##u)_n}hThrBsLRn>iI&$%;p#^6xe z*-`=bk%%Xlx8JP`W4X=01@b=C9bO6w$40eg&0#jLN92&ZL}QHpS95 zm*P}T<#m2aNC_;X2^=%TsKOma6(4ZU{0FX>l<0DO!1ZtA`dqFA+`u-j-{l5@+pvu* zx!e$N8@F)-E_V-b!`rw)mn#FeX&blU78~396i*O;$^wnCb|cZUIkTXVbly<4jC(rz z>Z}62SMdhqROXys*v)lVoFj$BdNeGe@wE)F(seXGPqQw z1fash|K@2RU?3vw3*o)(Nvqi6i*V| z@Y4@-*?&L%;Ma=`!DUti+plVq%B)_n`KeqUh;q{w;K0*Q5i2p?7R`b}3fN~%6F?2K z!mO}1Ynr(2=vVRyWqSIA2@7fYjCOqbmfdhEetA5ZPEK4NOw2XWBOdp3HNrG=1jsy7 z_A!n7Hx6_xzrA|%k>8p~l{&9jt?z8K9$G$M>4{#kT4>L?)oW`vtRGL5qA9E8$}_*v zx~I(h z$O)$_qh$mX;}&!XYKcQoxyKZ$Rw}YZuE;+?9+XMY^$HsYsE7ou852NNnm$SP0+Tc& zL8X}}ItqpC$1apPCpo&w8f`cr0%V@~Qr@?qEhg{xuFFTQaJMCOlU88FSAw>bPUn>= zEuFT5>GYJL&gxhXrPDLBitch6GP+`#Q(EDgp^_GuXotoIljCD?#~jsG{M=iE30t~2 z9v?_v8U>^boJ$T4jgC#woe|s%UDw&I51U9D;NLs}=ZGJ3AOLFx7{&hKKg?lW?F>suG9R_R#4{nzriNe1ZJYp3*P%H0w5qMsA)PoXU zHF(A}gQyy*0Z-NKw}L0fLh$u10em31y5J#bG>DOYqP?#XQv=sGNMSDIHlx_0P$H*r zAV=Ea$M(7YNxiH-rre!*G1UiC%`PAnOtl@`IM~0|`sku{Henq|J`;Ham587ccLEiH zs8R%|P*9O^=t^(rI0px~eMub8Qv1WR2sebt9wbWw5_&b~s*w5?-;Fga(YMtGT>V$QgOF#?BZ%DR_FI;)%1~Y(!E6YEW%Z zLrXlkPotZg>~Uf*bsvH$56w*oUN1B)At9*Q{_~@wBZKGS7j1q*Gi@$zfFWsMmUQK+ zrl*a(9dK)tlfa|iC1#A-e8FxY+O)1@v?(oL*dcnicu(hbx3^#iCVH*M-DsvVv$o$F zOO!g3R_pjiG`2eX=mU-JbE7IDI2mxR&;4&klLnH4cJGB5@$~TqInh z+N|*oj(Y>hJoBXCwJ#&>k0Yl_ky8(c*R=J>h}Ar5g+~8|y~j$CW6OQ3fga{(D?|GAE0!P-|Q+i5yUf&jZng?_M6KZnVK!^o0e|{1BvjuY&)E>kG`!`3#qZ6zE;R^liS#f6D#LCqVl) zk0{P?EUn+DBsb(TtYC+xJxbpW)w3|BE>vqYlQm2nhfxhedfSo5Z6`}@Cs+F(j{muB zU|qgYY8zOX`9+h}Hc*l;Sm7Z{8v56t-d;aPE* zb)Z@i0Ro&-xv5{`9h%T{@cMBxMk*B_e>5odL&X!@={r=F=u4D`H)(Af9=P~Ub@_oM z!F`F+OnYmz$0fFee6Se3cx&HIJB05hx^pjE6i5}zvHCB}p@+kdeq_CuE=7N0 zwJ7-J-15QUc(>5}V2f1+5#T6 zCg`PMJ~<1dUeNQ?Q)i9eBHuaelkRV=%e^ao_mzc~d+ndLFV1}4)VbVOl6$T2o0jw@ zgt=ZS>TXNt3&PyHxFnKKP^<@Iy9{%EPLx9lu@Lz-{MdoX*?fj(Dm2Iw{N6^;)95z| z`nII0GtC2Nh@~K>yKt!Wnl9-oguTs iY?(povS>#a