refactor(core): remove is_flux_phase routing logic and experimental gates for split-workflow architecture

Removed phase routing from DynamicSceneDirector, IncrementalVideoStitcher, and SaveSceneKeyframe.
Deleted Meisoft_LazyGate and removed references from __init__.py.
This commit is contained in:
Meisoftcoltd
2026-06-29 19:37:20 +00:00
parent fcbaeb00d9
commit fccd913fba
3 changed files with 28 additions and 88 deletions
+1 -6
View File
@@ -48,12 +48,7 @@ NODE_DISPLAY_NAME_MAPPINGS.update({
"PreciseAudioSlicer": "✂️ Precise Audio Slicer",
"SaveSceneKeyframe": "💾 Save Scene Keyframe",
"LoadSceneKeyframe": "🖼️ Load Scene Keyframe",
"LTXVSingleFrameInjector": "💉 LTXV Single Frame Injector",
"Meisoft_LazyGate": "🚦 Meisoft Lazy Gate",
})
NODE_CLASS_MAPPINGS.update({
"Meisoft_LazyGate": tools.Meisoft_LazyGate,
"LTXVSingleFrameInjector": "💉 LTXV Single Frame Injector"
})
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
+26 -41
View File
@@ -65,7 +65,7 @@ class SequentialLoopStart:
print(f" -> ⚠️ ATENCIÓN: El puerto de entrada ({port}) no coincide con el puerto del sistema ({system_port}).")
print(f" -> 🔒 Por motivos de seguridad (Prevención SSRF), se forzará el puerto real del sistema: {system_port}.")
safe_port = system_port
else:
safe_port = system_port
print(f" -> 📡 Verificando conexión con ComfyUI en el puerto seguro {safe_port}...")
@@ -81,7 +81,7 @@ class SequentialLoopStart:
if attempt < max_retries - 1:
print(f" -> ⏳ Reintentando conexión ({attempt + 1}/{max_retries}) por alta carga del servidor...")
time.sleep(1)
else:
error_msg = f"🚨 FATAL ERROR: EL PUERTO {safe_port} NO RESPONDE TRAS VARIOS INTENTOS. El servidor ComfyUI podría estar bloqueado o apagado. (Error original: {e})"
print(f" -> ❌ {error_msg}")
raise RuntimeError(error_msg)
@@ -101,7 +101,7 @@ class SequentialLoopStart:
global_ltx_mode = False
print(" -> 🔄 Bucle y Acumulador reiniciados a 0.")
# HACK: Mantenemos global_is_batch_advancing = True durante el ciclo 0
else:
global_loop_index = loop_idx
# Lo apagamos a partir del ciclo 1
global_is_batch_advancing = False
@@ -191,7 +191,7 @@ class AutoLoopCalculatorTTSBatch:
if raw_chunks: raw_chunks[-1] += " " + buffer_text
else: raw_chunks.append(buffer_text)
chunk_type_name = "Frases Optimizadas"
else:
raw_chunks = [p.strip() for p in current_file_text.split('\n') if p.strip()]
chunk_type_name = "Párrafos"
@@ -263,7 +263,7 @@ class AutoLoopCalculatorLTX:
if potential_effective_frames < 9:
safe_effective_frames = 9
else:
safe_effective_frames = ((potential_effective_frames + 6) // 8) * 8 + 1
# 🚀 FIX: Conservamos el límite FÍSICO real del vídeo para el timeline global
@@ -282,7 +282,7 @@ class AutoLoopCalculatorLTX:
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 {physical_source_frame_count} (Límite Físico Real)")
@@ -308,7 +308,7 @@ class AutoLoopCalculatorLTX:
if frames_left <= equitable_target:
best_cut = physical_source_frame_count
print(f" -> 🧮 Absorbiendo resto final: meta fijada en frame {best_cut}")
else:
if scene_cuts_list and len(scene_cuts_list) > 0:
future_cuts = [c for c in scene_cuts_list if c > current_pos]
if future_cuts:
@@ -319,10 +319,10 @@ class AutoLoopCalculatorLTX:
if safe_chunk_from_scene < 9: safe_chunk_from_scene = 9
best_cut = current_pos + (safe_chunk_from_scene * select_every_nth)
print(f" -> 🎬 Bucle aislado por escena (Ajustado x8). Cortando en el frame: {best_cut} (Corte real: {next_cut})")
else:
best_cut = ideal_cut
print(f" -> ⚠️ Toma demasiado larga para la VRAM. Cortando por límite técnico en: {best_cut} (El plano real acaba en {next_cut})")
else:
best_cut = ideal_cut
print(f" -> 🎬 No quedan cortes de cámara por delante. Forzando corte final por VRAM en: {ideal_cut}")
elif safe_faces_list and len(safe_faces_list) > 0:
@@ -332,7 +332,7 @@ class AutoLoopCalculatorLTX:
if safe_chunk_from_face < 9: safe_chunk_from_face = 9
best_cut = current_pos + (safe_chunk_from_face * select_every_nth)
print(f" -> ✂️ Corte Facial Inteligente (Ajustado x8): {best_cut} (Meta equitativa: {ideal_cut})")
else:
best_cut = ideal_cut
print(f" -> ⚖️ Sin detectores conectados. Forzando corte equitativo x8: {ideal_cut}")
@@ -345,7 +345,7 @@ class AutoLoopCalculatorLTX:
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.")
loop.global_is_final_chunk = True
else:
loop.global_is_final_chunk = False
skip_frames = current_pos
@@ -407,7 +407,7 @@ class SequentialLoopTrigger:
if not found_calculator and "AutoLoopCalculator" in class_type:
if "TTS" in class_type:
estimated_chunk = 1
else:
estimated_chunk = inputs.get("target_frames_per_loop", 50)
found_calculator = True
@@ -420,7 +420,7 @@ class SequentialLoopTrigger:
if is_final_chunk and has_more_batches:
inputs["loop_idx"] = 0
inputs["reset_loop"] = True
else:
inputs["loop_idx"] = next_loop
inputs["reset_loop"] = False
@@ -448,7 +448,7 @@ class SequentialLoopTrigger:
loop.global_batch_index = getattr(loop, 'global_batch_index', 0) + 1
loop.global_is_batch_advancing = True
print(f" -> 📦 Archivo finalizado. Iniciando archivo {loop.global_batch_index + 1} del lote...")
else:
print(f" -> ⚙️ Preparando Ciclo {next_loop}...")
p = {"prompt": prompt}
@@ -465,7 +465,7 @@ class SequentialLoopTrigger:
gc.collect()
if torch.cuda.is_available(): torch.cuda.empty_cache()
else:
print(f" -> 🏁 ¡Generación Finalizada! Todos los archivos del lote completados.")
# ... Mantener aquí el código existente de limpieza extrema de VRAM (mm.unload_all_models, malloc_trim, etc.) ...
print(f" -> 🧹 Iniciando vaciado automático de VRAM...")
@@ -640,8 +640,8 @@ class DynamicSceneDirector:
}
}
RETURN_TYPES = ("BOOLEAN", "STRING", "STRING", "INT", "STRING")
RETURN_NAMES = ("is_flux_phase", "flux_prompt", "wan_prompt", "chunk_frames", "image_load_path")
RETURN_TYPES = ("STRING", "STRING", "INT", "STRING")
RETURN_NAMES = ("flux_prompt", "wan_prompt", "chunk_frames", "image_load_path")
FUNCTION = "direct_scene"
CATEGORY = "🔁 Sequential Batcher/Director"
@@ -670,19 +670,16 @@ class DynamicSceneDirector:
loop.global_current_scene_index = 0
loop.global_accumulated_frames = 0
loop.global_audio_offset_frames = 0
loop.global_is_flux_phase = True
loop.global_is_final_chunk = False
_log(" -> 🆕 Inicio de Proyecto. Estado reseteado.")
elif getattr(loop, 'global_is_final_chunk', False) and not getattr(loop, 'global_is_flux_phase', False):
elif getattr(loop, 'global_is_final_chunk', False):
loop.global_audio_offset_frames = getattr(loop, 'global_audio_offset_frames', 0) + getattr(loop, 'global_source_frame_count', 0)
loop.global_current_scene_index = getattr(loop, 'global_current_scene_index', 0) + 1
loop.global_accumulated_frames = 0
loop.global_is_flux_phase = True
loop.global_is_final_chunk = False
_log(" -> ⏭️ Escena terminada. Avanzando a la siguiente escena del guion.")
else:
loop.global_is_flux_phase = False
scene_idx = getattr(loop, 'global_current_scene_index', 0)
@@ -693,7 +690,6 @@ class DynamicSceneDirector:
loop.global_is_absolute_video_final = False
scene = scenes[scene_idx]
is_flux_phase = loop.global_is_flux_phase
base_output = folder_paths.get_temp_directory()
safe_audio_name = "".join(c for c in audio_filename if c.isalnum() or c in " _-").strip() or "proyecto"
@@ -702,20 +698,17 @@ class DynamicSceneDirector:
image_path = os.path.join(keyframes_dir, f"scene_{scene.get('scene_id', scene_idx)}.png")
flux_prompt = scene.get("flux_prompt", "") if is_flux_phase else ""
wan_prompt = scene.get("wan_prompt", "") if not is_flux_phase else ""
flux_prompt = scene.get("flux_prompt", "")
wan_prompt = scene.get("wan_prompt", "")
duration = scene.get("duration_seconds", 5.0)
chunk_frames = math.ceil(duration * fps)
loop.global_source_frame_count = chunk_frames
if is_flux_phase:
_log(f" -> 📸 FASE 1 (FLUX): Keyframe para Escena {scene_idx + 1}/{total_scenes}")
else:
_log(f" -> 🎥 FASE 2 (LTX): Renderizando Escena {scene_idx + 1}/{total_scenes} ({duration}s)")
_log(f" -> 🎬 Procesando Escena {scene_idx + 1}/{total_scenes} ({duration}s)")
_log(f"{'='*50}\n")
return (is_flux_phase, flux_prompt, wan_prompt, chunk_frames, image_path)
return (flux_prompt, wan_prompt, chunk_frames, image_path)
@register_node
class IncrementalVideoStitcher:
@@ -726,9 +719,6 @@ class IncrementalVideoStitcher:
"images": ("IMAGE",),
"audio": ("AUDIO",),
"current_loop_index": ("INT", {"default": 0}),
},
"optional": {
"is_flux_phase": ("BOOLEAN", {"forceInput": True, "default": False}),
}
}
@@ -741,12 +731,10 @@ class IncrementalVideoStitcher:
def IS_CHANGED(cls, **kwargs):
import time; return time.time()
def stitch(self, images, audio, current_loop_index, is_flux_phase=False):
def stitch(self, images, audio, current_loop_index):
log_output = []
def _log(msg): print(msg); log_output.append(str(msg))
# 🛡️ FIX: Asegurar que is_flux_phase sea un booleano real
is_flux_bool = str(is_flux_phase).strip().lower() in ["true", "1", "t", "yes", "y"]
from . import loop
import os, folder_paths, torch, shutil, time
from PIL import Image
@@ -763,9 +751,6 @@ class IncrementalVideoStitcher:
else:
os.makedirs(cache_dir, exist_ok=True)
if is_flux_bool:
_log(" -> 📸 Fase 1 activa. Ignorando imagen para la caché de vídeo.")
return (images[-1:].clone(), audio, False, False, "\n".join(log_output))
timestamp = int(time.time() * 1000)
@@ -814,12 +799,12 @@ class IncrementalVideoStitcher:
waveforms.append(chunk_audio["waveform"])
sample_rate = chunk_audio["sample_rate"].item()
final_audio = {"waveform": torch.cat(waveforms, dim=-1), "sample_rate": sample_rate}
else:
final_audio = audio
try: shutil.rmtree(cache_dir)
except: pass
return (final_tensor, final_audio, True, not has_more_batches, "\n".join(log_output))
else:
return (images[-1:].clone(), None, False, False, "\n".join(log_output))
+1 -41
View File
@@ -453,9 +453,6 @@ class SaveSceneKeyframe:
"required": {
"image": ("IMAGE",),
"file_path": ("STRING", {"forceInput": True}),
},
"optional": {
"is_flux_phase": ("BOOLEAN", {"forceInput": True, "default": True})
}
}
RETURN_TYPES = ("IMAGE", "STRING")
@@ -464,12 +461,7 @@ class SaveSceneKeyframe:
FUNCTION = "save"
CATEGORY = "🔁 Sequential Batcher/Tools"
def save(self, image, file_path, is_flux_phase=True):
# 🛡️ FIX: Asegurar que is_flux_phase sea un booleano real
is_flux_bool = str(is_flux_phase).strip().lower() in ["true", "1", "t", "yes", "y"]
if not is_flux_bool:
return (image, file_path) # Bypass silencioso en Fase 2
def save(self, image, file_path):
import os, numpy as np
from PIL import Image
@@ -582,35 +574,3 @@ class LTXVSingleFrameInjector:
return ({"samples": samples, "noise_mask": mask},)
class Meisoft_LazyGate:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"is_flux_phase": ("BOOLEAN", {"forceInput": True, "tooltip": "Conectar al Director"}),
},
"optional": {
"flux_branch": (any_type,),
"ltx_branch": (any_type,),
}
}
RETURN_TYPES = (any_type,)
RETURN_NAMES = ("active_branch",)
FUNCTION = "route"
CATEGORY = "🔁 Sequential Batcher/Logic"
# 🛡️ LA MAGIA: ComfyUI solo evaluará la rama que devolvamos aquí. La otra se cancela.
def check_lazy_status(self, is_flux_phase, flux_branch=None, ltx_branch=None):
is_flux = str(is_flux_phase).strip().lower() in ["true", "1", "t", "yes", "y"]
if is_flux:
return ["flux_branch"]
else:
return ["ltx_branch"]
def route(self, is_flux_phase, flux_branch=None, ltx_branch=None):
is_flux = str(is_flux_phase).strip().lower() in ["true", "1", "t", "yes", "y"]
if is_flux:
return (flux_branch,)
else:
return (ltx_branch,)