From bd6016f5592d00873cd72daf6ca8abacfe705de2 Mon Sep 17 00:00:00 2001 From: Meisoftcoltd <38667978+Meisoftcoltd@users.noreply.github.com> Date: Sun, 5 Apr 2026 21:32:14 +0000 Subject: [PATCH] fix: bypass keyframe save and output full tensor on final cycle - Updated `SessionImageSender` to conditionally save keyframe only when not in the final cycle. - Updated `IncrementalVideoStitcher` to return full concatenated tensor when in the final cycle, avoiding dummy frame output and blocking downstream nodes. - Expose `IS_FINAL_CYCLE` boolean from `IncrementalVideoStitcher` to UI for downstream conditionals. --- __pycache__/__init__.cpython-312.pyc | Bin 0 -> 1561 bytes __pycache__/image.cpython-312.pyc | Bin 0 -> 8902 bytes __pycache__/loop.cpython-312.pyc | Bin 11504 -> 11504 bytes __pycache__/video.cpython-312.pyc | Bin 19936 -> 20270 bytes image.py | 23 ++++++++++----- video.py | 40 +++++++++++++++------------ 6 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 __pycache__/__init__.cpython-312.pyc create mode 100644 __pycache__/image.cpython-312.pyc diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89df6e4e004dc51fefe0751e24a4c3eff06779f0 GIT binary patch literal 1561 zcmaJ>&1)M+6rYt=AJ&IIq{w!a+8xz2(K@w^?Ia|Srl_`)2o%-nnk3!JuxJKl!)jOA zk6;-e9O$WpxL`_>QtC^42+gI3LVD^SP$+g9=rSkM_R>>bTqxw!nUz*{TGNG@ee-_5 zdGF2p?d%WfbONyFw;!Ktu?PUa@*c(I2!jO02w?HYEqbHYws^CqWNlBmeJMn(o&^%S*CG>47EuRqIn-)sK`sz@`tEa zzPi|BNg*oKX$ot0OIO?e2AdZQ;mg5kwbhzNNHa7Cp;D_&(-vuHwnHprn3z~xt|%LJ ziB0RkJ8|-bIPyr?N<2>Nh!ft_`R7xYpI!T2`A&Hujz7AywfcBL~E)ILPgy)TQqW4!^ET!$24`@JmO+FfF~uXXS* z|AouMVD-5LAARt}AI~;m#i(1PNeoBTVcB)eDcRAS`YpzK+h=|HEoWWl$zj=0_4X>U z;3c(AASMo}JEjFsv5iOIxs#oejz(eehH7+@%;D?hMaiA` ze|mNKCY&wJN$!Zx+ql=sCp)U;z+#0Rb47B;eeARK4z_G*jRq^e=pXZ%OI*^~{>#QU z{EXZUFD=Qisx>txW;gWaC#tlyG%FT+TS()5%*(ar`I*;RvLUXS=yK2yW9+`E$ z+jQF3?bx{$n&_IWwLP2lv1hZM(R7!?Y?j&Le+P@-J&XT$q$Euu#O?#XA_yHq`4y$W zN^@pEp9c6e$h}dwg;j^p7(%M9BlHAVNe=e0>1F&v)5NY$E?RG~CBLV3i%olBK@hzD z+=qJIEEInl!a literal 0 HcmV?d00001 diff --git a/__pycache__/image.cpython-312.pyc b/__pycache__/image.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45127b9cfa19e80e685ebaae7135eed4c35284d5 GIT binary patch literal 8902 zcmcgRX>1!;dNVvnA|>jkZjBDxlC4mV;yb={ONmW6lI+@w6K2dANyAX2{AOrd%Ft1Q z>{dooSZQ`^JF#J<=yqkKKm{ziP`8WLt+A+qrob@NR@||JdQ$_1e^p**(S`eC`+YMU ziB^=fTNHf+-@JLp_rCYN@1B3m$;qamL_Ysqd{{|Q|B5eWv8NkPz6Xsf6i@MVm>M>o z^f0ZzTZS$A(>iR^pY~xpJS}0zxO3P^8*`aqhPU#zaMpPCa5nVOoQu!q?eEdUZXw%3 z^-#RyJ&JdJYBBcvG`;O`PI{Dq(X6kH=1$U0u17f6e~Z@aedFAy@Q8-jCR)oO!l)?8 z0trN-ybw$S*5Nv#w!;6(pTXQKl#DC%63U#F?lVz|TNL_ZKwLISLWLaZDH#vm!dp!= zVYcmtEpC+=b3#ZbwE1Q{LpjgBg$Oq;XjxM1%t$oM3j_uyCxoUv&278~COCOa(h5ceIe^Ooyhwzg93_*Q zgK!Z(I<7hKDMn3}EchY!Y`)XmV<9S%^qc5)hGRHOB-IM#KvmW8y&Yri~NmJ7$UFqX7f7 z6`B&VFl&^jIW648M2il{H!-Odr6J2v<3#!ZX@muC_)9;4YMQ$5saW>-5*}aDvwgZZ zRZu!xdNu$0uA2w19h~2^P@JsTt(Nauw5gs0)4lgS`IpaMI)7QYq+IQt8%ugxU`1Zh ztTNa3!Q0E;tqJeeq_=t5+nVsUCcQfnc{`^2?q_>0w_IwuUUzfLwJmc^^RFyeljS>> z%U?{CzxdPkzwQ02-eh@KGP`@aBV}{`e(yWIGbfj9C8<(&o-FQ4xY|`)`x8Kv3Swmz zQ#zk?L(dgTpilt~TLddlzekyJ07}RL3dlO_;BCWB-VTb#F`NZ+oEdYnc~B_BuF0&X zY^`WekR&l0(Y42b5ER670tv3_1lVd-Um#?Y&$0h#{|-%)Abp8;yt?H|70sv&*)!xUti379fZT9 zmbb~`xNyq?(ifIA$D8LlGAc<3kxi2%yKgYi(c9+l>FS)S*_a}$6E?%TC%I5fQx83b zmnSbx{=r)+Q?4I7m{xW=LU?hy$@pg=zQF=vYMhQ+X==?NC>D9uB$!5k*cD(FRNfI< zvBYhPjkmrDR{-i5Vd@1G9rT*YHSTz3#Lm;JBTgB~rWaJlA5%9hYw9v%jM=a^OR<4T z%2pguyA)W>yKgwxW-4~cA)|`W%XG6s|8LC==g7b%Cu0XX&P4Jv#&Xxj)^?6F88bWz z6DlxqT6=!u$?;T={EO%;LOf9@dLAqK-ymJ7-;F1(M0UWGkIqN7BRqO5`R zKK7}VZDFUJtbuWC2f#NLuiX6Z_u0OP7!=XSv63K(cYYLMgV6{Z=2%^*BW(XbUr(RE z?T9aSXm#P2*Z%t77caBNC4HR`W?vR2b?EFlE-dmK0Gnh1hae|on->t?K-*DY43($3 z`5*rjfeh&;kd?-`31J_Ln$u9ennOoG0x7q|L@>mH1j@3>MbK3ueNbuHq9jHnnTrGk zxGnJWG$&{yPL_#gpCD+hi_#YdM?>wN)~a@(SwtWxkD6WIShtK|25gcL9w9q%hh3}N zFoyEw!7__dU_>Oakt>50G3u%=nsz0Ffhei zeU(=m4&BaAmUPUx?!&9BZaRFsHtFr0aX(~oW-YV3W*cY5mzc`?OpcoO{1Wqm-e{OR zIroz4Yrp;C?dR@F>R><}ctd?-RLvb*V#Hs%3YWawRPPI^`t8g0yAt)glJ$F*>-Q(> z_b2NQs&4kaJ6|p8U2?ya@-|%ReTZ*^OYYIyngF-wRyjqdtiw< z2z$3ybM+q| zLi-7F)}=As$Ne2cef?m=JKBc2din=WkuHFsUl-YpE!`ZV;U(M53iXTB1J!%f1AE?M zi^HD%#7a5xSFp8`M-^31A4wH%f~ufo`sI|T9GoYQZ@O2XOxr8cldztlc?bi8I;OsUZP}zfzoi_A<9OwZ#)_FjAC)$qmbpmg62Kvym8w4jv zGMylD0QUI5@N)=iTXGnycBnF)8=W7gs=j_}zBQ-k6fQ&$+3;d)&@#)aBl~0UQ!>05 z8${$*o(fyy;5ft68h7YVTimHX?Qx9Wsjvg=CPwkjaV8E?yh2aA)`CjjZd&tqX?e}+ zTGj$X)B3LSiCo}mY!Gr9UQ^tyxOsXCv}im>UJW}p@P5|DGQFR*-Ymuatk(@)cUi*Mt8}=Iphn=^TgfX4e&SjQ3#pA409Qu0RNm!N9s=;utT0lcFA4 zR}}1TUe^h)=IzD{6;G(;>*GoxpU3C(1%F=nX__bFMKUS`y(mTIzTh8sqLjZst>njZ z_#&Xw+AS6<`9R5%8yS?O!LzneN}iO-A6^q0a&|l?;?00jW>OmicHAqYCeaIc@)a4rd8y)6O7%obnT)o=oKtDiMFLq;Y=PC}$TN_t_#E@z z@DUe?@Vequ#H6*}*Z^Db0DYgrA2vy>`qLfX6rxdh&!(P=HSXU^v4Ixy#c^Y`H zGJ|S0iWlg`X3{HTw02`Su%b?>0J+Od>y!YO164z)FV_{5XG7n@>+ve3imOp7r5{=- zhz<^~0|Bollz25?uekVzk8S*>8`gM@;tZieT~|VAjE70!<^{(rLSEUv->Spuqe|IT0`8-~y)P-K&06uHZfJk*sjl53n5rcm8mw zuLEKyKL$4tkOajuqCguLYml!9t$6y79X@KsXpM)U+5@ktL!u``g@GL}typ_bUjh%u7 zVV4n2h582#uPmdA%4p)zi=JVJ{{&STrExnRGr&<(L= zlxS|qQTYXNbnHx&NSYJg2E{31D2ArP!1K%N-(d#~d<>TjBR_+>1{SYQSzhzSVrvUN zn!|Io9qAev0=gJ?WGub&qYaMc@ALQE`N(ga$;S?I;d3TEf_fAjTMb8z4Y#usQ38}7 z!CbB{hHiTrRb%HaLSP(F?4J-K9Vb{wR~g|e*~i9CuFbxDi|s@wG^j_%Kvje6c&QRl zM?A;zoG&97HgCtkf;%Hc!!a47;4mAG$^zRsWohzh-qp|)qs0K;+hiajkCB&PXU&ry z4a*lW$PoxibqsUmCbi$q2%cpUivT-8B7lwI7l3rA&z`&U354TO$(OMbn0kgCg7jHf zj0h|TG@LS45GsFlH1XLq<XQzG`B8S>0F5B(FH@oY#Pu3@+1z>Vj?eSr5q3J7=+|nl!&9qFJ_j_W6A+60)d0? zd5CJRo{qt3!O0+yHyiE>B1P1_niGhI6w|+*auwt-xecVLtyq) zZB}ImvAPf~k7os!(0^_X{f3s#lW*r&agqZ%O79 zZ(1(imMGpf-h9Btn$xpx=rlSOPA6;HXWb9;iq%q1Jrh==6NxhuDtA^r8&^v% zF6F)bptNe%e!r~x>ePJOwTts)p*2~y>sM4(e)Viys@gYyHc|cja`nzc_0DAV?&azO ziRuH%>O;3TC8~RttNIgF{pzuSKm>yDYoIN{eEec z%J$yPziYkQs#g1#O8b9NTD4LLNIfp0N~`V{m90=z)qv$IiYgwmq-xmN-rs!1P&ox^ zQCJ;6uTH&{7=KF*E2?r)EqEJd<+NE+-dgyKb#bN_K8Tby&gaaRF0|dMRK2@q+^O=q zxmT_=&v@Xz3d*h)sWp2RYm@o=XB?^e)`b&^`WID(edsBfmDQ>}OP;-{=X^7rOYW^O zy`pZeL2cZ-xHDPaHskq)s~pg1+&2IG{M)zl6D{qxM-sjx$t_1!cf$k5z07R>oY|~y zZBH=mKW8>Sd^T+dc$HT#m+elJ?OxoUDC?QY)l+IM3m&!QuzL6vHFsc%8Ppk}Wx=+* z-JjU*S6lj5vv|4O5_9H3aqV2)T)kSndAYbbQQVv?_AM8;CW>2=#XEq@YMNB0^09-; zt4@{do((UT>`s*IPL}MQ=}DE<&Fz`*nDZ^zRPWB2-juiO%J~n@Ur|0#=6dE&CV@`9 z4@xV6O=_>jmup%RHLXeDxzZgoeW`-l*-ckkK4_WSGFSJ*EuU*PE<6nk?|obfny#%e|L+XHPDugpmh-wl&+Gor6)TQB zl~9}iy|p9f$S&$<`J15rY**INQtM}ja*q~T|EbUpZOA{8Gcb(=vEs23u-b>!2v(z5 zjbSBX6~YQb3xfJ%efltreOUFtUqY*yP9Hi~(}&PnDM+Ue8~;b?!#8OAOX>U}f`(6a zE@?IUJ=)|kG=Ccmks_>gsLM-_fSRXp%ks8rDto0IOK q;iMMNe^^@RZhD2n8q%T`&x+A;hSmpTH2o5c>JNSRu|;p|X#5xW@|4p6 literal 0 HcmV?d00001 diff --git a/__pycache__/loop.cpython-312.pyc b/__pycache__/loop.cpython-312.pyc index ae70f83bb6ea215173cf1de96aad62059e93aff8..eba9e6861b602394cd34e1a1f606506d01b3c20b 100644 GIT binary patch delta 22 ccmewm`5}_`G%qg~0}wnpcPVq_M&8Fd0A#cX@&Et; delta 22 ccmewm`5}_`G%qg~0}$v>y_C6eBkyA!09>~S3;+NC diff --git a/__pycache__/video.cpython-312.pyc b/__pycache__/video.cpython-312.pyc index f69fb318770eb968ac32ffce2ffdbce80b53402b..36f206528aeb32b1437adc8c863488e1b887122d 100644 GIT binary patch delta 1068 zcmYjQ-A~(A9QDlyTZl;rF&_@J+>{25W${EQq*)+^K?xa7E5@n`b&}`CW(rMWWIJL- zfzm$lfW*+%259Q=w1-tfMX91yeVjDyL)spkk}Z+dhi>X05UH9}Z4%o}s5Y*yj_y6b zd(Q7%TleYb;NT13__?X6k-%f~@#Ccb-3JbCC}fsLF(F54;K9&mHX6rRmW##5qL*e3 z+t}5K*cI-|)%OfXY?hmhT^?h(@wsvK651N-0iPAV8A^iFYA06KN6sQN31HuRyjy9^ zSr)p`1G_uWc;f?tSh45qvaYtjJWL3VeQ>-6#0l0}kT3KDx_RI9(#CnB=*l_K3`rq9 zWDB<3HUDprbLLEfb>2LC(#n0J4g^XdJj8+@d2ODqmF8TtKnSfhK@S0lIbx&uv3ZVI z0)1`hD0KTn`33HteIEmaFc5*&z0KP&DrQ8PPb9x9WMD!{FQ!D5XCRxrj@gVLK~>Bs zk_=Tz#P`BZj6f{VP&!$nz2h>E1Z`DBJqi3(bM>(1NR!TZuGzQsL?mxWvswN;K?zD#ab=Rt%6dz@kAO*N;iVUT?|O+bb@# z)=iu?`B(Qmzn?Gx){g+5iLgGD;{Xt+p6*B!sx=_jO1 zr*4$UbbY~=$jcQ^*ZTC6R*&8p$b({M|8K45J^>Y~Z9BRZ-DbB~jlMUZpUErv=%WGs zjf;oWC@#BOw}V^3?Z{R{^Zx2STcNtkRB!PX)vGOQ*{_$sT+VyHS}9S375dCh%O1T; zYyEe-sy3o+pz0vHd^>}Cq1}*nEzg%aFRZimEJOKRIT$MjV?TL|K~^7%>v3Lhxn3d@ z)dr%~tNHFxx|`8S=2`6uu@X65@j$IbbMN@e9)Ho}KlJzm>(jNvP@}Z0?hfc=;6;_j z;@yeA6cYcZ?>kze7SoRwQ}k8z%g8zW?fw}V>F!@+EACMY&i1vbij!HJdT!~lGV(B1 z$TooBUteR@u1@vhgf@|nu{PlkU9s^DI delta 780 zcmZ8e?N3rs96slQ@y@+?)k}gQ1Wsz&5Mhbn%Sy${%&ZSAm9c>iQ>zO`Zur(GbTp;2 z2Cda-eUQ!7aQp*((GS&!OHnZA7Hxfx=7(Hu)xEg3=CiZsob!8rzvsOCUOj`wCs6)Y zCKCe|hDY!H53X#<-BuY!cd-GwgC!$|7OhIOVb#I&nKxEH(J!T}( zs=bX8`UCE9|BPp9S9Y8B-SzuI?s3{PjXvAeI{&n1!WRe-Y*GCY&^FFVj!rv_a1~v5 zBo~V$l_yPE(v-fIw+v_5K@LZRUvR3R)+J5Jp~S_=K%q*TY-PwsM%wsQC}Rj`PI#eE zql`%Q6g3QGOm%0erca9INOys#j0<9dxHKkBxKa&iMcSMeqytQ2`v=0#%D7@Jv=Uld zSXoGTKH~KSLbaGDbXh`|v?VPYw&%8#=Y=CjmFtZ(efl^Xn=Mw4M7h z5k&PCC1M)dA9nqQH!^>^cKA@A+368sFol@Dl4w^j;R||#K{~VTJO<%R=3W1No~{Q@#$`gw9=#TAHAt1X$YV0Sqr; d+;$AI%O98@!ai66<*BTGKMWm|1`&Vo)L*pH= source_total + print(f" -> ✂️ Tensor truncado a {frames_accepted} frames válidos.") print(f" -> 📈 Timeline avanzado a {loop.global_accumulated_frames} / {source_total}") last_frame = valid_images[-1:].clone().cpu() global_session_image = last_frame - out_dir = folder_paths.get_output_directory() - filename = f"keyframe_{loop_idx:03d}.png" - filepath = os.path.join(out_dir, filename) + # 🛡️ Solo guardamos el keyframe si habrá un próximo ciclo + if not is_final_cycle: + out_dir = folder_paths.get_output_directory() + filename = f"keyframe_{loop_idx:03d}.png" + filepath = os.path.join(out_dir, filename) - img_array = 255. * last_frame[0].numpy() - img = Image.fromarray(np.clip(img_array, 0, 255).astype(np.uint8)) - img.save(filepath) + img_array = 255. * last_frame[0].numpy() + img = Image.fromarray(np.clip(img_array, 0, 255).astype(np.uint8)) + img.save(filepath) + + print(f" -> 💾 Keyframe seguro guardado: {filename}") + else: + print(f" -> 🚀 Ciclo final/único detectado. Omitiendo guardado de keyframe.") - print(f" -> 💾 Keyframe seguro guardado: {filename}") print(f"{'='*50}\n") ui_image = tensor_to_temp_image(last_frame, "sender") diff --git a/video.py b/video.py index 5e96eda..b6c857a 100644 --- a/video.py +++ b/video.py @@ -306,8 +306,8 @@ class IncrementalVideoStitcher: } } - RETURN_TYPES = ("IMAGE", "AUDIO") - RETURN_NAMES = ("ALL_IMAGES", "AUDIO_OUT") + RETURN_TYPES = ("IMAGE", "AUDIO", "BOOLEAN") + RETURN_NAMES = ("ALL_IMAGES", "AUDIO_OUT", "IS_FINAL_CYCLE") FUNCTION = "stitch" CATEGORY = "🔁 Sequential Batcher/Video" @@ -322,25 +322,31 @@ class IncrementalVideoStitcher: torch.save(images.cpu(), path) print(f"🎞️ [Stitcher] Lote {current_loop_index} guardado en disco ({images.shape[0]} frames).") - is_final = loop.global_accumulated_frames >= loop.global_source_frame_count + source_total = getattr(loop, 'global_source_frame_count', 1) + is_final_cycle = loop.global_accumulated_frames >= source_total - if not is_final: - preview_frame = images[0:1] - return (preview_frame, None) + if is_final_cycle: + print(f" -> 🏁 Generación completa. Liberando tensor total al flujo para procesos finales.") + print(f"📦 [Stitcher] Ensamblando todos los lotes de vídeo...") + all_tensors = [] + for i in range(current_loop_index + 1): + p = os.path.join(cache_dir, f"batch_{i:04d}.pt") + if os.path.exists(p): + all_tensors.append(torch.load(p)) + try: os.remove(p) + except: pass - print(f"📦 [Stitcher] Ensamblando todos los lotes de vídeo...") - all_tensors = [] - for i in range(current_loop_index + 1): - p = os.path.join(cache_dir, f"batch_{i:04d}.pt") - if os.path.exists(p): - all_tensors.append(torch.load(p)) - try: os.remove(p) - except: pass + final_tensor = torch.cat(all_tensors, dim=0) + print(f"✅ [Stitcher] Vídeo completado: {final_tensor.shape[0]} frames ensamblados.") - final_images = torch.cat(all_tensors, dim=0) - print(f"✅ [Stitcher] Vídeo completado: {final_images.shape[0]} frames ensamblados.") + # 🚀 Retornamos True al final + return (final_tensor, audio, True) + else: + print(f" -> ⏳ Ciclo intermedio. Soltando 1 frame dummy...") + dummy_frame = images[-1:].clone() - return (final_images, audio) + # 🚀 Retornamos False al final + return (dummy_frame, None, False) @register_node class AutoLoopCalculatorWan: