From 0d794fc10ec4d8bae05382da8ef9c28e5db7fe52 Mon Sep 17 00:00:00 2001 From: Meisoftcoltd <38667978+Meisoftcoltd@users.noreply.github.com> Date: Sun, 12 Apr 2026 10:26:08 +0000 Subject: [PATCH] fix: decouple timeline from frame padding to prevent EOF crashes Replaced artificial safe limit in AutoLoopCalculatorWan (video.py) and AutoLoopCalculatorLTX (loop.py) with actual physical source frame limit. This prevents trigger nodes from falsely requesting frames past the EOF which resulted in "RuntimeError: No frames generated". --- loop.py | 35 +++++++++++++---------------------- video.py | 34 +++++++++++----------------------- 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/loop.py b/loop.py index 9867212..813c899 100644 --- a/loop.py +++ b/loop.py @@ -99,38 +99,33 @@ class AutoLoopCalculatorLTX: potential_effective_frames = source_frame_count // select_every_nth - # Truncar al (8n + 1) más cercano hacia ARRIBA (Acolchado) if potential_effective_frames < 9: - safe_effective_frames = 9 # Too short, bypass + safe_effective_frames = 9 else: safe_effective_frames = ((potential_effective_frames + 6) // 8) * 8 + 1 - safe_source_frame_count = safe_effective_frames * select_every_nth - loop.global_source_frame_count = safe_source_frame_count + # 🚀 FIX: Conservamos el límite FÍSICO real del vídeo para el timeline global + physical_source_frame_count = potential_effective_frames * select_every_nth + loop.global_source_frame_count = physical_source_frame_count effective_padding = safe_effective_frames - potential_effective_frames - # Ajuste proporcional forzando regla 8n + 1 + # Ajuste proporcional estimated_loops = math.ceil(safe_effective_frames / target_frames_per_loop) if estimated_loops > 0: optimal_target = math.ceil(safe_effective_frames / estimated_loops) - # Encontrar el 8n + 1 más cercano hacia arriba para cubrir el total adjusted_target = ((optimal_target - 1 + 7) // 8) * 8 + 1 target_frames_per_loop = adjusted_target print(f" -> ⚖️ Ajuste Proporcional LTX: Target recalculado a {adjusted_target} frames por ciclo.") - # --- LOGS MEJORADOS Y TRANSPARENTES --- print(f" -> 🎞️ Capacidad del video original: {potential_effective_frames} frames (Nth: {select_every_nth})") if effective_padding > 0: print(f" -> 🛡️ Ajuste VAE: Se pedirán {safe_effective_frames} frames (Acolchado técnico: Se rellenarán {effective_padding} frames)") else: print(f" -> ✅ Ajuste VAE: Perfecto. Regla 8n+1 detectada.") - print(f" -> 📊 Timeline final: 0 a {safe_source_frame_count} (de {source_frame_count} totales)") - # ------------------------------------------------------------- + print(f" -> 📊 Timeline final: 0 a {physical_source_frame_count} (Límite Físico Real)") loop.global_select_every_nth = select_every_nth - - # Indicador global para el Stitcher y Sender (LTX mode activo) global global_ltx_mode global_ltx_mode = True @@ -138,19 +133,18 @@ class AutoLoopCalculatorLTX: print(f"\n{'='*50}") print(f"📊 [DEBUG] NODO: Auto Loop Calculator (LTX 2.3)") - print(f" -> Timeline seguro ajustado a LTX: {current_pos} / {safe_source_frame_count} (Original: {source_frame_count})") + print(f" -> Timeline físico ajustado a LTX: {current_pos} / {physical_source_frame_count} (Original: {source_frame_count})") - if current_pos >= safe_source_frame_count: + if current_pos >= physical_source_frame_count: return (1, current_pos, select_every_nth) - frames_left = safe_source_frame_count - current_pos + frames_left = physical_source_frame_count - current_pos equitable_target = target_frames_per_loop * select_every_nth - ideal_cut = current_pos + equitable_target if frames_left <= equitable_target: - best_cut = safe_source_frame_count + best_cut = physical_source_frame_count print(f" -> 🧮 Absorbiendo resto final: meta fijada en frame {best_cut}") else: best_cut = ideal_cut @@ -159,18 +153,15 @@ class AutoLoopCalculatorLTX: best_cut = closest_face print(f" -> ✂️ Corte Inteligente proyectado: {best_cut} (Meta equitativa: {ideal_cut})") else: - print(f" -> ⚖️ Sin caras detectadas. Forzando corte equitativo: {ideal_cut}") + print(f" -> ⚖️ Sin caras. Forzando corte equitativo: {ideal_cut}") effective_chunk_frames = math.ceil((best_cut - current_pos) / select_every_nth) - - # Redondear siempre hacia ARRIBA al (8n + 1) más cercano effective_chunk_frames = ((effective_chunk_frames + 6) // 8) * 8 + 1 if effective_chunk_frames < 9: - effective_chunk_frames = 9 # Mínimo vital LTX + effective_chunk_frames = 9 - # ELIMINAMOS el recorte final que rompe la regla 8n + 1 - if current_pos + (effective_chunk_frames * select_every_nth) >= safe_source_frame_count: + if current_pos + (effective_chunk_frames * select_every_nth) >= physical_source_frame_count: print(f" -> 🏁 Chunk final LTX detectado. Ajustando a {effective_chunk_frames} frames para mantener regla 8n+1.") skip_frames = current_pos diff --git a/video.py b/video.py index 67139d5..53fa7fb 100644 --- a/video.py +++ b/video.py @@ -404,8 +404,9 @@ class AutoLoopCalculatorWan: potential_effective_frames = source_frame_count // select_every_nth safe_effective_frames = ((potential_effective_frames + 3) // 4) * 4 - safe_source_frame_count = safe_effective_frames * select_every_nth - loop.global_source_frame_count = safe_source_frame_count + # 🚀 FIX: Conservamos el límite FÍSICO real del vídeo para el timeline global + physical_source_frame_count = potential_effective_frames * select_every_nth + loop.global_source_frame_count = physical_source_frame_count effective_padding = safe_effective_frames - potential_effective_frames loop.global_select_every_nth = select_every_nth @@ -413,50 +414,42 @@ class AutoLoopCalculatorWan: # 2. Ajuste Proporcional Real (Evitar micro-ciclos al final) estimated_loops = math.ceil(safe_effective_frames / target_frames_per_loop) if estimated_loops > 0: - # Repartir los frames equitativamente optimal_target = math.ceil(safe_effective_frames / estimated_loops) - # Asegurar que el nuevo target sea múltiplo de 4 (redondeando hacia arriba por seguridad) adjusted_target = ((optimal_target + 3) // 4) * 4 - print(f" -> ⚖️ Ajuste Proporcional: Target recalculado de {target_frames_per_loop} a {adjusted_target} frames por ciclo (para {estimated_loops} ciclos)") target_frames_per_loop = adjusted_target - # --- LOGS MEJORADOS Y TRANSPARENTES --- print(f" -> 🎞️ Capacidad del video original: {potential_effective_frames} frames (Nth: {select_every_nth})") if effective_padding > 0: print(f" -> 🛡️ Ajuste VAE: Se pedirán {safe_effective_frames} frames (Acolchado técnico: Se rellenarán {effective_padding} frames)") else: print(f" -> ✅ Ajuste VAE: Perfecto. Múltiplo de 4 detectado.") - print(f" -> 📊 Timeline final: 0 a {safe_source_frame_count} (de {source_frame_count} totales)") - # ------------------------------------------------------------- + print(f" -> 📊 Timeline final: 0 a {physical_source_frame_count} (Límite Físico Real)") current_pos = getattr(loop, 'global_accumulated_frames', 0) print(f"\n{'='*50}") print(f"📊 [DEBUG] NODO: Auto Loop Calculator (WanVideo 3D VAE)") - print(f" -> Timeline seguro ajustado a múltiplos de 4: {current_pos} / {safe_source_frame_count} (Original: {source_frame_count})") + print(f" -> Timeline físico ajustado: {current_pos} / {physical_source_frame_count} (Original: {source_frame_count})") - # Prevención de desbordamiento de bucle - if current_pos >= safe_source_frame_count: + # Prevención de desbordamiento de bucle (Usando límite físico) + if current_pos >= physical_source_frame_count: return (4, current_pos, select_every_nth) - frames_left = safe_source_frame_count - current_pos + frames_left = physical_source_frame_count - current_pos equitable_target = target_frames_per_loop * select_every_nth - ideal_cut = current_pos + equitable_target if frames_left <= equitable_target: - best_cut = safe_source_frame_count + best_cut = physical_source_frame_count print(f" -> 🧮 Absorbiendo resto final seguro: meta fijada en frame {best_cut}") else: best_cut = ideal_cut if safe_faces_list and len(safe_faces_list) > 0: closest_face = min(safe_faces_list, key=lambda x: abs(x - ideal_cut)) - # Distancia al corte de cara chunk_from_face = math.ceil((closest_face - current_pos) / select_every_nth) - # Blindaje matemático x4 para el corte de cara safe_chunk_from_face = (chunk_from_face // 4) * 4 if safe_chunk_from_face < 4: safe_chunk_from_face = 4 @@ -465,19 +458,14 @@ class AutoLoopCalculatorWan: else: print(f" -> ⚖️ Sin caras. Forzando corte equitativo x4: {ideal_cut}") - # 4. Cálculo final del chunk consolidado effective_chunk_frames = math.ceil((best_cut - current_pos) / select_every_nth) - - # Redondear siempre hacia ARRIBA al múltiplo de 4 más cercano para evitar chunks inválidos effective_chunk_frames = ((effective_chunk_frames + 3) // 4) * 4 if effective_chunk_frames < 4: effective_chunk_frames = 4 - # ELIMINAMOS el recorte final que rompe el múltiplo de 4. - # Si pedimos más frames de los que quedan, VHS duplicará el último frame automáticamente, - # salvando así el renderizado sin crashear el 3D VAE. - if current_pos + (effective_chunk_frames * select_every_nth) >= safe_source_frame_count: + # Comprobación contra el límite físico para evitar pedir más allá del final + if current_pos + (effective_chunk_frames * select_every_nth) >= physical_source_frame_count: print(f" -> 🏁 Chunk final detectado. Ajustando a {effective_chunk_frames} frames para mantener múltiplo de 4.") skip_frames = current_pos