tutorials / custom_nodes /dolphin_nodes /wan_i2v_continuity.py
bjooo's picture
Upload folder using huggingface_hub
9c98083 verified
Raw History Blame Contribute Delete
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์™€ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.
"""
@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})