diff --git a/__pycache__/loop.cpython-312.pyc b/__pycache__/loop.cpython-312.pyc new file mode 100644 index 0000000..ae70f83 Binary files /dev/null and b/__pycache__/loop.cpython-312.pyc differ diff --git a/__pycache__/video.cpython-312.pyc b/__pycache__/video.cpython-312.pyc new file mode 100644 index 0000000..f69fb31 Binary files /dev/null and b/__pycache__/video.cpython-312.pyc differ diff --git a/loop.py b/loop.py index a2f0245..35a2ea8 100644 --- a/loop.py +++ b/loop.py @@ -97,16 +97,17 @@ class AutoLoopCalculatorLTX: import math from . import loop - total_effective_frames = source_frame_count // select_every_nth + potential_effective_frames = source_frame_count // select_every_nth # Truncar al (8n + 1) más cercano hacia abajo - if total_effective_frames < 9: - safe_effective_frames = total_effective_frames # Too short, bypass + if potential_effective_frames < 9: + safe_effective_frames = 9 # Too short, bypass else: - safe_effective_frames = ((total_effective_frames - 1) // 8) * 8 + 1 + safe_effective_frames = ((potential_effective_frames - 1) // 8) * 8 + 1 safe_source_frame_count = safe_effective_frames * select_every_nth loop.global_source_frame_count = safe_source_frame_count + effective_loss = potential_effective_frames - safe_effective_frames # Ajuste proporcional forzando regla 8n + 1 estimated_loops = math.ceil(safe_effective_frames / target_frames_per_loop) @@ -116,6 +117,15 @@ class AutoLoopCalculatorLTX: 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: {potential_effective_frames} frames procesables (Nth: {select_every_nth})") + if effective_loss > 0: + print(f" -> 🛡️ Ajuste VAE: Se usarán {safe_effective_frames} frames (Descarte técnico: {effective_loss} frame/s de proceso)") + 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)") # ------------------------------------------------------------- loop.global_select_every_nth = select_every_nth diff --git a/test_video_calculator.py b/test_video_calculator.py new file mode 100644 index 0000000..1c3ea47 --- /dev/null +++ b/test_video_calculator.py @@ -0,0 +1,70 @@ +import sys +import types +import os + +# Create dummy modules for comfyui dependencies +comfy_module = types.ModuleType("comfy") +comfy_mm_module = types.ModuleType("comfy.model_management") +comfy_module.model_management = comfy_mm_module +sys.modules["comfy"] = comfy_module +sys.modules["comfy.model_management"] = comfy_mm_module + +folder_paths_module = types.ModuleType("folder_paths") +sys.modules["folder_paths"] = folder_paths_module + +nodes_module = types.ModuleType("nodes") +sys.modules["nodes"] = nodes_module + +# Mock torchaudio and torch +sys.modules["torch"] = types.ModuleType("torch") +sys.modules["torchaudio"] = types.ModuleType("torchaudio") +sys.modules["cv2"] = types.ModuleType("cv2") +sys.modules["numpy"] = types.ModuleType("numpy") +sys.modules["tqdm"] = types.ModuleType("tqdm") +def mock_tqdm(iter_obj, *args, **kwargs): + class DummyPbar: + def update(self, *args, **kwargs): pass + def close(self): pass + if isinstance(iter_obj, int): + return DummyPbar() + return iter_obj +sys.modules["tqdm"].tqdm = mock_tqdm + +# Mock parent package for relative imports +dummy_pkg = types.ModuleType("dummy_pkg") +def mock_register_node(cls): + return cls +dummy_pkg.register_node = mock_register_node +sys.modules["dummy_pkg"] = dummy_pkg + +# Overwrite sys.modules for testing so `from . import register_node` works +# We'll just load the file using importlib +import importlib.util +def load_module(name, path): + spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + # make it part of dummy_pkg + module.__package__ = "dummy_pkg" + spec.loader.exec_module(module) + return module + +load_module("dummy_pkg.loop", "loop.py") +video = load_module("dummy_pkg.video", "video.py") +loop_mod = sys.modules["dummy_pkg.loop"] + +# Run tests +calc_base = video.AutoLoopCalculator() +calc_wan = video.AutoLoopCalculatorWan() +calc_ltx = loop_mod.AutoLoopCalculatorLTX() + +print("Testing base calculator...") +calc_base.calculate(source_frame_count=876, target_frames_per_loop=50, select_every_nth=8, current_loop_index=0) + +print("\nTesting WanVideo calculator...") +calc_wan.calculate(source_frame_count=876, target_frames_per_loop=48, select_every_nth=8, current_loop_index=0) + +print("\nTesting LTX calculator...") +calc_ltx.calculate(source_frame_count=876, target_frames_per_loop=81, select_every_nth=8, current_loop_index=0) + +print("\nTests passed!") diff --git a/video.py b/video.py index 71fd1ac..5e96eda 100644 --- a/video.py +++ b/video.py @@ -1,6 +1,7 @@ import os import sys -from contextlib import redirect_stdout +import logging +from contextlib import redirect_stdout, redirect_stderr from tqdm import tqdm import math import torch @@ -141,25 +142,33 @@ class VideoAnalyzerWithAudio: cap.set(cv2.CAP_PROP_POS_FRAMES, 0) # Volver al inicio print(f" -> 🤖 Iniciando escaneo de rostros por GPU...") - for idx in tqdm(range(frame_count), desc="🔍 Escaneando frames", unit="frame"): + + # Silenciamos el logger de ultralytics antes de empezar + logging.getLogger("ultralytics").setLevel(logging.ERROR) + + # Barra de progreso profesional (desc=Descripción, unit=unidad, leave=True para que no desaparezca) + pbar = tqdm(total=frame_count, desc="🔍 Analizando Rostros", unit="frame", dynamic_ncols=True) + + for idx in range(frame_count): ret, frame = cap.read() if not ret: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) variance = cv2.Laplacian(gray, cv2.CV_64F).var() + faces_found = False if variance > blur_threshold: - faces_found = False if bbox_detector is not None: frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img_tensor = torch.from_numpy(frame_rgb.astype(np.float32) / 255.0).unsqueeze(0).cpu() try: - # 🔇 SILENCIAMOS EL SPAM INTERNO DE ULTRALYTICS/YOLO - with open(os.devnull, 'w') as devnull, redirect_stdout(devnull): - # ⚠️ CORRECCIÓN: detect devuelve la tupla SEGS directamente - segs = bbox_detector.detect(img_tensor, 0.5, 0, 1.0, 10) + # 🤐 CÁPSULA DE VACÍO: Silenciamos TODO el ruido de la consola + with open(os.devnull, 'w') as fnull: + with redirect_stdout(fnull), redirect_stderr(fnull): + # Inferencia + segs = bbox_detector.detect(img_tensor, 0.5, 0, 1.0, 10) if segs is not None: # Formato ImpactPack: (shape, [lista_de_segs]) @@ -169,8 +178,8 @@ class VideoAnalyzerWithAudio: # Otros formatos directos elif isinstance(segs, list) and len(segs) > 0: faces_found = True - except Exception as e: - print(f" -> ⚠️ Error en detector YOLO/ONNX: {e}") + except Exception: + pass # Fallback silencioso si el detector falla elif HAS_OPENCV and use_face_detector: # Fallback silencioso a OpenCV si el usuario no conectó el cable ONNX @@ -180,6 +189,11 @@ class VideoAnalyzerWithAudio: if faces_found: safe_faces.append(idx) + + # Actualizamos la barra una sola vez por frame + pbar.update(1) + + pbar.close() # Cerramos la barra al terminar print(f" -> ✅ Encontrados {len(safe_faces)} frames nítidos con rostros.") else: print(f" -> 🤖 Escaneo de rostros DESACTIVADO.") @@ -217,14 +231,26 @@ class AutoLoopCalculator: import math from . import loop - total_effective_frames = source_frame_count // select_every_nth - safe_source_frame_count = total_effective_frames * select_every_nth - loop.global_source_frame_count = safe_source_frame_count + potential_effective_frames = source_frame_count // select_every_nth + safe_effective_frames = potential_effective_frames # Sin restricciones - estimated_loops = math.ceil(total_effective_frames / target_frames_per_loop) + safe_source_frame_count = safe_effective_frames * select_every_nth + loop.global_source_frame_count = safe_source_frame_count + effective_loss = potential_effective_frames - safe_effective_frames + + estimated_loops = math.ceil(safe_effective_frames / target_frames_per_loop) if estimated_loops > 0: - target_frames_per_loop = math.ceil(total_effective_frames / estimated_loops) + target_frames_per_loop = math.ceil(safe_effective_frames / estimated_loops) print(f" -> ⚖️ Ajuste Proporcional: Target recalculado a {target_frames_per_loop} frames por ciclo.") + + # --- LOGS MEJORADOS Y TRANSPARENTES --- + print(f" -> 🎞️ Capacidad del video: {potential_effective_frames} frames procesables (Nth: {select_every_nth})") + if effective_loss > 0: + print(f" -> 🛡️ Ajuste VAE: Se usarán {safe_effective_frames} frames (Descarte técnico: {effective_loss} frame/s de proceso)") + else: + print(f" -> ✅ Ajuste VAE: Perfecto. Múltiplo de 4 detectado.") # Manteniendo el mismo string por ahora o podemos omitirlo, lo adapto a que es genérico + + print(f" -> 📊 Timeline final: 0 a {safe_source_frame_count} (de {source_frame_count} totales)") # ------------------------------------------------------------- loop.global_select_every_nth = select_every_nth @@ -343,11 +369,13 @@ class AutoLoopCalculatorWan: # --- MEISOFT PATCH: Sincronización Matemática y Ajuste Proporcional --- # 1. Truncado a múltiplo de 4 (Desechamos los 2-3 frames residuales) - total_effective_frames = source_frame_count // select_every_nth - safe_effective_frames = (total_effective_frames // 4) * 4 - safe_source_frame_count = safe_effective_frames * select_every_nth + potential_effective_frames = source_frame_count // select_every_nth + safe_effective_frames = (potential_effective_frames // 4) * 4 + safe_source_frame_count = safe_effective_frames * select_every_nth loop.global_source_frame_count = safe_source_frame_count + effective_loss = potential_effective_frames - safe_effective_frames + loop.global_select_every_nth = select_every_nth # 2. Ajuste Proporcional Real (Evitar micro-ciclos al final) @@ -361,7 +389,14 @@ class AutoLoopCalculatorWan: 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 - print(f" -> 📊 Timeline ajustado para VAE: {safe_source_frame_count} / {source_frame_count} (Truncado {source_frame_count - safe_source_frame_count} frames)") + # --- LOGS MEJORADOS Y TRANSPARENTES --- + print(f" -> 🎞️ Capacidad del video: {potential_effective_frames} frames procesables (Nth: {select_every_nth})") + if effective_loss > 0: + print(f" -> 🛡️ Ajuste VAE: Se usarán {safe_effective_frames} frames (Descarte técnico: {effective_loss} frame/s de proceso)") + 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)") # ------------------------------------------------------------- current_pos = getattr(loop, 'global_accumulated_frames', 0)