commit 6d098c794df2f996659f404c49dc79e50e326cd5 Author: Matthew Date: Wed Apr 8 17:53:22 2026 -0700 Publish NativeLooper diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa5dcec --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +__pycache__/ +*.pyc +*.pyo +.DS_Store +*.egg-info/ +dist/ +build/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..14fac91 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2397db1 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# ComfyUI-NativeLooper + +Multi-loop video generation with **sharp output** and character consistency. + +## Features + +- **Multi-loop generation** — up to 10 segments, chained for seamless long videos +- **Per-loop prompts** — different prompt for each segment +- **Per-loop frame counts** — customize duration per segment +- **Per-loop LoRA** — different LoRA per segment +- **Memory efficient** — segments saved to disk between loops, VRAM cleaned between iterations + +## Installation + +### Option 1: Clone into custom_nodes +```bash +cd ComfyUI/custom_nodes +git clone https://github.com/YOUR_USERNAME/ComfyUI-NativeLooper.git +``` + +### Option 2: Manual +Copy the `nodes.py` and `__init__.py` files into `ComfyUI/custom_nodes/ComfyUI-NativeLooper/`. + +No additional dependencies required — uses only ComfyUI built-in modules. + +## How It Works + +1. For each loop: + - Encodes prompt and CLIP Vision features for the current start image + - Builds WanImageToVideo conditioning + - Optionally applies a per-loop LoRA + - Samples with KSampler, decodes with VAE + - Saves segment to disk; uses last frame as start image for the next loop +2. Concatenates all segments into the final video. + +## License + +MIT diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..ae9fe85 --- /dev/null +++ b/__init__.py @@ -0,0 +1,16 @@ +from .nodes import NODE_CLASS_MAPPINGS as NCM1, NODE_DISPLAY_NAME_MAPPINGS as NDM1 +from .nodes_highlow import I2VLooperHL, I2VLoopHL + +NODE_CLASS_MAPPINGS = { + **NCM1, + "I2VLooperHL": I2VLooperHL, + "I2VLoopHL": I2VLoopHL, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + **NDM1, + "I2VLooperHL": "I2V Looper High/Low", + "I2VLoopHL": "I2V Loop High/Low", +} + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/nodes.py b/nodes.py new file mode 100644 index 0000000..f868b05 --- /dev/null +++ b/nodes.py @@ -0,0 +1,261 @@ +""" +Native Looper - Multi-loop video generation using WanImageToVideo conditioning. + +Each loop uses the last frame from the previous loop as the start image, +chaining I2V generation for seamless long videos. +""" + +import torch +import gc +import os +import tempfile +import shutil + +import comfy.model_management +import comfy.utils +import comfy.latent_formats +import comfy.sd +import folder_paths +import nodes +import node_helpers + + +class NativeLoop: + """Per-loop config: prompt, frames, LoRA.""" + @classmethod + def INPUT_TYPES(cls): + lora_list = ["None"] + folder_paths.get_filename_list("loras") + return { + "required": { + "prompt": ("STRING", {"default": "", "multiline": True}), + "frames": ("INT", {"default": 0, "min": 0, "max": 200, "tooltip": "Frames for this loop (0 = use 81)"}), + "lora_name": (lora_list, {"default": "None"}), + "lora_strength": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.05}), + }, + } + + RETURN_TYPES = ("NATIVE_LOOP",) + RETURN_NAMES = ("loop_config",) + FUNCTION = "build" + CATEGORY = "video/native" + + def build(self, prompt, frames, lora_name, lora_strength): + return ({ + "prompt": prompt, + "frames": frames, + "lora_name": lora_name if lora_name != "None" else None, + "lora_strength": lora_strength, + },) + + +class NativeLooper: + """ + Multi-loop video generation using WanImageToVideo. + Last frame of each loop becomes start image for the next. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model": ("MODEL",), + "vae": ("VAE",), + "clip": ("CLIP",), + "clip_vision": ("CLIP_VISION", {"tooltip": "CLIP Vision model (e.g. clip_vision_h.safetensors)"}), + "start_image": ("IMAGE", {"tooltip": "Start image for loop 1"}), + "width": ("INT", {"default": 832, "min": 16, "max": 4096, "step": 16}), + "height": ("INT", {"default": 480, "min": 16, "max": 4096, "step": 16}), + "steps": ("INT", {"default": 20, "min": 1, "max": 100}), + "cfg": ("FLOAT", {"default": 4.0, "min": 0.0, "max": 30.0, "step": 0.1}), + "sampler_name": (["uni_pc", "euler", "dpm++_sde", "dpm++_2m", "ddim"], {"default": "uni_pc"}), + "scheduler": (["normal", "simple", "karras", "sgm_uniform"], {"default": "normal"}), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + }, + "optional": { + "positive_prompt": ("STRING", {"forceInput": True, "tooltip": "Base/fallback prompt"}), + "negative_prompt": ("STRING", {"forceInput": True, "tooltip": "Global negative prompt"}), + "loop_1": ("NATIVE_LOOP",), + "loop_2": ("NATIVE_LOOP",), + "loop_3": ("NATIVE_LOOP",), + "loop_4": ("NATIVE_LOOP",), + "loop_5": ("NATIVE_LOOP",), + "loop_6": ("NATIVE_LOOP",), + "loop_7": ("NATIVE_LOOP",), + "loop_8": ("NATIVE_LOOP",), + "loop_9": ("NATIVE_LOOP",), + "loop_10": ("NATIVE_LOOP",), + } + } + + RETURN_TYPES = ("IMAGE", "STRING",) + RETURN_NAMES = ("full_video", "used_prompts",) + FUNCTION = "generate" + CATEGORY = "video/native" + + def generate(self, model, vae, clip, clip_vision, start_image, + width, height, + steps, cfg, sampler_name, scheduler, seed, + positive_prompt="", negative_prompt="low quality, blurry, glitch, distortion", + loop_1=None, loop_2=None, loop_3=None, loop_4=None, loop_5=None, + loop_6=None, loop_7=None, loop_8=None, loop_9=None, loop_10=None): + + from comfy_extras.nodes_wan import WanImageToVideo + + loop_configs = [loop_1, loop_2, loop_3, loop_4, loop_5, + loop_6, loop_7, loop_8, loop_9, loop_10] + + used_prompts_log = [] + segment_dir = tempfile.mkdtemp(prefix="native_i2v_segments_") + print(f"\n{'='*60}") + print("NATIVE I2V LOOPER") + print(f"{'='*60}") + extension_loops = sum(1 for c in loop_configs if c is not None) + if extension_loops == 0: + extension_loops = 1 + print(f"Loops: {extension_loops} (auto-detected)") + + current_start_image = comfy.utils.common_upscale( + start_image[:1].movedim(-1, 1), width, height, "bilinear", "center" + ).movedim(1, -1) + + segment_paths = [] + current_seed = seed + + for loop_idx in range(10): + loop_id = loop_idx + 1 + + comfy.model_management.soft_empty_cache() + if torch.cuda.is_available(): + torch.cuda.empty_cache() + gc.collect() + + config = loop_configs[loop_idx] if loop_idx < len(loop_configs) else None + + if config is None: + print(f"\n{'─'*60}") + print(f"LOOP {loop_id}/{extension_loops} — SKIPPED") + print(f"{'─'*60}") + used_prompts_log.append(f"Loop {loop_id}: SKIPPED") + current_seed += 1 + continue + + loop_frame_count = config.get("frames", 0) + if loop_frame_count <= 0: + loop_frame_count = 81 + + active_prompt = config.get("prompt", "").strip() or positive_prompt + loop_lora_name = config.get("lora_name") + loop_lora_strength = config.get("lora_strength", 0.8) + + print(f"\n{'─'*60}") + print(f"LOOP {loop_id}/{extension_loops} ({loop_frame_count} frames)") + if loop_lora_name: + print(f"LoRA: {loop_lora_name} @ {loop_lora_strength}") + print(f"{'─'*60}") + print(f"Prompt: {active_prompt[:80]}...") + used_prompts_log.append(f"Loop {loop_id} ({loop_frame_count}f): {active_prompt[:80]}") + + # Encode text + tokens_pos = clip.tokenize(active_prompt) + cond_pos, pooled_pos = clip.encode_from_tokens(tokens_pos, return_pooled=True) + c_pos = [[cond_pos, {"pooled_output": pooled_pos}]] + + tokens_neg = clip.tokenize(negative_prompt) + cond_neg, pooled_neg = clip.encode_from_tokens(tokens_neg, return_pooled=True) + c_neg = [[cond_neg, {"pooled_output": pooled_neg}]] + + # CLIP Vision encode + cv_result = nodes.CLIPVisionEncode().encode(clip_vision, current_start_image, "none") + cv_out = cv_result[0] + + # WanImageToVideo conditioning + i2v_result = WanImageToVideo.execute( + positive=c_pos, negative=c_neg, vae=vae, + clip_vision_output=cv_out, start_image=current_start_image, + width=width, height=height, length=loop_frame_count, batch_size=1, + ) + c_sample_pos = i2v_result[0] + c_sample_neg = i2v_result[1] + latent_dict = i2v_result[2] + + # Apply LoRA + loop_model = model + if loop_lora_name: + lora_path = folder_paths.get_full_path("loras", loop_lora_name) + if lora_path: + print(f"Loading LoRA: {loop_lora_name}...") + lora = comfy.utils.load_torch_file(lora_path, safe_load=True) + loop_model, _ = comfy.sd.load_lora_for_models(model, clip, lora, loop_lora_strength, loop_lora_strength) + del lora + + # Sample + print(f"Sampling ({steps} steps, {sampler_name}, cfg={cfg})...") + ksampler = nodes.KSampler() + result = ksampler.sample( + model=loop_model, seed=current_seed, steps=steps, cfg=cfg, + sampler_name=sampler_name, scheduler=scheduler, + positive=c_sample_pos, negative=c_sample_neg, + latent_image=latent_dict, denoise=1.0 + ) + sampled = result[0]["samples"] + + # Decode + print("VAE decoding...") + decoded = vae.decode(sampled) + while decoded.ndim > 4: + decoded = decoded.squeeze(0) + + if decoded.shape[1] != height or decoded.shape[2] != width: + decoded = comfy.utils.common_upscale( + decoded.movedim(-1, 1), width, height, "bilinear", "center" + ).movedim(1, -1) + + print(f"Decoded: {decoded.shape[0]} frames") + + seg_path = os.path.join(segment_dir, f"segment_{loop_id:03d}.pt") + torch.save(decoded.cpu(), seg_path) + segment_paths.append(seg_path) + + # Last frame becomes next loop's start image + current_start_image = decoded[-1:].clone() + + if loop_lora_name and loop_model is not model: + del loop_model + del decoded, sampled, c_pos, c_neg + comfy.model_management.soft_empty_cache() + if torch.cuda.is_available(): + torch.cuda.empty_cache() + gc.collect() + current_seed += 1 + + # Combine + print(f"\n{'='*60}") + print("Combining segments...") + full_video = None + for seg_path in segment_paths: + seg = torch.load(seg_path, map_location="cpu") + full_video = seg if full_video is None else torch.cat([full_video, seg], dim=0) + del seg + gc.collect() + print(f" Total: {full_video.shape[0]} frames") + + if full_video is None: + full_video = torch.zeros((1, height, width, 3)) + + try: + shutil.rmtree(segment_dir) + except: + pass + + print(f"\nFinal video: {full_video.shape[0]} frames") + return (full_video, "\n".join(used_prompts_log)) + + +NODE_CLASS_MAPPINGS = { + "I2VLooper": NativeLooper, + "I2VLoop": NativeLoop, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "I2VLooper": "I2V Looper", + "I2VLoop": "I2V Loop", +} diff --git a/nodes_highlow.py b/nodes_highlow.py new file mode 100644 index 0000000..86e3fba --- /dev/null +++ b/nodes_highlow.py @@ -0,0 +1,329 @@ +""" +I2V Looper High/Low - Multi-loop I2V video generation with dual High/Low noise models. + +Each loop uses WanImageToVideo conditioning with the last frame from the +previous loop as the start image. Matches APP VIDEO workflow approach. +""" + +import torch +import gc +import os +import tempfile +import shutil + +import comfy.model_management +import comfy.utils +import comfy.latent_formats +import comfy.sd +import folder_paths +import nodes +import node_helpers + + +class I2VLoopHL: + """Per-loop config with High/Low LoRA support.""" + @classmethod + def INPUT_TYPES(cls): + lora_list = ["None"] + folder_paths.get_filename_list("loras") + return { + "required": { + "prompt": ("STRING", {"default": "", "multiline": True}), + "frames": ("INT", {"default": 81, "min": 1, "max": 200}), + }, + "optional": { + "lora_high": (lora_list, {"default": "None"}), + "lora_high_strength": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.05}), + "lora_low": (lora_list, {"default": "None"}), + "lora_low_strength": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.05}), + }, + } + + RETURN_TYPES = ("I2V_LOOP_HL",) + RETURN_NAMES = ("loop_config",) + FUNCTION = "build" + CATEGORY = "video/i2v_looper" + + def build(self, prompt, frames, lora_high="None", lora_high_strength=0.8, + lora_low="None", lora_low_strength=0.8): + return ({ + "prompt": prompt, + "frames": frames, + "lora_high": lora_high if lora_high != "None" else None, + "lora_high_strength": lora_high_strength, + "lora_low": lora_low if lora_low != "None" else None, + "lora_low_strength": lora_low_strength, + },) + + +class I2VLooperHL: + """ + Multi-loop I2V with High/Low noise models. + Last frame of each loop becomes start image for the next. + Matches APP VIDEO dual-pass KSamplerAdvanced approach. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model_high": ("MODEL", {"tooltip": "High-noise model"}), + "model_low": ("MODEL", {"tooltip": "Low-noise model"}), + "vae": ("VAE",), + "clip": ("CLIP",), + "clip_vision": ("CLIP_VISION", {"tooltip": "CLIP Vision model (e.g. clip_vision_h.safetensors)"}), + "start_image": ("IMAGE", {"tooltip": "Start image for loop 1"}), + "width": ("INT", {"default": 832, "min": 16, "max": 4096, "step": 16}), + "height": ("INT", {"default": 480, "min": 16, "max": 4096, "step": 16}), + "steps": ("INT", {"default": 8, "min": 1, "max": 100}), + "cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 30.0, "step": 0.1}), + "split_step": ("INT", {"default": 4, "min": 1, "max": 50, "tooltip": "Step where high switches to low model"}), + "sampler_name": (["euler", "uni_pc", "dpm++_sde", "dpm++_2m", "ddim"], {"default": "euler"}), + "scheduler": (["simple", "normal", "karras", "sgm_uniform"], {"default": "simple"}), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + }, + "optional": { + "positive_prompt": ("STRING", {"forceInput": True, "tooltip": "Base/fallback prompt"}), + "negative_prompt": ("STRING", {"forceInput": True, "tooltip": "Global negative prompt"}), + "loop_1": ("I2V_LOOP_HL",), + "loop_2": ("I2V_LOOP_HL",), + "loop_3": ("I2V_LOOP_HL",), + "loop_4": ("I2V_LOOP_HL",), + "loop_5": ("I2V_LOOP_HL",), + "loop_6": ("I2V_LOOP_HL",), + "loop_7": ("I2V_LOOP_HL",), + "loop_8": ("I2V_LOOP_HL",), + "loop_9": ("I2V_LOOP_HL",), + "loop_10": ("I2V_LOOP_HL",), + } + } + + RETURN_TYPES = ("IMAGE", "STRING",) + RETURN_NAMES = ("full_video", "used_prompts",) + FUNCTION = "generate" + CATEGORY = "video/i2v_looper" + + def _load_lora(self, model, clip, lora_name, strength): + lora_path = folder_paths.get_full_path("loras", lora_name) + if lora_path: + lora = comfy.utils.load_torch_file(lora_path, safe_load=True) + patched_model, patched_clip = comfy.sd.load_lora_for_models( + model, clip, lora, strength, strength + ) + del lora + return patched_model, patched_clip + print(f"WARNING: LoRA not found: {lora_name}") + return model, clip + + def generate(self, model_high, model_low, vae, clip, clip_vision, start_image, + width, height, + steps, cfg, split_step, sampler_name, scheduler, seed, + positive_prompt="", negative_prompt="low quality, blurry, glitch, distortion", + loop_1=None, loop_2=None, loop_3=None, loop_4=None, loop_5=None, + loop_6=None, loop_7=None, loop_8=None, loop_9=None, loop_10=None): + + from comfy_extras.nodes_wan import WanImageToVideo + + loop_configs = [loop_1, loop_2, loop_3, loop_4, loop_5, + loop_6, loop_7, loop_8, loop_9, loop_10] + + # Count connected loops + extension_loops = sum(1 for c in loop_configs if c is not None) + if extension_loops == 0: + extension_loops = 1 # At least 1 loop + + used_prompts_log = [] + segment_dir = tempfile.mkdtemp(prefix="i2v_hl_segments_") + print(f"\n{'='*60}") + print("I2V LOOPER HIGH/LOW") + print(f"{'='*60}") + print(f"Loops: {extension_loops} (auto-detected from connected loop nodes)") + print(f"Steps: {steps} (high 0-{split_step}, low {split_step}-end)") + + # Resize start image + current_start_image = comfy.utils.common_upscale( + start_image[:1].movedim(-1, 1), width, height, "bilinear", "center" + ).movedim(1, -1) + + segment_paths = [] + current_seed = seed + + for loop_idx in range(10): + loop_id = loop_idx + 1 + + comfy.model_management.soft_empty_cache() + if torch.cuda.is_available(): + torch.cuda.empty_cache() + gc.collect() + + config = loop_configs[loop_idx] if loop_idx < len(loop_configs) else None + + if config is None: + print(f"\n{'─'*60}") + print(f"LOOP {loop_id}/{extension_loops} — SKIPPED") + print(f"{'─'*60}") + used_prompts_log.append(f"Loop {loop_id}: SKIPPED") + current_seed += 1 + continue + + loop_frame_count = config.get("frames", 81) + if loop_frame_count <= 0: + loop_frame_count = 81 + + active_prompt = config.get("prompt", "").strip() or positive_prompt + + lora_high_name = config.get("lora_high") + lora_high_str = config.get("lora_high_strength", 0.8) + lora_low_name = config.get("lora_low") + lora_low_str = config.get("lora_low_strength", 0.8) + + print(f"\n{'─'*60}") + print(f"LOOP {loop_id}/{extension_loops} ({loop_frame_count} frames)") + if lora_high_name: + print(f"LoRA HIGH: {lora_high_name} @ {lora_high_str}") + if lora_low_name: + print(f"LoRA LOW: {lora_low_name} @ {lora_low_str}") + print(f"{'─'*60}") + print(f"Prompt: {active_prompt[:80]}...") + used_prompts_log.append(f"Loop {loop_id} ({loop_frame_count}f): {active_prompt[:80]}") + + # Encode text + tokens_pos = clip.tokenize(active_prompt) + cond_pos, pooled_pos = clip.encode_from_tokens(tokens_pos, return_pooled=True) + c_pos = [[cond_pos, {"pooled_output": pooled_pos}]] + + tokens_neg = clip.tokenize(negative_prompt) + cond_neg, pooled_neg = clip.encode_from_tokens(tokens_neg, return_pooled=True) + c_neg = [[cond_neg, {"pooled_output": pooled_neg}]] + + # CLIP Vision encode — use CLIPVisionEncode node (same as APP VIDEO) + print("CLIP Vision encoding...") + cv_result = nodes.CLIPVisionEncode().encode(clip_vision, current_start_image, "none") + cv_out = cv_result[0] # CLIP_VISION_OUTPUT + + # WanImageToVideo conditioning (same as APP VIDEO) + print("WanImageToVideo conditioning...") + i2v_result = WanImageToVideo.execute( + positive=c_pos, negative=c_neg, vae=vae, + clip_vision_output=cv_out, start_image=current_start_image, + width=width, height=height, length=loop_frame_count, batch_size=1, + ) + c_sample_pos = i2v_result[0] + c_sample_neg = i2v_result[1] + latent_dict = i2v_result[2] + + # Apply per-loop LoRAs + high_model = model_high + low_model = model_low + + if lora_high_name: + print(f"Loading HIGH LoRA...") + high_model, _ = self._load_lora(model_high, clip, lora_high_name, lora_high_str) + + if lora_low_name: + print(f"Loading LOW LoRA...") + low_model, _ = self._load_lora(model_low, clip, lora_low_name, lora_low_str) + + # TWO-PASS SAMPLING (matching APP VIDEO exactly) + ksampler = nodes.KSamplerAdvanced() + + # Pass 1: High-noise model (steps 0 to split_step) + # APP VIDEO: add_noise=enable, return_with_leftover_noise=enable + print(f"Sampling HIGH (steps 0-{split_step})...") + high_out = ksampler.sample( + model=high_model, + add_noise="enable", + noise_seed=current_seed, + steps=steps, + cfg=cfg, + sampler_name=sampler_name, + scheduler=scheduler, + positive=c_sample_pos, + negative=c_sample_neg, + latent_image=latent_dict, + start_at_step=0, + end_at_step=split_step, + return_with_leftover_noise="enable", + ) + + comfy.model_management.soft_empty_cache() + if torch.cuda.is_available(): + torch.cuda.empty_cache() + gc.collect() + + # Pass 2: Low-noise model (split_step to end) + # APP VIDEO: add_noise=disable, noise_seed=0, end_at_step=1000, return_with_leftover_noise=disable + print(f"Sampling LOW (steps {split_step}-end)...") + low_out = ksampler.sample( + model=low_model, + add_noise="disable", + noise_seed=0, + steps=steps, + cfg=cfg, + sampler_name=sampler_name, + scheduler=scheduler, + positive=c_sample_pos, + negative=c_sample_neg, + latent_image={"samples": high_out[0]["samples"]}, + start_at_step=split_step, + end_at_step=1000, + return_with_leftover_noise="disable", + ) + + sampled = low_out[0]["samples"] + + # Decode + print("VAE decoding...") + decoded = vae.decode(sampled) + while decoded.ndim > 4: + decoded = decoded.squeeze(0) + print(f"Decoded: {decoded.shape[0]} frames") + + if decoded.shape[1] != height or decoded.shape[2] != width: + decoded = comfy.utils.common_upscale( + decoded.movedim(-1, 1), width, height, "bilinear", "center" + ).movedim(1, -1) + + # Save segment + seg_path = os.path.join(segment_dir, f"segment_{loop_id:03d}.pt") + torch.save(decoded.cpu(), seg_path) + segment_paths.append(seg_path) + + # Last frame becomes next loop's start image + current_start_image = decoded[-1:].clone() + print(f"Next start image from frame {decoded.shape[0]-1}") + + # Cleanup + if lora_high_name and high_model is not model_high: + del high_model + if lora_low_name and low_model is not model_low: + del low_model + del decoded, sampled, c_pos, c_neg + comfy.model_management.soft_empty_cache() + if torch.cuda.is_available(): + torch.cuda.empty_cache() + gc.collect() + + current_seed += 1 + + # Combine all segments + print(f"\n{'='*60}") + print("Combining segments...") + print(f"{'='*60}") + + full_video = None + for seg_path in segment_paths: + seg = torch.load(seg_path, map_location="cpu") + full_video = seg if full_video is None else torch.cat([full_video, seg], dim=0) + del seg + gc.collect() + print(f" Total: {full_video.shape[0]} frames") + + if full_video is None: + full_video = torch.zeros((1, height, width, 3)) + + try: + shutil.rmtree(segment_dir) + except: + pass + + print(f"\nFinal video: {full_video.shape[0]} frames") + return (full_video, "\n".join(used_prompts_log)) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..013a3d8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "comfyui-native-looper" +description = "Multi-loop video generation with sharp output and character consistency" +version = "1.0.0" +license = "MIT" + +[project.urls] +Repository = "https://github.com/YOUR_USERNAME/ComfyUI-NativeLooper" + +[tool.comfy] +PublisherId = "" +DisplayName = "Native Looper" +Icon = "" diff --git a/workflows/I2V Looper HL Detailed.json b/workflows/I2V Looper HL Detailed.json new file mode 100644 index 0000000..38fc3c9 --- /dev/null +++ b/workflows/I2V Looper HL Detailed.json @@ -0,0 +1 @@ +{"id": "e529c139-4fac-47ea-b612-59955eff431a", "revision": 0, "last_node_id": 41, "last_link_id": 50, "nodes": [{"id": 26, "type": "ModelSamplingSD3", "pos": [-1918.2135836051846, -989.1772639680827], "size": [270, 58], "flags": {}, "order": 11, "mode": 0, "inputs": [{"localized_name": "model", "name": "model", "type": "MODEL", "link": 18}, {"localized_name": "shift", "name": "shift", "type": "FLOAT", "widget": {"name": "shift"}, "link": null}], "outputs": [{"localized_name": "MODEL", "name": "MODEL", "type": "MODEL", "links": [45]}], "title": "ModelSamplingSD3 (HIGH)", "properties": {"cnr_id": "comfy-core", "ver": "0.18.1", "Node name for S&R": "ModelSamplingSD3", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.7.1"}}, "widgets_values": [8]}, {"id": 29, "type": "CLIPVisionLoader", "pos": [-2379.2691323258073, -703.962430240438], "size": [300, 60], "flags": {}, "order": 0, "mode": 0, "inputs": [{"localized_name": "clip_name", "name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": null}], "outputs": [{"localized_name": "CLIP_VISION", "name": "CLIP_VISION", "type": "CLIP_VISION", "slot_index": 0, "links": [25]}], "properties": {"cnr_id": "comfy-core", "ver": "0.18.1", "Node name for S&R": "CLIPVisionLoader", "ue_properties": {"widget_ue_connectable": {}, "version": "7.7.1", "input_ue_unconnectable": {}}}, "widgets_values": ["clip_vision_h.safetensors"]}, {"id": 28, "type": "DF_Text", "pos": [-2427.4536427598537, -850.4502324420374], "size": [400, 100], "flags": {}, "order": 1, "mode": 0, "inputs": [{"localized_name": "Text", "name": "Text", "type": "STRING", "widget": {"name": "Text"}, "link": null}], "outputs": [{"localized_name": "STRING", "name": "STRING", "type": "STRING", "slot_index": 0, "links": [21]}], "title": "Negative Prompt", "properties": {"cnr_id": "derfuu_comfyui_moddednodes", "ver": "1.0.1", "Node name for S&R": "DF_Text", "ue_properties": {"widget_ue_connectable": {}, "version": "7.7.1", "input_ue_unconnectable": {}}}, "widgets_values": ["cartoon, anime, 3d render, cgi, low quality, blurry, glitch, distortion, text, watermark, subtitles, childish, ugly"]}, {"id": 25, "type": "UnetLoaderGGUF", "pos": [-1916.5280142620743, -1092.6566939819752], "size": [270, 58], "flags": {}, "order": 2, "mode": 0, "inputs": [{"localized_name": "unet_name", "name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": null}], "outputs": [{"localized_name": "MODEL", "name": "MODEL", "type": "MODEL", "links": [18]}], "title": "UnetLoaderGGUF (HIGH)", "properties": {"cnr_id": "ComfyUI-GGUF", "ver": "6ea2651e7df66d7585f6ffee804b20e92fb38b8a", "Node name for S&R": "UnetLoaderGGUF", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.7.1"}}, "widgets_values": ["wan22I2VA14BGGUF_q8A14BHigh.gguf"]}, {"id": 2, "type": "UnetLoaderGGUF", "pos": [-1617.1222952689902, -1087.7281134592963], "size": [270, 58], "flags": {}, "order": 3, "mode": 0, "inputs": [{"localized_name": "unet_name", "name": "unet_name", "type": "COMBO", "widget": {"name": "unet_name"}, "link": null}], "outputs": [{"localized_name": "MODEL", "name": "MODEL", "type": "MODEL", "links": [1]}], "title": "UnetLoaderGGUF (LOW)", "properties": {"cnr_id": "ComfyUI-GGUF", "ver": "6ea2651e7df66d7585f6ffee804b20e92fb38b8a", "Node name for S&R": "UnetLoaderGGUF", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.7.1"}}, "widgets_values": ["wan22I2VA14BGGUF_q8A14BLow.gguf"]}, {"id": 3, "type": "ModelSamplingSD3", "pos": [-1616.550481037919, -977.7166614101166], "size": [270, 58], "flags": {}, "order": 12, "mode": 0, "inputs": [{"localized_name": "model", "name": "model", "type": "MODEL", "link": 1}, {"localized_name": "shift", "name": "shift", "type": "FLOAT", "widget": {"name": "shift"}, "link": null}], "outputs": [{"localized_name": "MODEL", "name": "MODEL", "type": "MODEL", "links": [47]}], "title": "ModelSamplingSD3 (LOW)", "properties": {"cnr_id": "comfy-core", "ver": "0.18.1", "Node name for S&R": "ModelSamplingSD3", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.7.1"}}, "widgets_values": [8]}, {"id": 6, "type": "VAELoader", "pos": [-1922.4829447490238, -844.0946836581325], "size": [270, 58], "flags": {}, "order": 4, "mode": 0, "inputs": [{"localized_name": "vae_name", "name": "vae_name", "type": "COMBO", "widget": {"name": "vae_name"}, "link": null}], "outputs": [{"localized_name": "VAE", "name": "VAE", "type": "VAE", "links": [3]}], "properties": {"cnr_id": "comfy-core", "ver": "0.18.1", "Node name for S&R": "VAELoader", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.7.1"}}, "widgets_values": ["wan_2.1_vae.safetensors"]}, {"id": 9, "type": "CLIPLoader", "pos": [-1917.2859432138207, -727.9395534287916], "size": [270, 106], "flags": {}, "order": 5, "mode": 0, "inputs": [{"localized_name": "clip_name", "name": "clip_name", "type": "COMBO", "widget": {"name": "clip_name"}, "link": null}, {"localized_name": "type", "name": "type", "type": "COMBO", "widget": {"name": "type"}, "link": null}, {"localized_name": "device", "name": "device", "shape": 7, "type": "COMBO", "widget": {"name": "device"}, "link": null}], "outputs": [{"localized_name": "CLIP", "name": "CLIP", "type": "CLIP", "links": [4]}], "properties": {"cnr_id": "comfy-core", "ver": "0.18.1", "Node name for S&R": "CLIPLoader", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.7.1"}}, "widgets_values": ["umt5_xxl_fp8_e4m3fn_scaled.safetensors", "wan", "default"]}, {"id": 33, "type": "GetImageSize+", "pos": [-1877.575261717702, 389.551451014326], "size": [214.20001220703125, 286], "flags": {"collapsed": false}, "order": 15, "mode": 0, "inputs": [{"localized_name": "image", "name": "image", "type": "IMAGE", "link": 32}], "outputs": [{"localized_name": "width", "name": "width", "type": "INT", "slot_index": 0, "links": [34]}, {"localized_name": "height", "name": "height", "type": "INT", "slot_index": 1, "links": [35]}, {"localized_name": "count", "name": "count", "type": "INT", "links": null}], "properties": {"cnr_id": "comfyui_essentials", "ver": "33ff89fd354d8ec3ab6affb605a79a931b445d99", "Node name for S&R": "GetImageSize+", "ue_properties": {"version": "7.5.1", "widget_ue_connectable": {}, "input_ue_unconnectable": {}}}, "widgets_values": [], "shape": 1}, {"id": 32, "type": "PreviewImage", "pos": [-1875.265799994794, 743.1682544066871], "size": [210, 320], "flags": {}, "order": 14, "mode": 0, "inputs": [{"localized_name": "images", "name": "images", "type": "IMAGE", "link": 28}], "outputs": [], "properties": {"cnr_id": "comfy-core", "ver": "0.3.71", "Node name for S&R": "PreviewImage", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.5.1"}}, "widgets_values": []}, {"id": 1, "type": "LoadImage", "pos": [-1910.4973848236225, -564.0914659959175], "size": [282.798828125, 314.00000000000006], "flags": {}, "order": 6, "mode": 0, "inputs": [{"localized_name": "image", "name": "image", "type": "COMBO", "widget": {"name": "image"}, "link": null}, {"localized_name": "choose file to upload", "name": "upload", "type": "IMAGEUPLOAD", "widget": {"name": "upload"}, "link": null}], "outputs": [{"localized_name": "IMAGE", "name": "IMAGE", "type": "IMAGE", "links": [31]}, {"localized_name": "MASK", "name": "MASK", "type": "MASK", "links": null}], "properties": {"cnr_id": "comfy-core", "ver": "0.18.1", "Node name for S&R": "LoadImage", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.7.1"}}, "widgets_values": ["horse.jpg", "image"]}, {"id": 37, "type": "Text _O", "pos": [-2418.4334686284474, -1088.618486249721], "size": [420, 160], "flags": {}, "order": 7, "mode": 0, "inputs": [{"localized_name": "text", "name": "text", "type": "STRING", "widget": {"name": "text"}, "link": null}], "outputs": [{"localized_name": "STRING", "name": "STRING", "type": "STRING", "links": [41]}], "title": "MAIN PROMPT", "properties": {"cnr_id": "qol", "ver": "f09d10dea0afbd3984a284acf8f0913a634e36ec", "Node name for S&R": "Text _O", "ue_properties": {"version": "7.5.1", "widget_ue_connectable": {"text": true}, "input_ue_unconnectable": {}}}, "widgets_values": ["Ultra-realistic cinematic POV video, warm natural daylight, Young 18-year-old woman , Fixed camera, sharp focus, 4K quality."], "shape": 1}, {"id": 11, "type": "VHS_VideoCombine", "pos": [-1012.1122725438876, -743.0001261700288], "size": [735.8333163038824, 262], "flags": {}, "order": 17, "mode": 0, "inputs": [{"localized_name": "images", "name": "images", "type": "IMAGE", "link": 50}, {"localized_name": "audio", "name": "audio", "shape": 7, "type": "AUDIO", "link": null}, {"localized_name": "meta_batch", "name": "meta_batch", "shape": 7, "type": "VHS_BatchManager", "link": null}, {"localized_name": "vae", "name": "vae", "shape": 7, "type": "VAE", "link": null}, {"localized_name": "frame_rate", "name": "frame_rate", "type": "FLOAT", "widget": {"name": "frame_rate"}, "link": null}, {"localized_name": "loop_count", "name": "loop_count", "type": "INT", "widget": {"name": "loop_count"}, "link": null}, {"localized_name": "filename_prefix", "name": "filename_prefix", "type": "STRING", "widget": {"name": "filename_prefix"}, "link": null}, {"localized_name": "format", "name": "format", "type": "COMBO", "widget": {"name": "format"}, "link": null}, {"localized_name": "pingpong", "name": "pingpong", "type": "BOOLEAN", "widget": {"name": "pingpong"}, "link": null}, {"localized_name": "save_output", "name": "save_output", "type": "BOOLEAN", "widget": {"name": "save_output"}, "link": null}, {"name": "lossless", "type": "BOOLEAN", "widget": {"name": "lossless"}, "link": null}], "outputs": [{"localized_name": "Filenames", "name": "Filenames", "type": "VHS_FILENAMES", "links": null}], "properties": {"cnr_id": "comfyui-videohelpersuite", "ver": "449839959f0153fb8a57211a9364c55163935ca9", "Node name for S&R": "VHS_VideoCombine", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.7.1"}}, "widgets_values": {"frame_rate": 24, "loop_count": 0, "filename_prefix": "WanLooper", "format": "image/webp", "lossless": true, "pingpong": false, "save_output": true, "videopreview": {"hidden": false, "paused": false, "params": {"filename": "WanLooper_00016.webp", "subfolder": "", "type": "output", "format": "image/webp", "frame_rate": 24, "workflow": "WanLooper_00016.png", "fullpath": "D:\\ComfyUI-Easy-Install\\ComfyUI\\output\\WanLooper_00016.webp"}}}}, {"id": 13, "type": "I2VLoopHL", "pos": [-2896.9542920521135, -605.3922697639429], "size": [400, 208], "flags": {}, "order": 8, "mode": 0, "inputs": [{"localized_name": "prompt", "name": "prompt", "type": "STRING", "widget": {"name": "prompt"}, "link": null}, {"localized_name": "frames", "name": "frames", "type": "INT", "widget": {"name": "frames"}, "link": null}, {"localized_name": "lora_high", "name": "lora_high", "shape": 7, "type": "COMBO", "widget": {"name": "lora_high"}, "link": null}, {"localized_name": "lora_high_strength", "name": "lora_high_strength", "shape": 7, "type": "FLOAT", "widget": {"name": "lora_high_strength"}, "link": null}, {"localized_name": "lora_low", "name": "lora_low", "shape": 7, "type": "COMBO", "widget": {"name": "lora_low"}, "link": null}, {"localized_name": "lora_low_strength", "name": "lora_low_strength", "shape": 7, "type": "FLOAT", "widget": {"name": "lora_low_strength"}, "link": null}], "outputs": [{"localized_name": "loop_config", "name": "loop_config", "type": "I2V_LOOP_HL", "links": [42]}], "properties": {"Node name for S&R": "NativeLoop", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.7.1"}}, "widgets_values": ["a woman on a horse rides through the field", 41, "None", 1, "None", 1]}, {"id": 14, "type": "I2VLoopHL", "pos": [-2890.116792553664, -334.6626400721885], "size": [400, 208], "flags": {}, "order": 9, "mode": 0, "inputs": [{"localized_name": "prompt", "name": "prompt", "type": "STRING", "widget": {"name": "prompt"}, "link": null}, {"localized_name": "frames", "name": "frames", "type": "INT", "widget": {"name": "frames"}, "link": null}, {"localized_name": "lora_high", "name": "lora_high", "shape": 7, "type": "COMBO", "widget": {"name": "lora_high"}, "link": null}, {"localized_name": "lora_high_strength", "name": "lora_high_strength", "shape": 7, "type": "FLOAT", "widget": {"name": "lora_high_strength"}, "link": null}, {"localized_name": "lora_low", "name": "lora_low", "shape": 7, "type": "COMBO", "widget": {"name": "lora_low"}, "link": null}, {"localized_name": "lora_low_strength", "name": "lora_low_strength", "shape": 7, "type": "FLOAT", "widget": {"name": "lora_low_strength"}, "link": null}], "outputs": [{"localized_name": "loop_config", "name": "loop_config", "type": "I2V_LOOP_HL", "links": [43]}], "properties": {"Node name for S&R": "NativeLoop", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.7.1"}}, "widgets_values": ["she pulls on the reigns and slows the horse, she holds her hand up to block the sun as she looks around", 41, "None", 1, "None", 1]}, {"id": 31, "type": "ImageResizeKJv2", "pos": [-1908.8098450744117, -173.287117016489], "size": [270, 498], "flags": {}, "order": 13, "mode": 0, "inputs": [{"localized_name": "image", "name": "image", "type": "IMAGE", "link": 31}, {"localized_name": "mask", "name": "mask", "shape": 7, "type": "MASK", "link": null}, {"localized_name": "width", "name": "width", "type": "INT", "widget": {"name": "width"}, "link": null}, {"localized_name": "height", "name": "height", "type": "INT", "widget": {"name": "height"}, "link": null}, {"localized_name": "upscale_method", "name": "upscale_method", "type": "COMBO", "widget": {"name": "upscale_method"}, "link": null}, {"localized_name": "keep_proportion", "name": "keep_proportion", "type": "COMBO", "widget": {"name": "keep_proportion"}, "link": null}, {"localized_name": "pad_color", "name": "pad_color", "type": "STRING", "widget": {"name": "pad_color"}, "link": null}, {"localized_name": "crop_position", "name": "crop_position", "type": "COMBO", "widget": {"name": "crop_position"}, "link": null}, {"localized_name": "divisible_by", "name": "divisible_by", "type": "INT", "widget": {"name": "divisible_by"}, "link": null}, {"localized_name": "device", "name": "device", "shape": 7, "type": "COMBO", "widget": {"name": "device"}, "link": null}], "outputs": [{"localized_name": "IMAGE", "name": "IMAGE", "type": "IMAGE", "links": [28, 32, 33]}, {"localized_name": "width", "name": "width", "type": "INT", "links": null}, {"localized_name": "height", "name": "height", "type": "INT", "links": null}, {"localized_name": "mask", "name": "mask", "type": "MASK", "links": null}], "properties": {"cnr_id": "comfyui-kjnodes", "ver": "4dfb85dcc52e4315c33170d97bb987baa46d128b", "Node name for S&R": "ImageResizeKJv2", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.5.1"}}, "widgets_values": [480, 848, "area", "crop", "0, 0, 0", "center", 2, "gpu"]}, {"id": 10, "type": "I2VLooperHL", "pos": [-1481.4238227153735, -740.6458917849172], "size": [400, 1490.000000000001], "flags": {}, "order": 16, "mode": 0, "inputs": [{"localized_name": "model_high", "name": "model_high", "type": "MODEL", "link": 46}, {"localized_name": "model_low", "name": "model_low", "type": "MODEL", "link": 48}, {"localized_name": "vae", "name": "vae", "type": "VAE", "link": 3}, {"localized_name": "clip", "name": "clip", "type": "CLIP", "link": 4}, {"localized_name": "clip_vision", "name": "clip_vision", "type": "CLIP_VISION", "link": 25}, {"localized_name": "start_image", "name": "start_image", "type": "IMAGE", "link": 33}, {"localized_name": "positive_prompt", "name": "positive_prompt", "shape": 7, "type": "STRING", "link": 41}, {"localized_name": "negative_prompt", "name": "negative_prompt", "shape": 7, "type": "STRING", "link": 21}, {"localized_name": "loop_1", "name": "loop_1", "shape": 7, "type": "I2V_LOOP_HL", "link": 42}, {"localized_name": "loop_2", "name": "loop_2", "shape": 7, "type": "I2V_LOOP_HL", "link": 43}, {"localized_name": "loop_3", "name": "loop_3", "shape": 7, "type": "I2V_LOOP_HL", "link": null}, {"localized_name": "loop_4", "name": "loop_4", "shape": 7, "type": "I2V_LOOP_HL", "link": null}, {"localized_name": "loop_5", "name": "loop_5", "shape": 7, "type": "I2V_LOOP_HL", "link": null}, {"localized_name": "loop_6", "name": "loop_6", "shape": 7, "type": "I2V_LOOP_HL", "link": null}, {"localized_name": "loop_7", "name": "loop_7", "shape": 7, "type": "I2V_LOOP_HL", "link": null}, {"localized_name": "loop_8", "name": "loop_8", "shape": 7, "type": "I2V_LOOP_HL", "link": null}, {"localized_name": "loop_9", "name": "loop_9", "shape": 7, "type": "I2V_LOOP_HL", "link": null}, {"localized_name": "loop_10", "name": "loop_10", "shape": 7, "type": "I2V_LOOP_HL", "link": null}, {"localized_name": "width", "name": "width", "type": "INT", "widget": {"name": "width"}, "link": 34}, {"localized_name": "height", "name": "height", "type": "INT", "widget": {"name": "height"}, "link": 35}, {"localized_name": "steps", "name": "steps", "type": "INT", "widget": {"name": "steps"}, "link": null}, {"localized_name": "cfg", "name": "cfg", "type": "FLOAT", "widget": {"name": "cfg"}, "link": null}, {"localized_name": "split_step", "name": "split_step", "type": "INT", "widget": {"name": "split_step"}, "link": null}, {"localized_name": "sampler_name", "name": "sampler_name", "type": "COMBO", "widget": {"name": "sampler_name"}, "link": null}, {"localized_name": "scheduler", "name": "scheduler", "type": "COMBO", "widget": {"name": "scheduler"}, "link": null}, {"localized_name": "seed", "name": "seed", "type": "INT", "widget": {"name": "seed"}, "link": null}], "outputs": [{"localized_name": "full_video", "name": "full_video", "type": "IMAGE", "links": [49]}, {"localized_name": "used_prompts", "name": "used_prompts", "type": "STRING", "links": null}], "properties": {"Node name for S&R": "NativeLooper", "ue_properties": {"widget_ue_connectable": {}, "input_ue_unconnectable": {}, "version": "7.7.1"}}, "widgets_values": [832, 480, 30, 2, 20, "euler", "simple", 514123557033578, "randomize"]}, {"id": 38, "type": "MarkdownNote", "pos": [-155.52147313434827, -579.5740337106699], "size": [568.4984285500423, 486.94065479356266], "flags": {}, "order": 10, "mode": 0, "inputs": [], "outputs": [], "properties": {"ue_properties": {"widget_ue_connectable": {}, "version": "7.7.1", "input_ue_unconnectable": {}}}, "widgets_values": [""], "color": "#432", "bgcolor": "#653"}, {"id": 39, "type": "PathchSageAttentionKJ", "pos": [-1668.2135836051846, -989.1772639680827], "size": [300, 60], "flags": {}, "order": 60, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 45}], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [46], "slot_index": 0}], "title": "SageAttention (HIGH)", "properties": {"Node name for S&R": "PathchSageAttentionKJ"}, "widgets_values": ["auto", false]}, {"id": 40, "type": "PathchSageAttentionKJ", "pos": [-1366.550481037919, -977.7166614101166], "size": [300, 60], "flags": {}, "order": 61, "mode": 0, "inputs": [{"name": "model", "type": "MODEL", "link": 47}], "outputs": [{"name": "MODEL", "type": "MODEL", "links": [48], "slot_index": 0}], "title": "SageAttention (LOW)", "properties": {"Node name for S&R": "PathchSageAttentionKJ"}, "widgets_values": ["auto", false]}, {"id": 41, "type": "RIFE VFI", "pos": [-1081.4238227153735, -740.6458917849172], "size": [300, 200], "flags": {}, "order": 62, "mode": 4, "inputs": [{"name": "frames", "type": "IMAGE", "link": 49}, {"name": "optional_interpolation_states", "type": "INTERPOLATION_STATES", "link": null}], "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [50], "slot_index": 0}], "title": "RIFE Interpolation (enable for smooth)", "properties": {"Node name for S&R": "RIFE VFI"}, "widgets_values": ["rife47.pth", 10, 2, true, true, 1]}], "links": [[1, 2, 0, 3, 0, "MODEL"], [3, 6, 0, 10, 2, "VAE"], [4, 9, 0, 10, 3, "CLIP"], [18, 25, 0, 26, 0, "MODEL"], [21, 28, 0, 10, 7, "STRING"], [25, 29, 0, 10, 4, "CLIP_VISION"], [28, 31, 0, 32, 0, "IMAGE"], [31, 1, 0, 31, 0, "IMAGE"], [32, 31, 0, 33, 0, "IMAGE"], [33, 31, 0, 10, 5, "IMAGE"], [34, 33, 0, 10, 18, "INT"], [35, 33, 1, 10, 19, "INT"], [41, 37, 0, 10, 6, "STRING"], [42, 13, 0, 10, 8, "I2V_LOOP_HL"], [43, 14, 0, 10, 9, "I2V_LOOP_HL"], [45, 26, 0, 39, 0, "MODEL"], [46, 39, 0, 10, 0, "MODEL"], [47, 3, 0, 40, 0, "MODEL"], [48, 40, 0, 10, 1, "MODEL"], [49, 10, 0, 41, 0, "IMAGE"], [50, 41, 0, 11, 0, "IMAGE"]], "groups": [], "config": {}, "extra": {"ue_links": [], "ds": {"scale": 0.6043083782265182, "offset": [1810.8601152024, 1210.5008880680532]}, "links_added_by_ue": []}, "version": 0.4} \ No newline at end of file diff --git a/workflows/I2V Looper HL Preview.json b/workflows/I2V Looper HL Preview.json new file mode 100644 index 0000000..d107f7a --- /dev/null +++ b/workflows/I2V Looper HL Preview.json @@ -0,0 +1 @@ +{"id":"3295cd28-4fb8-4fde-a0b9-5d638cb119da","revision":0,"last_node_id":38,"last_link_id":44,"nodes":[{"id":26,"type":"ModelSamplingSD3","pos":[-1918.2135836051846,-989.1772639680827],"size":[270,58],"flags":{},"order":11,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":18},{"localized_name":"shift","name":"shift","type":"FLOAT","widget":{"name":"shift"},"link":null}],"outputs":[{"localized_name":"MODEL","name":"MODEL","type":"MODEL","links":[19]}],"title":"ModelSamplingSD3 (HIGH)","properties":{"cnr_id":"comfy-core","ver":"0.18.1","Node name for S&R":"ModelSamplingSD3","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.7.1"}},"widgets_values":[8]},{"id":29,"type":"CLIPVisionLoader","pos":[-2379.2691323258073,-703.962430240438],"size":[300,60],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"clip_name","name":"clip_name","type":"COMBO","widget":{"name":"clip_name"},"link":null}],"outputs":[{"localized_name":"CLIP_VISION","name":"CLIP_VISION","type":"CLIP_VISION","slot_index":0,"links":[25]}],"properties":{"cnr_id":"comfy-core","ver":"0.18.1","Node name for S&R":"CLIPVisionLoader","ue_properties":{"widget_ue_connectable":{},"version":"7.7.1","input_ue_unconnectable":{}}},"widgets_values":["clip_vision_h.safetensors"]},{"id":28,"type":"DF_Text","pos":[-2427.4536427598537,-850.4502324420374],"size":[400,100],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"Text","name":"Text","type":"STRING","widget":{"name":"Text"},"link":null}],"outputs":[{"localized_name":"STRING","name":"STRING","type":"STRING","slot_index":0,"links":[21]}],"title":"Negative Prompt","properties":{"cnr_id":"derfuu_comfyui_moddednodes","ver":"1.0.1","Node name for S&R":"DF_Text","ue_properties":{"widget_ue_connectable":{},"version":"7.7.1","input_ue_unconnectable":{}}},"widgets_values":["cartoon, anime, 3d render, cgi, low quality, blurry, glitch, distortion, text, watermark, subtitles, childish, ugly"]},{"id":25,"type":"UnetLoaderGGUF","pos":[-1916.5280142620743,-1092.6566939819752],"size":[270,58],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"unet_name","name":"unet_name","type":"COMBO","widget":{"name":"unet_name"},"link":null}],"outputs":[{"localized_name":"MODEL","name":"MODEL","type":"MODEL","links":[18]}],"title":"UnetLoaderGGUF (HIGH)","properties":{"cnr_id":"ComfyUI-GGUF","ver":"6ea2651e7df66d7585f6ffee804b20e92fb38b8a","Node name for S&R":"UnetLoaderGGUF","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.7.1"}},"widgets_values":["wan22I2VA14BGGUF_q8A14BHigh.gguf"]},{"id":2,"type":"UnetLoaderGGUF","pos":[-1617.1222952689902,-1087.7281134592963],"size":[270,58],"flags":{},"order":3,"mode":0,"inputs":[{"localized_name":"unet_name","name":"unet_name","type":"COMBO","widget":{"name":"unet_name"},"link":null}],"outputs":[{"localized_name":"MODEL","name":"MODEL","type":"MODEL","links":[1]}],"title":"UnetLoaderGGUF (LOW)","properties":{"cnr_id":"ComfyUI-GGUF","ver":"6ea2651e7df66d7585f6ffee804b20e92fb38b8a","Node name for S&R":"UnetLoaderGGUF","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.7.1"}},"widgets_values":["wan22I2VA14BGGUF_q8A14BLow.gguf"]},{"id":3,"type":"ModelSamplingSD3","pos":[-1616.550481037919,-977.7166614101166],"size":[270,58],"flags":{},"order":12,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":1},{"localized_name":"shift","name":"shift","type":"FLOAT","widget":{"name":"shift"},"link":null}],"outputs":[{"localized_name":"MODEL","name":"MODEL","type":"MODEL","links":[2]}],"title":"ModelSamplingSD3 (LOW)","properties":{"cnr_id":"comfy-core","ver":"0.18.1","Node name for S&R":"ModelSamplingSD3","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.7.1"}},"widgets_values":[8]},{"id":6,"type":"VAELoader","pos":[-1922.4829447490238,-844.0946836581325],"size":[270,58],"flags":{},"order":4,"mode":0,"inputs":[{"localized_name":"vae_name","name":"vae_name","type":"COMBO","widget":{"name":"vae_name"},"link":null}],"outputs":[{"localized_name":"VAE","name":"VAE","type":"VAE","links":[3]}],"properties":{"cnr_id":"comfy-core","ver":"0.18.1","Node name for S&R":"VAELoader","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.7.1"}},"widgets_values":["wan_2.1_vae.safetensors"]},{"id":9,"type":"CLIPLoader","pos":[-1917.2859432138207,-727.9395534287916],"size":[270,106],"flags":{},"order":5,"mode":0,"inputs":[{"localized_name":"clip_name","name":"clip_name","type":"COMBO","widget":{"name":"clip_name"},"link":null},{"localized_name":"type","name":"type","type":"COMBO","widget":{"name":"type"},"link":null},{"localized_name":"device","name":"device","shape":7,"type":"COMBO","widget":{"name":"device"},"link":null}],"outputs":[{"localized_name":"CLIP","name":"CLIP","type":"CLIP","links":[4]}],"properties":{"cnr_id":"comfy-core","ver":"0.18.1","Node name for S&R":"CLIPLoader","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.7.1"}},"widgets_values":["umt5_xxl_fp8_e4m3fn_scaled.safetensors","wan","default"]},{"id":33,"type":"GetImageSize+","pos":[-1877.575261717702,389.551451014326],"size":[214.20001220703125,286],"flags":{"collapsed":false},"order":15,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"IMAGE","link":32}],"outputs":[{"localized_name":"width","name":"width","type":"INT","slot_index":0,"links":[34]},{"localized_name":"height","name":"height","type":"INT","slot_index":1,"links":[35]},{"localized_name":"count","name":"count","type":"INT","links":null}],"properties":{"cnr_id":"comfyui_essentials","ver":"33ff89fd354d8ec3ab6affb605a79a931b445d99","Node name for S&R":"GetImageSize+","ue_properties":{"version":"7.5.1","widget_ue_connectable":{},"input_ue_unconnectable":{}}},"widgets_values":[],"shape":1},{"id":32,"type":"PreviewImage","pos":[-1875.265799994794,743.1682544066871],"size":[210,320],"flags":{},"order":14,"mode":0,"inputs":[{"localized_name":"images","name":"images","type":"IMAGE","link":28}],"outputs":[],"properties":{"cnr_id":"comfy-core","ver":"0.3.71","Node name for S&R":"PreviewImage","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.5.1"}},"widgets_values":[]},{"id":1,"type":"LoadImage","pos":[-1910.4973848236225,-564.0914659959175],"size":[282.798828125,314.00000000000006],"flags":{},"order":6,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[31]},{"localized_name":"MASK","name":"MASK","type":"MASK","links":null}],"properties":{"cnr_id":"comfy-core","ver":"0.18.1","Node name for S&R":"LoadImage","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.7.1"}},"widgets_values":["horse.jpg","image"]},{"id":37,"type":"Text _O","pos":[-2418.4334686284474,-1088.618486249721],"size":[420,160],"flags":{},"order":7,"mode":0,"inputs":[{"localized_name":"text","name":"text","type":"STRING","widget":{"name":"text"},"link":null}],"outputs":[{"localized_name":"STRING","name":"STRING","type":"STRING","links":[41]}],"title":"MAIN PROMPT","properties":{"cnr_id":"qol","ver":"f09d10dea0afbd3984a284acf8f0913a634e36ec","Node name for S&R":"Text _O","ue_properties":{"version":"7.5.1","widget_ue_connectable":{"text":true},"input_ue_unconnectable":{}}},"widgets_values":["Ultra-realistic cinematic POV video, warm natural daylight, Young 18-year-old woman , Fixed camera, sharp focus, 4K quality."],"shape":1},{"id":11,"type":"VHS_VideoCombine","pos":[-1012.1122725438876,-743.0001261700288],"size":[735.8333163038824,262],"flags":{},"order":17,"mode":0,"inputs":[{"localized_name":"images","name":"images","type":"IMAGE","link":6},{"localized_name":"audio","name":"audio","shape":7,"type":"AUDIO","link":null},{"localized_name":"meta_batch","name":"meta_batch","shape":7,"type":"VHS_BatchManager","link":null},{"localized_name":"vae","name":"vae","shape":7,"type":"VAE","link":null},{"localized_name":"frame_rate","name":"frame_rate","type":"FLOAT","widget":{"name":"frame_rate"},"link":null},{"localized_name":"loop_count","name":"loop_count","type":"INT","widget":{"name":"loop_count"},"link":null},{"localized_name":"filename_prefix","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null},{"localized_name":"format","name":"format","type":"COMBO","widget":{"name":"format"},"link":null},{"localized_name":"pingpong","name":"pingpong","type":"BOOLEAN","widget":{"name":"pingpong"},"link":null},{"localized_name":"save_output","name":"save_output","type":"BOOLEAN","widget":{"name":"save_output"},"link":null},{"name":"lossless","type":"BOOLEAN","widget":{"name":"lossless"},"link":null}],"outputs":[{"localized_name":"Filenames","name":"Filenames","type":"VHS_FILENAMES","links":null}],"properties":{"cnr_id":"comfyui-videohelpersuite","ver":"449839959f0153fb8a57211a9364c55163935ca9","Node name for S&R":"VHS_VideoCombine","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.7.1"}},"widgets_values":{"frame_rate":24,"loop_count":0,"filename_prefix":"WanLooper","format":"image/webp","lossless":true,"pingpong":false,"save_output":true,"videopreview":{"hidden":false,"paused":false,"params":{"filename":"WanLooper_00016.webp","subfolder":"","type":"output","format":"image/webp","frame_rate":24,"workflow":"WanLooper_00016.png","fullpath":"D:\\ComfyUI-Easy-Install\\ComfyUI\\output\\WanLooper_00016.webp"}}}},{"id":13,"type":"I2VLoopHL","pos":[-2896.9542920521135,-605.3922697639429],"size":[400,208],"flags":{},"order":8,"mode":0,"inputs":[{"localized_name":"prompt","name":"prompt","type":"STRING","widget":{"name":"prompt"},"link":null},{"localized_name":"frames","name":"frames","type":"INT","widget":{"name":"frames"},"link":null},{"localized_name":"lora_high","name":"lora_high","shape":7,"type":"COMBO","widget":{"name":"lora_high"},"link":null},{"localized_name":"lora_high_strength","name":"lora_high_strength","shape":7,"type":"FLOAT","widget":{"name":"lora_high_strength"},"link":null},{"localized_name":"lora_low","name":"lora_low","shape":7,"type":"COMBO","widget":{"name":"lora_low"},"link":null},{"localized_name":"lora_low_strength","name":"lora_low_strength","shape":7,"type":"FLOAT","widget":{"name":"lora_low_strength"},"link":null}],"outputs":[{"localized_name":"loop_config","name":"loop_config","type":"I2V_LOOP_HL","links":[42]}],"properties":{"Node name for S&R":"NativeLoop","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.7.1"}},"widgets_values":["a woman on a horse rides through the field",41,"None",1,"None",1]},{"id":14,"type":"I2VLoopHL","pos":[-2890.116792553664,-334.6626400721885],"size":[400,208],"flags":{},"order":9,"mode":0,"inputs":[{"localized_name":"prompt","name":"prompt","type":"STRING","widget":{"name":"prompt"},"link":null},{"localized_name":"frames","name":"frames","type":"INT","widget":{"name":"frames"},"link":null},{"localized_name":"lora_high","name":"lora_high","shape":7,"type":"COMBO","widget":{"name":"lora_high"},"link":null},{"localized_name":"lora_high_strength","name":"lora_high_strength","shape":7,"type":"FLOAT","widget":{"name":"lora_high_strength"},"link":null},{"localized_name":"lora_low","name":"lora_low","shape":7,"type":"COMBO","widget":{"name":"lora_low"},"link":null},{"localized_name":"lora_low_strength","name":"lora_low_strength","shape":7,"type":"FLOAT","widget":{"name":"lora_low_strength"},"link":null}],"outputs":[{"localized_name":"loop_config","name":"loop_config","type":"I2V_LOOP_HL","links":[43]}],"properties":{"Node name for S&R":"NativeLoop","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.7.1"}},"widgets_values":["she pulls on the reigns and slows the horse, she holds her hand up to block the sun as she looks around",41,"None",1,"None",1]},{"id":31,"type":"ImageResizeKJv2","pos":[-1908.8098450744117,-173.287117016489],"size":[270,498],"flags":{},"order":13,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"IMAGE","link":31},{"localized_name":"mask","name":"mask","shape":7,"type":"MASK","link":null},{"localized_name":"width","name":"width","type":"INT","widget":{"name":"width"},"link":null},{"localized_name":"height","name":"height","type":"INT","widget":{"name":"height"},"link":null},{"localized_name":"upscale_method","name":"upscale_method","type":"COMBO","widget":{"name":"upscale_method"},"link":null},{"localized_name":"keep_proportion","name":"keep_proportion","type":"COMBO","widget":{"name":"keep_proportion"},"link":null},{"localized_name":"pad_color","name":"pad_color","type":"STRING","widget":{"name":"pad_color"},"link":null},{"localized_name":"crop_position","name":"crop_position","type":"COMBO","widget":{"name":"crop_position"},"link":null},{"localized_name":"divisible_by","name":"divisible_by","type":"INT","widget":{"name":"divisible_by"},"link":null},{"localized_name":"device","name":"device","shape":7,"type":"COMBO","widget":{"name":"device"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[28,32,33]},{"localized_name":"width","name":"width","type":"INT","links":null},{"localized_name":"height","name":"height","type":"INT","links":null},{"localized_name":"mask","name":"mask","type":"MASK","links":null}],"properties":{"cnr_id":"comfyui-kjnodes","ver":"4dfb85dcc52e4315c33170d97bb987baa46d128b","Node name for S&R":"ImageResizeKJv2","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.5.1"}},"widgets_values":[480,848,"area","crop","0, 0, 0","center",2,"gpu"]},{"id":10,"type":"I2VLooperHL","pos":[-1481.4238227153735,-740.6458917849172],"size":[400,1490.000000000001],"flags":{},"order":16,"mode":0,"inputs":[{"localized_name":"model_high","name":"model_high","type":"MODEL","link":19},{"localized_name":"model_low","name":"model_low","type":"MODEL","link":2},{"localized_name":"vae","name":"vae","type":"VAE","link":3},{"localized_name":"clip","name":"clip","type":"CLIP","link":4},{"localized_name":"clip_vision","name":"clip_vision","type":"CLIP_VISION","link":25},{"localized_name":"start_image","name":"start_image","type":"IMAGE","link":33},{"localized_name":"positive_prompt","name":"positive_prompt","shape":7,"type":"STRING","link":41},{"localized_name":"negative_prompt","name":"negative_prompt","shape":7,"type":"STRING","link":21},{"localized_name":"loop_1","name":"loop_1","shape":7,"type":"I2V_LOOP_HL","link":42},{"localized_name":"loop_2","name":"loop_2","shape":7,"type":"I2V_LOOP_HL","link":43},{"localized_name":"loop_3","name":"loop_3","shape":7,"type":"I2V_LOOP_HL","link":null},{"localized_name":"loop_4","name":"loop_4","shape":7,"type":"I2V_LOOP_HL","link":null},{"localized_name":"loop_5","name":"loop_5","shape":7,"type":"I2V_LOOP_HL","link":null},{"localized_name":"loop_6","name":"loop_6","shape":7,"type":"I2V_LOOP_HL","link":null},{"localized_name":"loop_7","name":"loop_7","shape":7,"type":"I2V_LOOP_HL","link":null},{"localized_name":"loop_8","name":"loop_8","shape":7,"type":"I2V_LOOP_HL","link":null},{"localized_name":"loop_9","name":"loop_9","shape":7,"type":"I2V_LOOP_HL","link":null},{"localized_name":"loop_10","name":"loop_10","shape":7,"type":"I2V_LOOP_HL","link":null},{"localized_name":"width","name":"width","type":"INT","widget":{"name":"width"},"link":34},{"localized_name":"height","name":"height","type":"INT","widget":{"name":"height"},"link":35},{"localized_name":"steps","name":"steps","type":"INT","widget":{"name":"steps"},"link":null},{"localized_name":"cfg","name":"cfg","type":"FLOAT","widget":{"name":"cfg"},"link":null},{"localized_name":"split_step","name":"split_step","type":"INT","widget":{"name":"split_step"},"link":null},{"localized_name":"sampler_name","name":"sampler_name","type":"COMBO","widget":{"name":"sampler_name"},"link":null},{"localized_name":"scheduler","name":"scheduler","type":"COMBO","widget":{"name":"scheduler"},"link":null},{"localized_name":"seed","name":"seed","type":"INT","widget":{"name":"seed"},"link":null}],"outputs":[{"localized_name":"full_video","name":"full_video","type":"IMAGE","links":[6]},{"localized_name":"used_prompts","name":"used_prompts","type":"STRING","links":null}],"properties":{"Node name for S&R":"NativeLooper","ue_properties":{"widget_ue_connectable":{},"input_ue_unconnectable":{},"version":"7.7.1"}},"widgets_values":[832,480,30,2,20,"euler","simple",514123557033578,"randomize"]},{"id":38,"type":"MarkdownNote","pos":[-155.52147313434827,-579.5740337106699],"size":[568.4984285500423,486.94065479356266],"flags":{},"order":10,"mode":0,"inputs":[],"outputs":[],"properties":{"ue_properties":{"widget_ue_connectable":{},"version":"7.7.1","input_ue_unconnectable":{}}},"widgets_values":[""],"color":"#432","bgcolor":"#653"}],"links":[[1,2,0,3,0,"MODEL"],[2,3,0,10,1,"MODEL"],[3,6,0,10,2,"VAE"],[4,9,0,10,3,"CLIP"],[6,10,0,11,0,"IMAGE"],[18,25,0,26,0,"MODEL"],[19,26,0,10,0,"MODEL"],[21,28,0,10,7,"STRING"],[25,29,0,10,4,"CLIP_VISION"],[28,31,0,32,0,"IMAGE"],[31,1,0,31,0,"IMAGE"],[32,31,0,33,0,"IMAGE"],[33,31,0,10,5,"IMAGE"],[34,33,0,10,18,"INT"],[35,33,1,10,19,"INT"],[41,37,0,10,6,"STRING"],[42,13,0,10,8,"I2V_LOOP_HL"],[43,14,0,10,9,"I2V_LOOP_HL"]],"groups":[],"config":{},"extra":{"ue_links":[],"ds":{"scale":0.9732446862175901,"offset":[1678.2376517359612,1024.363100210845]},"links_added_by_ue":[]},"version":0.4} \ No newline at end of file