diff --git a/loop.py b/loop.py index e61790f..446d364 100644 --- a/loop.py +++ b/loop.py @@ -95,6 +95,12 @@ class AutoLoopCalculatorLTX: CATEGORY = "🔁 Sequential Batcher/Loop" def calculate(self, source_frame_count, target_frames_per_loop, select_every_nth, current_loop_index, safe_faces_list=None, scene_cuts_list=None): + if source_frame_count <= 0 or target_frames_per_loop <= 0: + return (1, 0, select_every_nth) + + source_frame_count = min(source_frame_count, 10000) + select_every_nth = max(1, select_every_nth) + # --- Ajuste Proporcional LTX (Regla DiT 8n + 1) --- import math from . import loop diff --git a/tests/test_loop_calculator.py b/tests/test_loop_calculator.py new file mode 100644 index 0000000..544402d --- /dev/null +++ b/tests/test_loop_calculator.py @@ -0,0 +1,136 @@ +import sys +import types +import unittest +from unittest.mock import MagicMock +import os + +# Mock required modules (as per memory) +mock_torch = types.ModuleType("torch") +mock_torch.cuda = MagicMock() +mock_torch.cuda.is_available.return_value = False +sys.modules["torch"] = mock_torch + +sys.modules["comfy"] = types.ModuleType("comfy") +sys.modules["comfy.model_management"] = types.ModuleType("comfy.model_management") +sys.modules["comfy.utils"] = types.ModuleType("comfy.utils") +sys.modules["torchaudio"] = types.ModuleType("torchaudio") +sys.modules["folder_paths"] = types.ModuleType("folder_paths") + + + +# Mock the loop module since it will be imported in video.py +mock_loop = types.ModuleType("loop") +mock_loop.global_step_by_chunk = False +mock_loop.global_source_frame_count = 0 +mock_loop.global_select_every_nth = 1 +mock_loop.global_is_final_chunk = False +mock_loop.global_accumulated_frames = 0 +sys.modules["loop"] = mock_loop + +import sys +sys.modules["tqdm"] = types.ModuleType("tqdm") +sys.modules["tqdm"].tqdm = lambda x, **kwargs: x +sys.modules["cv2"] = types.ModuleType("cv2") + + +def get_node_class(file_name, class_name): + # Read the file + path = os.path.join(os.path.dirname(__file__), "..", file_name) + with open(path, 'r') as f: + code = f.read() + + # Mock 'from . import register_node' + code = code.replace("from . import register_node", "def register_node(c, *args, **kwargs): return c") + # In video.py, there are more relative imports + code = code.replace("from . import loop", "import loop") + + # We create a new module + module = types.ModuleType(file_name.replace(".py", "")) + + # We must patch sys.modules inside so it can import loop + sys.modules[module.__name__] = module + + try: + exec(code, module.__dict__) + except Exception as e: + print(f"Failed to execute {file_name}: {e}") + pass + + return getattr(module, class_name, None) + + +class TestAutoLoopCalculators(unittest.TestCase): + + def setUp(self): + LTXClass = get_node_class("loop.py", "AutoLoopCalculatorLTX") + self.calc_ltx = LTXClass() if LTXClass else None + + BaseClass = get_node_class("video.py", "AutoLoopCalculator") + self.calc_base = BaseClass() if BaseClass else None + + WanClass = get_node_class("video.py", "AutoLoopCalculatorWan") + self.calc_wan = WanClass() if WanClass else None + + # LTX Calculator + def test_ltx_guard_zero_source(self): + res = self.calc_ltx.calculate(0, 100, 1, 0) + self.assertEqual(res, (1, 0, 1)) + + def test_ltx_guard_negative_source(self): + res = self.calc_ltx.calculate(-5, 100, 1, 0) + self.assertEqual(res, (1, 0, 1)) + + def test_ltx_guard_zero_target(self): + res = self.calc_ltx.calculate(100, 0, 1, 0) + self.assertEqual(res, (1, 0, 1)) + + def test_ltx_valid_input(self): + res = self.calc_ltx.calculate(80, 81, 1, 0) + self.assertEqual(res[0], 81) + self.assertEqual(res[1], 0) + self.assertEqual(res[2], 1) + + # Base Calculator + def test_base_guard_zero_source(self): + res = self.calc_base.calculate(0, 100, 1, 0) + self.assertEqual(res[0], 1) + self.assertEqual(res[1], 0) + self.assertEqual(res[2], 1) + self.assertIn("Invalid frame count", res[3]) + + def test_base_guard_zero_target(self): + res = self.calc_base.calculate(100, 0, 1, 0) + self.assertEqual(res[0], 1) + self.assertEqual(res[1], 0) + self.assertEqual(res[2], 1) + self.assertIn("Invalid frame count", res[3]) + + def test_base_valid_input(self): + res = self.calc_base.calculate(100, 50, 1, 0) + self.assertEqual(res[0], 50) + self.assertEqual(res[1], 0) + self.assertEqual(res[2], 1) + + # Wan Calculator + def test_wan_guard_zero_source(self): + res = self.calc_wan.calculate(0, 100, 1, 0) + self.assertEqual(res[0], 1) + self.assertEqual(res[1], 0) + self.assertEqual(res[2], 1) + self.assertIn("Invalid frame count", res[3]) + + def test_wan_guard_zero_target(self): + res = self.calc_wan.calculate(100, 0, 1, 0) + self.assertEqual(res[0], 1) + self.assertEqual(res[1], 0) + self.assertEqual(res[2], 1) + self.assertIn("Invalid frame count", res[3]) + + def test_wan_valid_input(self): + res = self.calc_wan.calculate(80, 81, 1, 0) + self.assertEqual(res[0], 81) + self.assertEqual(res[1], 0) + self.assertEqual(res[2], 1) + +if __name__ == "__main__": + unittest.main() diff --git a/video.py b/video.py index 6a55534..4f46b2b 100644 --- a/video.py +++ b/video.py @@ -484,6 +484,14 @@ class AutoLoopCalculator: def _log(msg): print(msg) log_output.append(str(msg)) + + if source_frame_count <= 0 or target_frames_per_loop <= 0: + _log("⚠️ [WARNING] Invalid frame count or target frames per loop.") + return (1, 0, select_every_nth, "\n".join(log_output)) + + source_frame_count = min(source_frame_count, 10000) + select_every_nth = max(1, select_every_nth) + # --- Ajuste Proporcional Base (Sin restricciones VAE) --- import math from . import loop @@ -807,6 +815,14 @@ class AutoLoopCalculatorWan: def _log(msg): print(msg) log_output.append(str(msg)) + + if source_frame_count <= 0 or target_frames_per_loop <= 0: + _log("⚠️ [WARNING] Invalid frame count or target frames per loop.") + return (1, 0, select_every_nth, "\n".join(log_output)) + + source_frame_count = min(source_frame_count, 10000) + select_every_nth = max(1, select_every_nth) + import math from . import loop