File size: 5,708 Bytes
9c98083 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | 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์ ๋์ผํ๊ฒ ๋์ํ๋ค.
"""
@classmethod
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})
|