Download custom_nodes/dolphin_nodes/wan_i2v_continuity.py from bjooo/tutorials: direct link, hf CLI and curl.
- Browser
- Download file 5.71 kB
-
https://huggingface.co/bjooo/tutorials/resolve/main/custom_nodes/dolphin_nodes/wan_i2v_continuity.py
- Command line
-
hf download hf://bjooo/tutorials/custom_nodes/dolphin_nodes/wan_i2v_continuity.py
-
curl -L -o wan_i2v_continuity.py https://huggingface.co/bjooo/tutorials/resolve/main/custom_nodes/dolphin_nodes/wan_i2v_continuity.py
5.71 kB
| import torch | |
| import comfy.model_management | |
| import comfy.utils | |
| import node_helpers | |
| class WanI2VContinuity_Dolphin: | |
| """ | |
| Wan2.2 I2V ๋ ธ๋ โ ํด๋ฆฝ ์ด์ด๋ถ์ด๊ธฐ(์ฐ์์ฑ)์ฉ. | |
| ๊ธฐ์กด ์ ๊ทผ(WanImageToVideoSVIPro)์ ๋ฌธ์ : anchor ์ด๋ฏธ์ง๋ฅผ ๋ฐ๋ก ์ธ์ฝ๋ฉํ latent์, | |
| ์ด์ ํด๋ฆฝ latent์ ๋ท๋ถ๋ถ์ ๊ทธ๋ฅ ์๋ผ๋ธ ์กฐ๊ฐ(motion_latent)์ ์ด์ด๋ถ์๋ค. WAN VAE๋ | |
| ์ธ๊ณผ์ (causal) ์๊ฐ ์์ถ์ด๋ผ ๊ฐ latent ํ๋ ์์ด "๊ทธ ์์ ๊น์ง์ ์ํ์ค ๋ฌธ๋งฅ"์ ์ ์ ๋ก | |
| ์ธ์ฝ๋ฉ๋๋๋ฐ, ์๋ก ๋ค๋ฅธ ๋ฌธ๋งฅ์์ ๋์จ latent ์กฐ๊ฐ์ ์ด์ด๋ถ์ด๋ฉด ํต๊ณ์ ์ผ๋ก ๋ถ์ฐ์์ด๋ผ | |
| ํ์ง์ด ํ๊ณ ๋์ ์ฐ์์ฑ๋ ๋ชป ์ด๋ ธ๋ค. | |
| ์ด ๋ ธ๋๋ ComfyUI ์ฝ์ด `WanImageToVideo`์ ๊ฒ์ฆ๋ ๋ฐฉ์์ ๋ฐ๋ฅธ๋ค: | |
| ์ด์ ํด๋ฆฝ์์ ๋์ฝ๋ฉํ ๋ง์ง๋ง Nํ๋ ์(ํฝ์ )์ start_image๋ก ๋ฐ์, ํ์(0.5) filler์ | |
| ํจ๊ป ํ๋์ ์ํ์ค๋ก ๋ง๋ค์ด **ํ ๋ฒ์ ํต์ง VAE ์ธ์ฝ๋ฉ**์ ํ๋ค. ๊ทธ๋์ known ๊ตฌ๊ฐ๊ณผ | |
| ์์ฑ ๊ตฌ๊ฐ์ latent ๋ฌธ๋งฅ์ด ์์ฐ์ค๋ฝ๊ฒ ์ด์ด์ง๋ค. ๋ง์คํฌ๋ ํ๋ ์์โlatent์ ๋ณํ์ | |
| ์ ํํ ํด์ known ํ๋ ์ ์ ์ฒด๋ฅผ ๊ณ ์ ํ๋ค. | |
| ์ฌ๊ธฐ์ PainterI2V์ motion_amplitude(์ฌ๋ก์ฐ๋ชจ์ ๋ฐฉ์ง)๋ฅผ ์ด์ํ๋ค. ๋จ known ํ๋ ์์ | |
| ๊ฑด๋๋ฆฌ์ง ์๊ณ , ๋ค์ ๋ถ๋ filler(์์ฑ ๋์) latent๋ง ๋ง์ง๋ง known latent ๋๋น ์ฐจ์ด๋ฅผ | |
| ์ฆํญํ๋ค. motion_amplitude=1.0์ด๋ฉด ์ฝ์ด WanImageToVideo์ ๋์ผํ๊ฒ ๋์ํ๋ค. | |
| """ | |
| def INPUT_TYPES(cls): | |
| return { | |
| "required": { | |
| "positive": ("CONDITIONING",), | |
| "negative": ("CONDITIONING",), | |
| "vae": ("VAE",), | |
| "width": ("INT", {"default": 832, "min": 16, "max": 4096, "step": 16}), | |
| "height": ("INT", {"default": 480, "min": 16, "max": 4096, "step": 16}), | |
| "length": ("INT", {"default": 81, "min": 1, "max": 9999, "step": 4}), | |
| "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}), | |
| "motion_amplitude": ("FLOAT", {"default": 1.0, "min": 1.0, "max": 2.0, "step": 0.05, | |
| "tooltip": "1.0=์ฝ์ด WanImageToVideo์ ๋์ผ. >1.0์ด๋ฉด filler ๊ตฌ๊ฐ์ ์ฌ๋ก์ฐ๋ชจ์ ๋ฐฉ์ง ์ฆํญ."}), | |
| }, | |
| "optional": { | |
| "clip_vision_output": ("CLIP_VISION_OUTPUT",), | |
| "start_image": ("IMAGE", {"tooltip": "known ํ๋ ์(๋ค). ์ด์ ํด๋ฆฝ์ ๋์ฝ๋ฉ๋ ๋ง์ง๋ง Nํ๋ ์์ ๋ฃ์ผ๋ฉด ์ฐ์์ฑ์ด ์๊น. 1์ฅ์ด๋ฉด ์ผ๋ฐ I2V."}), | |
| }, | |
| } | |
| RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT") | |
| RETURN_NAMES = ("positive", "negative", "latent") | |
| FUNCTION = "encode" | |
| CATEGORY = "Dolphin Node/Wan Video" | |
| def encode(self, positive, negative, vae, width, height, length, batch_size, | |
| motion_amplitude=1.0, clip_vision_output=None, start_image=None): | |
| latent = torch.zeros( | |
| [batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], | |
| device=comfy.model_management.intermediate_device(), | |
| ) | |
| if start_image is not None: | |
| start_image = comfy.utils.common_upscale( | |
| start_image[:length].movedim(-1, 1), width, height, "bilinear", "center" | |
| ).movedim(1, -1) | |
| n_known_frames = start_image.shape[0] | |
| # known ํ๋ ์ ๋ค๋ฅผ ํ์(0.5) filler๋ก ์ฑ์ด ๋ค ํต์ง๋ก ์ธ์ฝ๋ฉ (์ฝ์ด ๋ฐฉ์) | |
| image = torch.ones( | |
| (length, height, width, start_image.shape[-1]), | |
| device=start_image.device, dtype=start_image.dtype, | |
| ) * 0.5 | |
| image[:n_known_frames] = start_image | |
| concat_latent_image = vae.encode(image[:, :, :, :3]) | |
| # ํ๋ ์์ -> latent์ (WAN VAE 4x ์๊ฐ ์์ถ) | |
| n_known_latents = ((n_known_frames - 1) // 4) + 1 | |
| # motion_amplitude: filler(์์ฑ ๋์) latent๋ง ์ฆํญ. known ํ๋ ์์ ๊ทธ๋๋ก. | |
| if motion_amplitude > 1.0 and concat_latent_image.shape[2] > n_known_latents: | |
| base_latent = concat_latent_image[:, :, n_known_latents - 1:n_known_latents] | |
| filler = concat_latent_image[:, :, n_known_latents:] | |
| diff = filler - base_latent | |
| diff_mean = diff.mean(dim=(1, 3, 4), keepdim=True) | |
| diff_centered = diff - diff_mean | |
| scaled = base_latent + diff_centered * motion_amplitude + diff_mean | |
| scaled = torch.clamp(scaled, -6, 6) | |
| concat_latent_image = torch.cat( | |
| [concat_latent_image[:, :, :n_known_latents], scaled], dim=2 | |
| ) | |
| mask = torch.ones( | |
| (1, 1, latent.shape[2], concat_latent_image.shape[-2], concat_latent_image.shape[-1]), | |
| device=start_image.device, dtype=start_image.dtype, | |
| ) | |
| mask[:, :, :n_known_latents] = 0.0 | |
| positive = node_helpers.conditioning_set_values( | |
| positive, {"concat_latent_image": concat_latent_image, "concat_mask": mask} | |
| ) | |
| negative = node_helpers.conditioning_set_values( | |
| negative, {"concat_latent_image": concat_latent_image, "concat_mask": mask} | |
| ) | |
| if clip_vision_output is not None: | |
| positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output}) | |
| negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output}) | |
| return (positive, negative, {"samples": latent}) | |