mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2026-09-06 17:17:06 +08:00
Merge branch 'master' into attention-select
This commit is contained in:
commit
0a86b5bc74
@ -606,6 +606,11 @@ class HunyuanImage21(LatentFormat):
|
|||||||
|
|
||||||
latent_rgb_factors_bias = [0.0007, -0.0256, -0.0206]
|
latent_rgb_factors_bias = [0.0007, -0.0256, -0.0206]
|
||||||
|
|
||||||
|
class HunyuanImage21Refiner(LatentFormat):
|
||||||
|
latent_channels = 64
|
||||||
|
latent_dimensions = 3
|
||||||
|
scale_factor = 1.03682
|
||||||
|
|
||||||
class Hunyuan3Dv2(LatentFormat):
|
class Hunyuan3Dv2(LatentFormat):
|
||||||
latent_channels = 64
|
latent_channels = 64
|
||||||
latent_dimensions = 1
|
latent_dimensions = 1
|
||||||
|
|||||||
@ -279,6 +279,7 @@ class HunyuanVideo(nn.Module):
|
|||||||
guidance: Tensor = None,
|
guidance: Tensor = None,
|
||||||
guiding_frame_index=None,
|
guiding_frame_index=None,
|
||||||
ref_latent=None,
|
ref_latent=None,
|
||||||
|
disable_time_r=False,
|
||||||
control=None,
|
control=None,
|
||||||
transformer_options={},
|
transformer_options={},
|
||||||
) -> Tensor:
|
) -> Tensor:
|
||||||
@ -289,7 +290,7 @@ class HunyuanVideo(nn.Module):
|
|||||||
img = self.img_in(img)
|
img = self.img_in(img)
|
||||||
vec = self.time_in(timestep_embedding(timesteps, 256, time_factor=1.0).to(img.dtype))
|
vec = self.time_in(timestep_embedding(timesteps, 256, time_factor=1.0).to(img.dtype))
|
||||||
|
|
||||||
if self.time_r_in is not None:
|
if (self.time_r_in is not None) and (not disable_time_r):
|
||||||
w = torch.where(transformer_options['sigmas'][0] == transformer_options['sample_sigmas'])[0] # This most likely could be improved
|
w = torch.where(transformer_options['sigmas'][0] == transformer_options['sample_sigmas'])[0] # This most likely could be improved
|
||||||
if len(w) > 0:
|
if len(w) > 0:
|
||||||
timesteps_r = transformer_options['sample_sigmas'][w[0] + 1]
|
timesteps_r = transformer_options['sample_sigmas'][w[0] + 1]
|
||||||
@ -429,14 +430,14 @@ class HunyuanVideo(nn.Module):
|
|||||||
img_ids[:, :, 1] = img_ids[:, :, 1] + torch.linspace(0, w_len - 1, steps=w_len, device=x.device, dtype=x.dtype).unsqueeze(0)
|
img_ids[:, :, 1] = img_ids[:, :, 1] + torch.linspace(0, w_len - 1, steps=w_len, device=x.device, dtype=x.dtype).unsqueeze(0)
|
||||||
return repeat(img_ids, "h w c -> b (h w) c", b=bs)
|
return repeat(img_ids, "h w c -> b (h w) c", b=bs)
|
||||||
|
|
||||||
def forward(self, x, timestep, context, y=None, txt_byt5=None, guidance=None, attention_mask=None, guiding_frame_index=None, ref_latent=None, control=None, transformer_options={}, **kwargs):
|
def forward(self, x, timestep, context, y=None, txt_byt5=None, guidance=None, attention_mask=None, guiding_frame_index=None, ref_latent=None, disable_time_r=False, control=None, transformer_options={}, **kwargs):
|
||||||
return comfy.patcher_extension.WrapperExecutor.new_class_executor(
|
return comfy.patcher_extension.WrapperExecutor.new_class_executor(
|
||||||
self._forward,
|
self._forward,
|
||||||
self,
|
self,
|
||||||
comfy.patcher_extension.get_all_wrappers(comfy.patcher_extension.WrappersMP.DIFFUSION_MODEL, transformer_options)
|
comfy.patcher_extension.get_all_wrappers(comfy.patcher_extension.WrappersMP.DIFFUSION_MODEL, transformer_options)
|
||||||
).execute(x, timestep, context, y, txt_byt5, guidance, attention_mask, guiding_frame_index, ref_latent, control, transformer_options, **kwargs)
|
).execute(x, timestep, context, y, txt_byt5, guidance, attention_mask, guiding_frame_index, ref_latent, disable_time_r, control, transformer_options, **kwargs)
|
||||||
|
|
||||||
def _forward(self, x, timestep, context, y=None, txt_byt5=None, guidance=None, attention_mask=None, guiding_frame_index=None, ref_latent=None, control=None, transformer_options={}, **kwargs):
|
def _forward(self, x, timestep, context, y=None, txt_byt5=None, guidance=None, attention_mask=None, guiding_frame_index=None, ref_latent=None, disable_time_r=False, control=None, transformer_options={}, **kwargs):
|
||||||
bs = x.shape[0]
|
bs = x.shape[0]
|
||||||
if len(self.patch_size) == 3:
|
if len(self.patch_size) == 3:
|
||||||
img_ids = self.img_ids(x)
|
img_ids = self.img_ids(x)
|
||||||
@ -444,5 +445,5 @@ class HunyuanVideo(nn.Module):
|
|||||||
else:
|
else:
|
||||||
img_ids = self.img_ids_2d(x)
|
img_ids = self.img_ids_2d(x)
|
||||||
txt_ids = torch.zeros((bs, context.shape[1], 2), device=x.device, dtype=x.dtype)
|
txt_ids = torch.zeros((bs, context.shape[1], 2), device=x.device, dtype=x.dtype)
|
||||||
out = self.forward_orig(x, img_ids, context, txt_ids, attention_mask, timestep, y, txt_byt5, guidance, guiding_frame_index, ref_latent, control=control, transformer_options=transformer_options)
|
out = self.forward_orig(x, img_ids, context, txt_ids, attention_mask, timestep, y, txt_byt5, guidance, guiding_frame_index, ref_latent, disable_time_r=disable_time_r, control=control, transformer_options=transformer_options)
|
||||||
return out
|
return out
|
||||||
|
|||||||
268
comfy/ldm/hunyuan_video/vae_refiner.py
Normal file
268
comfy/ldm/hunyuan_video/vae_refiner.py
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
import torch
|
||||||
|
import torch.nn as nn
|
||||||
|
import torch.nn.functional as F
|
||||||
|
from comfy.ldm.modules.diffusionmodules.model import ResnetBlock, AttnBlock, VideoConv3d
|
||||||
|
import comfy.ops
|
||||||
|
import comfy.ldm.models.autoencoder
|
||||||
|
ops = comfy.ops.disable_weight_init
|
||||||
|
|
||||||
|
class RMS_norm(nn.Module):
|
||||||
|
def __init__(self, dim):
|
||||||
|
super().__init__()
|
||||||
|
shape = (dim, 1, 1, 1)
|
||||||
|
self.scale = dim**0.5
|
||||||
|
self.gamma = nn.Parameter(torch.empty(shape))
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return F.normalize(x, dim=1) * self.scale * self.gamma
|
||||||
|
|
||||||
|
class DnSmpl(nn.Module):
|
||||||
|
def __init__(self, ic, oc, tds=True):
|
||||||
|
super().__init__()
|
||||||
|
fct = 2 * 2 * 2 if tds else 1 * 2 * 2
|
||||||
|
assert oc % fct == 0
|
||||||
|
self.conv = VideoConv3d(ic, oc // fct, kernel_size=3)
|
||||||
|
|
||||||
|
self.tds = tds
|
||||||
|
self.gs = fct * ic // oc
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
r1 = 2 if self.tds else 1
|
||||||
|
h = self.conv(x)
|
||||||
|
|
||||||
|
if self.tds:
|
||||||
|
hf = h[:, :, :1, :, :]
|
||||||
|
b, c, f, ht, wd = hf.shape
|
||||||
|
hf = hf.reshape(b, c, f, ht // 2, 2, wd // 2, 2)
|
||||||
|
hf = hf.permute(0, 4, 6, 1, 2, 3, 5)
|
||||||
|
hf = hf.reshape(b, 2 * 2 * c, f, ht // 2, wd // 2)
|
||||||
|
hf = torch.cat([hf, hf], dim=1)
|
||||||
|
|
||||||
|
hn = h[:, :, 1:, :, :]
|
||||||
|
b, c, frms, ht, wd = hn.shape
|
||||||
|
nf = frms // r1
|
||||||
|
hn = hn.reshape(b, c, nf, r1, ht // 2, 2, wd // 2, 2)
|
||||||
|
hn = hn.permute(0, 3, 5, 7, 1, 2, 4, 6)
|
||||||
|
hn = hn.reshape(b, r1 * 2 * 2 * c, nf, ht // 2, wd // 2)
|
||||||
|
|
||||||
|
h = torch.cat([hf, hn], dim=2)
|
||||||
|
|
||||||
|
xf = x[:, :, :1, :, :]
|
||||||
|
b, ci, f, ht, wd = xf.shape
|
||||||
|
xf = xf.reshape(b, ci, f, ht // 2, 2, wd // 2, 2)
|
||||||
|
xf = xf.permute(0, 4, 6, 1, 2, 3, 5)
|
||||||
|
xf = xf.reshape(b, 2 * 2 * ci, f, ht // 2, wd // 2)
|
||||||
|
B, C, T, H, W = xf.shape
|
||||||
|
xf = xf.view(B, h.shape[1], self.gs // 2, T, H, W).mean(dim=2)
|
||||||
|
|
||||||
|
xn = x[:, :, 1:, :, :]
|
||||||
|
b, ci, frms, ht, wd = xn.shape
|
||||||
|
nf = frms // r1
|
||||||
|
xn = xn.reshape(b, ci, nf, r1, ht // 2, 2, wd // 2, 2)
|
||||||
|
xn = xn.permute(0, 3, 5, 7, 1, 2, 4, 6)
|
||||||
|
xn = xn.reshape(b, r1 * 2 * 2 * ci, nf, ht // 2, wd // 2)
|
||||||
|
B, C, T, H, W = xn.shape
|
||||||
|
xn = xn.view(B, h.shape[1], self.gs, T, H, W).mean(dim=2)
|
||||||
|
sc = torch.cat([xf, xn], dim=2)
|
||||||
|
else:
|
||||||
|
b, c, frms, ht, wd = h.shape
|
||||||
|
nf = frms // r1
|
||||||
|
h = h.reshape(b, c, nf, r1, ht // 2, 2, wd // 2, 2)
|
||||||
|
h = h.permute(0, 3, 5, 7, 1, 2, 4, 6)
|
||||||
|
h = h.reshape(b, r1 * 2 * 2 * c, nf, ht // 2, wd // 2)
|
||||||
|
|
||||||
|
b, ci, frms, ht, wd = x.shape
|
||||||
|
nf = frms // r1
|
||||||
|
sc = x.reshape(b, ci, nf, r1, ht // 2, 2, wd // 2, 2)
|
||||||
|
sc = sc.permute(0, 3, 5, 7, 1, 2, 4, 6)
|
||||||
|
sc = sc.reshape(b, r1 * 2 * 2 * ci, nf, ht // 2, wd // 2)
|
||||||
|
B, C, T, H, W = sc.shape
|
||||||
|
sc = sc.view(B, h.shape[1], self.gs, T, H, W).mean(dim=2)
|
||||||
|
|
||||||
|
return h + sc
|
||||||
|
|
||||||
|
|
||||||
|
class UpSmpl(nn.Module):
|
||||||
|
def __init__(self, ic, oc, tus=True):
|
||||||
|
super().__init__()
|
||||||
|
fct = 2 * 2 * 2 if tus else 1 * 2 * 2
|
||||||
|
self.conv = VideoConv3d(ic, oc * fct, kernel_size=3)
|
||||||
|
|
||||||
|
self.tus = tus
|
||||||
|
self.rp = fct * oc // ic
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
r1 = 2 if self.tus else 1
|
||||||
|
h = self.conv(x)
|
||||||
|
|
||||||
|
if self.tus:
|
||||||
|
hf = h[:, :, :1, :, :]
|
||||||
|
b, c, f, ht, wd = hf.shape
|
||||||
|
nc = c // (2 * 2)
|
||||||
|
hf = hf.reshape(b, 2, 2, nc, f, ht, wd)
|
||||||
|
hf = hf.permute(0, 3, 4, 5, 1, 6, 2)
|
||||||
|
hf = hf.reshape(b, nc, f, ht * 2, wd * 2)
|
||||||
|
hf = hf[:, : hf.shape[1] // 2]
|
||||||
|
|
||||||
|
hn = h[:, :, 1:, :, :]
|
||||||
|
b, c, frms, ht, wd = hn.shape
|
||||||
|
nc = c // (r1 * 2 * 2)
|
||||||
|
hn = hn.reshape(b, r1, 2, 2, nc, frms, ht, wd)
|
||||||
|
hn = hn.permute(0, 4, 5, 1, 6, 2, 7, 3)
|
||||||
|
hn = hn.reshape(b, nc, frms * r1, ht * 2, wd * 2)
|
||||||
|
|
||||||
|
h = torch.cat([hf, hn], dim=2)
|
||||||
|
|
||||||
|
xf = x[:, :, :1, :, :]
|
||||||
|
b, ci, f, ht, wd = xf.shape
|
||||||
|
xf = xf.repeat_interleave(repeats=self.rp // 2, dim=1)
|
||||||
|
b, c, f, ht, wd = xf.shape
|
||||||
|
nc = c // (2 * 2)
|
||||||
|
xf = xf.reshape(b, 2, 2, nc, f, ht, wd)
|
||||||
|
xf = xf.permute(0, 3, 4, 5, 1, 6, 2)
|
||||||
|
xf = xf.reshape(b, nc, f, ht * 2, wd * 2)
|
||||||
|
|
||||||
|
xn = x[:, :, 1:, :, :]
|
||||||
|
xn = xn.repeat_interleave(repeats=self.rp, dim=1)
|
||||||
|
b, c, frms, ht, wd = xn.shape
|
||||||
|
nc = c // (r1 * 2 * 2)
|
||||||
|
xn = xn.reshape(b, r1, 2, 2, nc, frms, ht, wd)
|
||||||
|
xn = xn.permute(0, 4, 5, 1, 6, 2, 7, 3)
|
||||||
|
xn = xn.reshape(b, nc, frms * r1, ht * 2, wd * 2)
|
||||||
|
sc = torch.cat([xf, xn], dim=2)
|
||||||
|
else:
|
||||||
|
b, c, frms, ht, wd = h.shape
|
||||||
|
nc = c // (r1 * 2 * 2)
|
||||||
|
h = h.reshape(b, r1, 2, 2, nc, frms, ht, wd)
|
||||||
|
h = h.permute(0, 4, 5, 1, 6, 2, 7, 3)
|
||||||
|
h = h.reshape(b, nc, frms * r1, ht * 2, wd * 2)
|
||||||
|
|
||||||
|
sc = x.repeat_interleave(repeats=self.rp, dim=1)
|
||||||
|
b, c, frms, ht, wd = sc.shape
|
||||||
|
nc = c // (r1 * 2 * 2)
|
||||||
|
sc = sc.reshape(b, r1, 2, 2, nc, frms, ht, wd)
|
||||||
|
sc = sc.permute(0, 4, 5, 1, 6, 2, 7, 3)
|
||||||
|
sc = sc.reshape(b, nc, frms * r1, ht * 2, wd * 2)
|
||||||
|
|
||||||
|
return h + sc
|
||||||
|
|
||||||
|
class Encoder(nn.Module):
|
||||||
|
def __init__(self, in_channels, z_channels, block_out_channels, num_res_blocks,
|
||||||
|
ffactor_spatial, ffactor_temporal, downsample_match_channel=True, **_):
|
||||||
|
super().__init__()
|
||||||
|
self.z_channels = z_channels
|
||||||
|
self.block_out_channels = block_out_channels
|
||||||
|
self.num_res_blocks = num_res_blocks
|
||||||
|
self.conv_in = VideoConv3d(in_channels, block_out_channels[0], 3, 1, 1)
|
||||||
|
|
||||||
|
self.down = nn.ModuleList()
|
||||||
|
ch = block_out_channels[0]
|
||||||
|
depth = (ffactor_spatial >> 1).bit_length()
|
||||||
|
depth_temporal = ((ffactor_spatial // ffactor_temporal) >> 1).bit_length()
|
||||||
|
|
||||||
|
for i, tgt in enumerate(block_out_channels):
|
||||||
|
stage = nn.Module()
|
||||||
|
stage.block = nn.ModuleList([ResnetBlock(in_channels=ch if j == 0 else tgt,
|
||||||
|
out_channels=tgt,
|
||||||
|
temb_channels=0,
|
||||||
|
conv_op=VideoConv3d, norm_op=RMS_norm)
|
||||||
|
for j in range(num_res_blocks)])
|
||||||
|
ch = tgt
|
||||||
|
if i < depth:
|
||||||
|
nxt = block_out_channels[i + 1] if i + 1 < len(block_out_channels) and downsample_match_channel else ch
|
||||||
|
stage.downsample = DnSmpl(ch, nxt, tds=i >= depth_temporal)
|
||||||
|
ch = nxt
|
||||||
|
self.down.append(stage)
|
||||||
|
|
||||||
|
self.mid = nn.Module()
|
||||||
|
self.mid.block_1 = ResnetBlock(in_channels=ch, out_channels=ch, temb_channels=0, conv_op=VideoConv3d, norm_op=RMS_norm)
|
||||||
|
self.mid.attn_1 = AttnBlock(ch, conv_op=ops.Conv3d, norm_op=RMS_norm)
|
||||||
|
self.mid.block_2 = ResnetBlock(in_channels=ch, out_channels=ch, temb_channels=0, conv_op=VideoConv3d, norm_op=RMS_norm)
|
||||||
|
|
||||||
|
self.norm_out = RMS_norm(ch)
|
||||||
|
self.conv_out = VideoConv3d(ch, z_channels << 1, 3, 1, 1)
|
||||||
|
|
||||||
|
self.regul = comfy.ldm.models.autoencoder.DiagonalGaussianRegularizer()
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
x = x.unsqueeze(2)
|
||||||
|
x = self.conv_in(x)
|
||||||
|
|
||||||
|
for stage in self.down:
|
||||||
|
for blk in stage.block:
|
||||||
|
x = blk(x)
|
||||||
|
if hasattr(stage, 'downsample'):
|
||||||
|
x = stage.downsample(x)
|
||||||
|
|
||||||
|
x = self.mid.block_2(self.mid.attn_1(self.mid.block_1(x)))
|
||||||
|
|
||||||
|
b, c, t, h, w = x.shape
|
||||||
|
grp = c // (self.z_channels << 1)
|
||||||
|
skip = x.view(b, c // grp, grp, t, h, w).mean(2)
|
||||||
|
|
||||||
|
out = self.conv_out(F.silu(self.norm_out(x))) + skip
|
||||||
|
out = self.regul(out)[0]
|
||||||
|
|
||||||
|
out = torch.cat((out[:, :, :1], out), dim=2)
|
||||||
|
out = out.permute(0, 2, 1, 3, 4)
|
||||||
|
b, f_times_2, c, h, w = out.shape
|
||||||
|
out = out.reshape(b, f_times_2 // 2, 2 * c, h, w)
|
||||||
|
out = out.permute(0, 2, 1, 3, 4).contiguous()
|
||||||
|
return out
|
||||||
|
|
||||||
|
class Decoder(nn.Module):
|
||||||
|
def __init__(self, z_channels, out_channels, block_out_channels, num_res_blocks,
|
||||||
|
ffactor_spatial, ffactor_temporal, upsample_match_channel=True, **_):
|
||||||
|
super().__init__()
|
||||||
|
block_out_channels = block_out_channels[::-1]
|
||||||
|
self.z_channels = z_channels
|
||||||
|
self.block_out_channels = block_out_channels
|
||||||
|
self.num_res_blocks = num_res_blocks
|
||||||
|
|
||||||
|
ch = block_out_channels[0]
|
||||||
|
self.conv_in = VideoConv3d(z_channels, ch, 3)
|
||||||
|
|
||||||
|
self.mid = nn.Module()
|
||||||
|
self.mid.block_1 = ResnetBlock(in_channels=ch, out_channels=ch, temb_channels=0, conv_op=VideoConv3d, norm_op=RMS_norm)
|
||||||
|
self.mid.attn_1 = AttnBlock(ch, conv_op=ops.Conv3d, norm_op=RMS_norm)
|
||||||
|
self.mid.block_2 = ResnetBlock(in_channels=ch, out_channels=ch, temb_channels=0, conv_op=VideoConv3d, norm_op=RMS_norm)
|
||||||
|
|
||||||
|
self.up = nn.ModuleList()
|
||||||
|
depth = (ffactor_spatial >> 1).bit_length()
|
||||||
|
depth_temporal = (ffactor_temporal >> 1).bit_length()
|
||||||
|
|
||||||
|
for i, tgt in enumerate(block_out_channels):
|
||||||
|
stage = nn.Module()
|
||||||
|
stage.block = nn.ModuleList([ResnetBlock(in_channels=ch if j == 0 else tgt,
|
||||||
|
out_channels=tgt,
|
||||||
|
temb_channels=0,
|
||||||
|
conv_op=VideoConv3d, norm_op=RMS_norm)
|
||||||
|
for j in range(num_res_blocks + 1)])
|
||||||
|
ch = tgt
|
||||||
|
if i < depth:
|
||||||
|
nxt = block_out_channels[i + 1] if i + 1 < len(block_out_channels) and upsample_match_channel else ch
|
||||||
|
stage.upsample = UpSmpl(ch, nxt, tus=i < depth_temporal)
|
||||||
|
ch = nxt
|
||||||
|
self.up.append(stage)
|
||||||
|
|
||||||
|
self.norm_out = RMS_norm(ch)
|
||||||
|
self.conv_out = VideoConv3d(ch, out_channels, 3)
|
||||||
|
|
||||||
|
def forward(self, z):
|
||||||
|
z = z.permute(0, 2, 1, 3, 4)
|
||||||
|
b, f, c, h, w = z.shape
|
||||||
|
z = z.reshape(b, f, 2, c // 2, h, w)
|
||||||
|
z = z.permute(0, 1, 2, 3, 4, 5).reshape(b, f * 2, c // 2, h, w)
|
||||||
|
z = z.permute(0, 2, 1, 3, 4)
|
||||||
|
z = z[:, :, 1:]
|
||||||
|
|
||||||
|
x = self.conv_in(z) + z.repeat_interleave(self.block_out_channels[0] // self.z_channels, 1)
|
||||||
|
x = self.mid.block_2(self.mid.attn_1(self.mid.block_1(x)))
|
||||||
|
|
||||||
|
for stage in self.up:
|
||||||
|
for blk in stage.block:
|
||||||
|
x = blk(x)
|
||||||
|
if hasattr(stage, 'upsample'):
|
||||||
|
x = stage.upsample(x)
|
||||||
|
|
||||||
|
return self.conv_out(F.silu(self.norm_out(x)))
|
||||||
@ -26,6 +26,12 @@ class DiagonalGaussianRegularizer(torch.nn.Module):
|
|||||||
z = posterior.mode()
|
z = posterior.mode()
|
||||||
return z, None
|
return z, None
|
||||||
|
|
||||||
|
class EmptyRegularizer(torch.nn.Module):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def forward(self, z: torch.Tensor) -> Tuple[torch.Tensor, dict]:
|
||||||
|
return z, None
|
||||||
|
|
||||||
class AbstractAutoencoder(torch.nn.Module):
|
class AbstractAutoencoder(torch.nn.Module):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -145,7 +145,7 @@ class Downsample(nn.Module):
|
|||||||
|
|
||||||
class ResnetBlock(nn.Module):
|
class ResnetBlock(nn.Module):
|
||||||
def __init__(self, *, in_channels, out_channels=None, conv_shortcut=False,
|
def __init__(self, *, in_channels, out_channels=None, conv_shortcut=False,
|
||||||
dropout=0.0, temb_channels=512, conv_op=ops.Conv2d):
|
dropout=0.0, temb_channels=512, conv_op=ops.Conv2d, norm_op=Normalize):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.in_channels = in_channels
|
self.in_channels = in_channels
|
||||||
out_channels = in_channels if out_channels is None else out_channels
|
out_channels = in_channels if out_channels is None else out_channels
|
||||||
@ -153,7 +153,7 @@ class ResnetBlock(nn.Module):
|
|||||||
self.use_conv_shortcut = conv_shortcut
|
self.use_conv_shortcut = conv_shortcut
|
||||||
|
|
||||||
self.swish = torch.nn.SiLU(inplace=True)
|
self.swish = torch.nn.SiLU(inplace=True)
|
||||||
self.norm1 = Normalize(in_channels)
|
self.norm1 = norm_op(in_channels)
|
||||||
self.conv1 = conv_op(in_channels,
|
self.conv1 = conv_op(in_channels,
|
||||||
out_channels,
|
out_channels,
|
||||||
kernel_size=3,
|
kernel_size=3,
|
||||||
@ -162,7 +162,7 @@ class ResnetBlock(nn.Module):
|
|||||||
if temb_channels > 0:
|
if temb_channels > 0:
|
||||||
self.temb_proj = ops.Linear(temb_channels,
|
self.temb_proj = ops.Linear(temb_channels,
|
||||||
out_channels)
|
out_channels)
|
||||||
self.norm2 = Normalize(out_channels)
|
self.norm2 = norm_op(out_channels)
|
||||||
self.dropout = torch.nn.Dropout(dropout, inplace=True)
|
self.dropout = torch.nn.Dropout(dropout, inplace=True)
|
||||||
self.conv2 = conv_op(out_channels,
|
self.conv2 = conv_op(out_channels,
|
||||||
out_channels,
|
out_channels,
|
||||||
@ -305,11 +305,11 @@ def vae_attention():
|
|||||||
return normal_attention
|
return normal_attention
|
||||||
|
|
||||||
class AttnBlock(nn.Module):
|
class AttnBlock(nn.Module):
|
||||||
def __init__(self, in_channels, conv_op=ops.Conv2d):
|
def __init__(self, in_channels, conv_op=ops.Conv2d, norm_op=Normalize):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.in_channels = in_channels
|
self.in_channels = in_channels
|
||||||
|
|
||||||
self.norm = Normalize(in_channels)
|
self.norm = norm_op(in_channels)
|
||||||
self.q = conv_op(in_channels,
|
self.q = conv_op(in_channels,
|
||||||
in_channels,
|
in_channels,
|
||||||
kernel_size=1,
|
kernel_size=1,
|
||||||
|
|||||||
@ -1432,3 +1432,29 @@ class HunyuanImage21(BaseModel):
|
|||||||
out['guidance'] = comfy.conds.CONDRegular(torch.FloatTensor([guidance]))
|
out['guidance'] = comfy.conds.CONDRegular(torch.FloatTensor([guidance]))
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
class HunyuanImage21Refiner(HunyuanImage21):
|
||||||
|
def concat_cond(self, **kwargs):
|
||||||
|
noise = kwargs.get("noise", None)
|
||||||
|
image = kwargs.get("concat_latent_image", None)
|
||||||
|
noise_augmentation = kwargs.get("noise_augmentation", 0.0)
|
||||||
|
device = kwargs["device"]
|
||||||
|
|
||||||
|
if image is None:
|
||||||
|
shape_image = list(noise.shape)
|
||||||
|
image = torch.zeros(shape_image, dtype=noise.dtype, layout=noise.layout, device=noise.device)
|
||||||
|
else:
|
||||||
|
image = utils.common_upscale(image.to(device), noise.shape[-1], noise.shape[-2], "bilinear", "center")
|
||||||
|
image = self.process_latent_in(image)
|
||||||
|
image = utils.resize_to_batch_size(image, noise.shape[0])
|
||||||
|
if noise_augmentation > 0:
|
||||||
|
noise = torch.randn(image.shape, generator=torch.manual_seed(kwargs.get("seed", 0) - 10), dtype=image.dtype, device="cpu").to(image.device)
|
||||||
|
image = noise_augmentation * noise + min(1.0 - noise_augmentation, 0.75) * image
|
||||||
|
else:
|
||||||
|
image = 0.75 * image
|
||||||
|
return image
|
||||||
|
|
||||||
|
def extra_conds(self, **kwargs):
|
||||||
|
out = super().extra_conds(**kwargs)
|
||||||
|
out['disable_time_r'] = comfy.conds.CONDConstant(True)
|
||||||
|
return out
|
||||||
|
|||||||
17
comfy/sd.py
17
comfy/sd.py
@ -285,6 +285,7 @@ class VAE:
|
|||||||
self.process_output = lambda image: torch.clamp((image + 1.0) / 2.0, min=0.0, max=1.0)
|
self.process_output = lambda image: torch.clamp((image + 1.0) / 2.0, min=0.0, max=1.0)
|
||||||
self.working_dtypes = [torch.bfloat16, torch.float32]
|
self.working_dtypes = [torch.bfloat16, torch.float32]
|
||||||
self.disable_offload = False
|
self.disable_offload = False
|
||||||
|
self.not_video = False
|
||||||
|
|
||||||
self.downscale_index_formula = None
|
self.downscale_index_formula = None
|
||||||
self.upscale_index_formula = None
|
self.upscale_index_formula = None
|
||||||
@ -409,6 +410,20 @@ class VAE:
|
|||||||
self.downscale_ratio = (lambda a: max(0, math.floor((a + 7) / 8)), 32, 32)
|
self.downscale_ratio = (lambda a: max(0, math.floor((a + 7) / 8)), 32, 32)
|
||||||
self.downscale_index_formula = (8, 32, 32)
|
self.downscale_index_formula = (8, 32, 32)
|
||||||
self.working_dtypes = [torch.bfloat16, torch.float32]
|
self.working_dtypes = [torch.bfloat16, torch.float32]
|
||||||
|
elif "decoder.conv_in.conv.weight" in sd and sd['decoder.conv_in.conv.weight'].shape[1] == 32:
|
||||||
|
ddconfig = {"block_out_channels": [128, 256, 512, 1024, 1024], "in_channels": 3, "out_channels": 3, "num_res_blocks": 2, "ffactor_spatial": 16, "ffactor_temporal": 4, "downsample_match_channel": True, "upsample_match_channel": True}
|
||||||
|
self.latent_channels = ddconfig['z_channels'] = sd["decoder.conv_in.conv.weight"].shape[1]
|
||||||
|
self.downscale_ratio = 16
|
||||||
|
self.upscale_ratio = 16
|
||||||
|
self.latent_dim = 3
|
||||||
|
self.not_video = True
|
||||||
|
self.working_dtypes = [torch.float16, torch.bfloat16, torch.float32]
|
||||||
|
self.first_stage_model = AutoencodingEngine(regularizer_config={'target': "comfy.ldm.models.autoencoder.EmptyRegularizer"},
|
||||||
|
encoder_config={'target': "comfy.ldm.hunyuan_video.vae_refiner.Encoder", 'params': ddconfig},
|
||||||
|
decoder_config={'target': "comfy.ldm.hunyuan_video.vae_refiner.Decoder", 'params': ddconfig})
|
||||||
|
|
||||||
|
self.memory_used_encode = lambda shape, dtype: (1400 * shape[-2] * shape[-1]) * model_management.dtype_size(dtype)
|
||||||
|
self.memory_used_decode = lambda shape, dtype: (1400 * shape[-3] * shape[-2] * shape[-1] * 16 * 16) * model_management.dtype_size(dtype)
|
||||||
elif "decoder.conv_in.conv.weight" in sd:
|
elif "decoder.conv_in.conv.weight" in sd:
|
||||||
ddconfig = {'double_z': True, 'z_channels': 4, 'resolution': 256, 'in_channels': 3, 'out_ch': 3, 'ch': 128, 'ch_mult': [1, 2, 4, 4], 'num_res_blocks': 2, 'attn_resolutions': [], 'dropout': 0.0}
|
ddconfig = {'double_z': True, 'z_channels': 4, 'resolution': 256, 'in_channels': 3, 'out_ch': 3, 'ch': 128, 'ch_mult': [1, 2, 4, 4], 'num_res_blocks': 2, 'attn_resolutions': [], 'dropout': 0.0}
|
||||||
ddconfig["conv3d"] = True
|
ddconfig["conv3d"] = True
|
||||||
@ -669,7 +684,7 @@ class VAE:
|
|||||||
self.throw_exception_if_invalid()
|
self.throw_exception_if_invalid()
|
||||||
pixel_samples = self.vae_encode_crop_pixels(pixel_samples)
|
pixel_samples = self.vae_encode_crop_pixels(pixel_samples)
|
||||||
pixel_samples = pixel_samples.movedim(-1, 1)
|
pixel_samples = pixel_samples.movedim(-1, 1)
|
||||||
if self.latent_dim == 3 and pixel_samples.ndim < 5:
|
if not self.not_video and self.latent_dim == 3 and pixel_samples.ndim < 5:
|
||||||
pixel_samples = pixel_samples.movedim(1, 0).unsqueeze(0)
|
pixel_samples = pixel_samples.movedim(1, 0).unsqueeze(0)
|
||||||
try:
|
try:
|
||||||
memory_used = self.memory_used_encode(pixel_samples.shape, self.vae_dtype)
|
memory_used = self.memory_used_encode(pixel_samples.shape, self.vae_dtype)
|
||||||
|
|||||||
@ -1321,6 +1321,23 @@ class HunyuanImage21(HunyuanVideo):
|
|||||||
hunyuan_detect = comfy.text_encoders.hunyuan_video.llama_detect(state_dict, "{}qwen25_7b.transformer.".format(pref))
|
hunyuan_detect = comfy.text_encoders.hunyuan_video.llama_detect(state_dict, "{}qwen25_7b.transformer.".format(pref))
|
||||||
return supported_models_base.ClipTarget(comfy.text_encoders.hunyuan_image.HunyuanImageTokenizer, comfy.text_encoders.hunyuan_image.te(**hunyuan_detect))
|
return supported_models_base.ClipTarget(comfy.text_encoders.hunyuan_image.HunyuanImageTokenizer, comfy.text_encoders.hunyuan_image.te(**hunyuan_detect))
|
||||||
|
|
||||||
models = [LotusD, Stable_Zero123, SD15_instructpix2pix, SD15, SD20, SD21UnclipL, SD21UnclipH, SDXL_instructpix2pix, SDXLRefiner, SDXL, SSD1B, KOALA_700M, KOALA_1B, Segmind_Vega, SD_X4Upscaler, Stable_Cascade_C, Stable_Cascade_B, SV3D_u, SV3D_p, SD3, StableAudio, AuraFlow, PixArtAlpha, PixArtSigma, HunyuanDiT, HunyuanDiT1, FluxInpaint, Flux, FluxSchnell, GenmoMochi, LTXV, HunyuanImage21, HunyuanVideoSkyreelsI2V, HunyuanVideoI2V, HunyuanVideo, CosmosT2V, CosmosI2V, CosmosT2IPredict2, CosmosI2VPredict2, Lumina2, WAN22_T2V, WAN21_T2V, WAN21_I2V, WAN21_FunControl2V, WAN21_Vace, WAN21_Camera, WAN22_Camera, WAN22_S2V, Hunyuan3Dv2mini, Hunyuan3Dv2, Hunyuan3Dv2_1, HiDream, Chroma, ACEStep, Omnigen2, QwenImage]
|
class HunyuanImage21Refiner(HunyuanVideo):
|
||||||
|
unet_config = {
|
||||||
|
"image_model": "hunyuan_video",
|
||||||
|
"patch_size": [1, 1, 1],
|
||||||
|
"vec_in_dim": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
sampling_settings = {
|
||||||
|
"shift": 4.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
latent_format = latent_formats.HunyuanImage21Refiner
|
||||||
|
|
||||||
|
def get_model(self, state_dict, prefix="", device=None):
|
||||||
|
out = model_base.HunyuanImage21Refiner(self, device=device)
|
||||||
|
return out
|
||||||
|
|
||||||
|
models = [LotusD, Stable_Zero123, SD15_instructpix2pix, SD15, SD20, SD21UnclipL, SD21UnclipH, SDXL_instructpix2pix, SDXLRefiner, SDXL, SSD1B, KOALA_700M, KOALA_1B, Segmind_Vega, SD_X4Upscaler, Stable_Cascade_C, Stable_Cascade_B, SV3D_u, SV3D_p, SD3, StableAudio, AuraFlow, PixArtAlpha, PixArtSigma, HunyuanDiT, HunyuanDiT1, FluxInpaint, Flux, FluxSchnell, GenmoMochi, LTXV, HunyuanImage21Refiner, HunyuanImage21, HunyuanVideoSkyreelsI2V, HunyuanVideoI2V, HunyuanVideo, CosmosT2V, CosmosI2V, CosmosT2IPredict2, CosmosI2VPredict2, Lumina2, WAN22_T2V, WAN21_T2V, WAN21_I2V, WAN21_FunControl2V, WAN21_Vace, WAN21_Camera, WAN22_Camera, WAN22_S2V, Hunyuan3Dv2mini, Hunyuan3Dv2, Hunyuan3Dv2_1, HiDream, Chroma, ACEStep, Omnigen2, QwenImage]
|
||||||
|
|
||||||
models += [SVD_img2vid]
|
models += [SVD_img2vid]
|
||||||
|
|||||||
@ -331,7 +331,7 @@ class String(ComfyTypeIO):
|
|||||||
})
|
})
|
||||||
|
|
||||||
@comfytype(io_type="COMBO")
|
@comfytype(io_type="COMBO")
|
||||||
class Combo(ComfyTypeI):
|
class Combo(ComfyTypeIO):
|
||||||
Type = str
|
Type = str
|
||||||
class Input(WidgetInput):
|
class Input(WidgetInput):
|
||||||
"""Combo input (dropdown)."""
|
"""Combo input (dropdown)."""
|
||||||
@ -360,6 +360,14 @@ class Combo(ComfyTypeI):
|
|||||||
"remote": self.remote.as_dict() if self.remote else None,
|
"remote": self.remote.as_dict() if self.remote else None,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
class Output(Output):
|
||||||
|
def __init__(self, id: str=None, display_name: str=None, options: list[str]=None, tooltip: str=None, is_output_list=False):
|
||||||
|
super().__init__(id, display_name, tooltip, is_output_list)
|
||||||
|
self.options = options if options is not None else []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def io_type(self):
|
||||||
|
return self.options
|
||||||
|
|
||||||
@comfytype(io_type="COMBO")
|
@comfytype(io_type="COMBO")
|
||||||
class MultiCombo(ComfyTypeI):
|
class MultiCombo(ComfyTypeI):
|
||||||
|
|||||||
@ -846,6 +846,8 @@ class KlingStartEndFrameNode(KlingImage2VideoNode):
|
|||||||
"pro mode / 10s duration / kling-v1-5": ("pro", "10", "kling-v1-5"),
|
"pro mode / 10s duration / kling-v1-5": ("pro", "10", "kling-v1-5"),
|
||||||
"pro mode / 5s duration / kling-v1-6": ("pro", "5", "kling-v1-6"),
|
"pro mode / 5s duration / kling-v1-6": ("pro", "5", "kling-v1-6"),
|
||||||
"pro mode / 10s duration / kling-v1-6": ("pro", "10", "kling-v1-6"),
|
"pro mode / 10s duration / kling-v1-6": ("pro", "10", "kling-v1-6"),
|
||||||
|
"pro mode / 5s duration / kling-v2-1": ("pro", "5", "kling-v2-1"),
|
||||||
|
"pro mode / 10s duration / kling-v2-1": ("pro", "10", "kling-v2-1"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
from inspect import cleandoc
|
from inspect import cleandoc
|
||||||
from typing import Union
|
from typing import Optional
|
||||||
import logging
|
import logging
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from comfy.comfy_types.node_typing import IO
|
from typing_extensions import override
|
||||||
|
from comfy_api.latest import ComfyExtension, io as comfy_io
|
||||||
from comfy_api.input_impl.video_types import VideoFromFile
|
from comfy_api.input_impl.video_types import VideoFromFile
|
||||||
from comfy_api_nodes.apis import (
|
from comfy_api_nodes.apis import (
|
||||||
MinimaxVideoGenerationRequest,
|
MinimaxVideoGenerationRequest,
|
||||||
@ -11,7 +12,7 @@ from comfy_api_nodes.apis import (
|
|||||||
MinimaxFileRetrieveResponse,
|
MinimaxFileRetrieveResponse,
|
||||||
MinimaxTaskResultResponse,
|
MinimaxTaskResultResponse,
|
||||||
SubjectReferenceItem,
|
SubjectReferenceItem,
|
||||||
MiniMaxModel
|
MiniMaxModel,
|
||||||
)
|
)
|
||||||
from comfy_api_nodes.apis.client import (
|
from comfy_api_nodes.apis.client import (
|
||||||
ApiEndpoint,
|
ApiEndpoint,
|
||||||
@ -31,372 +32,398 @@ from server import PromptServer
|
|||||||
I2V_AVERAGE_DURATION = 114
|
I2V_AVERAGE_DURATION = 114
|
||||||
T2V_AVERAGE_DURATION = 234
|
T2V_AVERAGE_DURATION = 234
|
||||||
|
|
||||||
class MinimaxTextToVideoNode:
|
|
||||||
|
async def _generate_mm_video(
|
||||||
|
*,
|
||||||
|
auth: dict[str, str],
|
||||||
|
node_id: str,
|
||||||
|
prompt_text: str,
|
||||||
|
seed: int,
|
||||||
|
model: str,
|
||||||
|
image: Optional[torch.Tensor] = None, # used for ImageToVideo
|
||||||
|
subject: Optional[torch.Tensor] = None, # used for SubjectToVideo
|
||||||
|
average_duration: Optional[int] = None,
|
||||||
|
) -> comfy_io.NodeOutput:
|
||||||
|
if image is None:
|
||||||
|
validate_string(prompt_text, field_name="prompt_text")
|
||||||
|
# upload image, if passed in
|
||||||
|
image_url = None
|
||||||
|
if image is not None:
|
||||||
|
image_url = (await upload_images_to_comfyapi(image, max_images=1, auth_kwargs=auth))[0]
|
||||||
|
|
||||||
|
# TODO: figure out how to deal with subject properly, API returns invalid params when using S2V-01 model
|
||||||
|
subject_reference = None
|
||||||
|
if subject is not None:
|
||||||
|
subject_url = (await upload_images_to_comfyapi(subject, max_images=1, auth_kwargs=auth))[0]
|
||||||
|
subject_reference = [SubjectReferenceItem(image=subject_url)]
|
||||||
|
|
||||||
|
|
||||||
|
video_generate_operation = SynchronousOperation(
|
||||||
|
endpoint=ApiEndpoint(
|
||||||
|
path="/proxy/minimax/video_generation",
|
||||||
|
method=HttpMethod.POST,
|
||||||
|
request_model=MinimaxVideoGenerationRequest,
|
||||||
|
response_model=MinimaxVideoGenerationResponse,
|
||||||
|
),
|
||||||
|
request=MinimaxVideoGenerationRequest(
|
||||||
|
model=MiniMaxModel(model),
|
||||||
|
prompt=prompt_text,
|
||||||
|
callback_url=None,
|
||||||
|
first_frame_image=image_url,
|
||||||
|
subject_reference=subject_reference,
|
||||||
|
prompt_optimizer=None,
|
||||||
|
),
|
||||||
|
auth_kwargs=auth,
|
||||||
|
)
|
||||||
|
response = await video_generate_operation.execute()
|
||||||
|
|
||||||
|
task_id = response.task_id
|
||||||
|
if not task_id:
|
||||||
|
raise Exception(f"MiniMax generation failed: {response.base_resp}")
|
||||||
|
|
||||||
|
video_generate_operation = PollingOperation(
|
||||||
|
poll_endpoint=ApiEndpoint(
|
||||||
|
path="/proxy/minimax/query/video_generation",
|
||||||
|
method=HttpMethod.GET,
|
||||||
|
request_model=EmptyRequest,
|
||||||
|
response_model=MinimaxTaskResultResponse,
|
||||||
|
query_params={"task_id": task_id},
|
||||||
|
),
|
||||||
|
completed_statuses=["Success"],
|
||||||
|
failed_statuses=["Fail"],
|
||||||
|
status_extractor=lambda x: x.status.value,
|
||||||
|
estimated_duration=average_duration,
|
||||||
|
node_id=node_id,
|
||||||
|
auth_kwargs=auth,
|
||||||
|
)
|
||||||
|
task_result = await video_generate_operation.execute()
|
||||||
|
|
||||||
|
file_id = task_result.file_id
|
||||||
|
if file_id is None:
|
||||||
|
raise Exception("Request was not successful. Missing file ID.")
|
||||||
|
file_retrieve_operation = SynchronousOperation(
|
||||||
|
endpoint=ApiEndpoint(
|
||||||
|
path="/proxy/minimax/files/retrieve",
|
||||||
|
method=HttpMethod.GET,
|
||||||
|
request_model=EmptyRequest,
|
||||||
|
response_model=MinimaxFileRetrieveResponse,
|
||||||
|
query_params={"file_id": int(file_id)},
|
||||||
|
),
|
||||||
|
request=EmptyRequest(),
|
||||||
|
auth_kwargs=auth,
|
||||||
|
)
|
||||||
|
file_result = await file_retrieve_operation.execute()
|
||||||
|
|
||||||
|
file_url = file_result.file.download_url
|
||||||
|
if file_url is None:
|
||||||
|
raise Exception(
|
||||||
|
f"No video was found in the response. Full response: {file_result.model_dump()}"
|
||||||
|
)
|
||||||
|
logging.info("Generated video URL: %s", file_url)
|
||||||
|
if node_id:
|
||||||
|
if hasattr(file_result.file, "backup_download_url"):
|
||||||
|
message = f"Result URL: {file_url}\nBackup URL: {file_result.file.backup_download_url}"
|
||||||
|
else:
|
||||||
|
message = f"Result URL: {file_url}"
|
||||||
|
PromptServer.instance.send_progress_text(message, node_id)
|
||||||
|
|
||||||
|
# Download and return as VideoFromFile
|
||||||
|
video_io = await download_url_to_bytesio(file_url)
|
||||||
|
if video_io is None:
|
||||||
|
error_msg = f"Failed to download video from {file_url}"
|
||||||
|
logging.error(error_msg)
|
||||||
|
raise Exception(error_msg)
|
||||||
|
return comfy_io.NodeOutput(VideoFromFile(video_io))
|
||||||
|
|
||||||
|
|
||||||
|
class MinimaxTextToVideoNode(comfy_io.ComfyNode):
|
||||||
"""
|
"""
|
||||||
Generates videos synchronously based on a prompt, and optional parameters using MiniMax's API.
|
Generates videos synchronously based on a prompt, and optional parameters using MiniMax's API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
AVERAGE_DURATION = T2V_AVERAGE_DURATION
|
@classmethod
|
||||||
|
def define_schema(cls) -> comfy_io.Schema:
|
||||||
|
return comfy_io.Schema(
|
||||||
|
node_id="MinimaxTextToVideoNode",
|
||||||
|
display_name="MiniMax Text to Video",
|
||||||
|
category="api node/video/MiniMax",
|
||||||
|
description=cleandoc(cls.__doc__ or ""),
|
||||||
|
inputs=[
|
||||||
|
comfy_io.String.Input(
|
||||||
|
"prompt_text",
|
||||||
|
multiline=True,
|
||||||
|
default="",
|
||||||
|
tooltip="Text prompt to guide the video generation",
|
||||||
|
),
|
||||||
|
comfy_io.Combo.Input(
|
||||||
|
"model",
|
||||||
|
options=["T2V-01", "T2V-01-Director"],
|
||||||
|
default="T2V-01",
|
||||||
|
tooltip="Model to use for video generation",
|
||||||
|
),
|
||||||
|
comfy_io.Int.Input(
|
||||||
|
"seed",
|
||||||
|
default=0,
|
||||||
|
min=0,
|
||||||
|
max=0xFFFFFFFFFFFFFFFF,
|
||||||
|
step=1,
|
||||||
|
control_after_generate=True,
|
||||||
|
tooltip="The random seed used for creating the noise.",
|
||||||
|
optional=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
outputs=[comfy_io.Video.Output()],
|
||||||
|
hidden=[
|
||||||
|
comfy_io.Hidden.auth_token_comfy_org,
|
||||||
|
comfy_io.Hidden.api_key_comfy_org,
|
||||||
|
comfy_io.Hidden.unique_id,
|
||||||
|
],
|
||||||
|
is_api_node=True,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
async def execute(
|
||||||
return {
|
cls,
|
||||||
"required": {
|
prompt_text: str,
|
||||||
"prompt_text": (
|
model: str = "T2V-01",
|
||||||
"STRING",
|
seed: int = 0,
|
||||||
{
|
) -> comfy_io.NodeOutput:
|
||||||
"multiline": True,
|
return await _generate_mm_video(
|
||||||
"default": "",
|
auth={
|
||||||
"tooltip": "Text prompt to guide the video generation",
|
"auth_token": cls.hidden.auth_token_comfy_org,
|
||||||
},
|
"comfy_api_key": cls.hidden.api_key_comfy_org,
|
||||||
),
|
|
||||||
"model": (
|
|
||||||
[
|
|
||||||
"T2V-01",
|
|
||||||
"T2V-01-Director",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
"default": "T2V-01",
|
|
||||||
"tooltip": "Model to use for video generation",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
"optional": {
|
node_id=cls.hidden.unique_id,
|
||||||
"seed": (
|
prompt_text=prompt_text,
|
||||||
IO.INT,
|
seed=seed,
|
||||||
{
|
model=model,
|
||||||
"default": 0,
|
image=None,
|
||||||
"min": 0,
|
subject=None,
|
||||||
"max": 0xFFFFFFFFFFFFFFFF,
|
average_duration=T2V_AVERAGE_DURATION,
|
||||||
"control_after_generate": True,
|
|
||||||
"tooltip": "The random seed used for creating the noise.",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
"hidden": {
|
|
||||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
|
||||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
|
||||||
"unique_id": "UNIQUE_ID",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
RETURN_TYPES = ("VIDEO",)
|
|
||||||
DESCRIPTION = "Generates videos from prompts using MiniMax's API"
|
|
||||||
FUNCTION = "generate_video"
|
|
||||||
CATEGORY = "api node/video/MiniMax"
|
|
||||||
API_NODE = True
|
|
||||||
|
|
||||||
async def generate_video(
|
|
||||||
self,
|
|
||||||
prompt_text,
|
|
||||||
seed=0,
|
|
||||||
model="T2V-01",
|
|
||||||
image: torch.Tensor=None, # used for ImageToVideo
|
|
||||||
subject: torch.Tensor=None, # used for SubjectToVideo
|
|
||||||
unique_id: Union[str, None]=None,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
'''
|
|
||||||
Function used between MiniMax nodes - supports T2V, I2V, and S2V, based on provided arguments.
|
|
||||||
'''
|
|
||||||
if image is None:
|
|
||||||
validate_string(prompt_text, field_name="prompt_text")
|
|
||||||
# upload image, if passed in
|
|
||||||
image_url = None
|
|
||||||
if image is not None:
|
|
||||||
image_url = (await upload_images_to_comfyapi(image, max_images=1, auth_kwargs=kwargs))[0]
|
|
||||||
|
|
||||||
# TODO: figure out how to deal with subject properly, API returns invalid params when using S2V-01 model
|
|
||||||
subject_reference = None
|
|
||||||
if subject is not None:
|
|
||||||
subject_url = (await upload_images_to_comfyapi(subject, max_images=1, auth_kwargs=kwargs))[0]
|
|
||||||
subject_reference = [SubjectReferenceItem(image=subject_url)]
|
|
||||||
|
|
||||||
|
|
||||||
video_generate_operation = SynchronousOperation(
|
|
||||||
endpoint=ApiEndpoint(
|
|
||||||
path="/proxy/minimax/video_generation",
|
|
||||||
method=HttpMethod.POST,
|
|
||||||
request_model=MinimaxVideoGenerationRequest,
|
|
||||||
response_model=MinimaxVideoGenerationResponse,
|
|
||||||
),
|
|
||||||
request=MinimaxVideoGenerationRequest(
|
|
||||||
model=MiniMaxModel(model),
|
|
||||||
prompt=prompt_text,
|
|
||||||
callback_url=None,
|
|
||||||
first_frame_image=image_url,
|
|
||||||
subject_reference=subject_reference,
|
|
||||||
prompt_optimizer=None,
|
|
||||||
),
|
|
||||||
auth_kwargs=kwargs,
|
|
||||||
)
|
)
|
||||||
response = await video_generate_operation.execute()
|
|
||||||
|
|
||||||
task_id = response.task_id
|
|
||||||
if not task_id:
|
|
||||||
raise Exception(f"MiniMax generation failed: {response.base_resp}")
|
|
||||||
|
|
||||||
video_generate_operation = PollingOperation(
|
|
||||||
poll_endpoint=ApiEndpoint(
|
|
||||||
path="/proxy/minimax/query/video_generation",
|
|
||||||
method=HttpMethod.GET,
|
|
||||||
request_model=EmptyRequest,
|
|
||||||
response_model=MinimaxTaskResultResponse,
|
|
||||||
query_params={"task_id": task_id},
|
|
||||||
),
|
|
||||||
completed_statuses=["Success"],
|
|
||||||
failed_statuses=["Fail"],
|
|
||||||
status_extractor=lambda x: x.status.value,
|
|
||||||
estimated_duration=self.AVERAGE_DURATION,
|
|
||||||
node_id=unique_id,
|
|
||||||
auth_kwargs=kwargs,
|
|
||||||
)
|
|
||||||
task_result = await video_generate_operation.execute()
|
|
||||||
|
|
||||||
file_id = task_result.file_id
|
|
||||||
if file_id is None:
|
|
||||||
raise Exception("Request was not successful. Missing file ID.")
|
|
||||||
file_retrieve_operation = SynchronousOperation(
|
|
||||||
endpoint=ApiEndpoint(
|
|
||||||
path="/proxy/minimax/files/retrieve",
|
|
||||||
method=HttpMethod.GET,
|
|
||||||
request_model=EmptyRequest,
|
|
||||||
response_model=MinimaxFileRetrieveResponse,
|
|
||||||
query_params={"file_id": int(file_id)},
|
|
||||||
),
|
|
||||||
request=EmptyRequest(),
|
|
||||||
auth_kwargs=kwargs,
|
|
||||||
)
|
|
||||||
file_result = await file_retrieve_operation.execute()
|
|
||||||
|
|
||||||
file_url = file_result.file.download_url
|
|
||||||
if file_url is None:
|
|
||||||
raise Exception(
|
|
||||||
f"No video was found in the response. Full response: {file_result.model_dump()}"
|
|
||||||
)
|
|
||||||
logging.info(f"Generated video URL: {file_url}")
|
|
||||||
if unique_id:
|
|
||||||
if hasattr(file_result.file, "backup_download_url"):
|
|
||||||
message = f"Result URL: {file_url}\nBackup URL: {file_result.file.backup_download_url}"
|
|
||||||
else:
|
|
||||||
message = f"Result URL: {file_url}"
|
|
||||||
PromptServer.instance.send_progress_text(message, unique_id)
|
|
||||||
|
|
||||||
video_io = await download_url_to_bytesio(file_url)
|
|
||||||
if video_io is None:
|
|
||||||
error_msg = f"Failed to download video from {file_url}"
|
|
||||||
logging.error(error_msg)
|
|
||||||
raise Exception(error_msg)
|
|
||||||
return (VideoFromFile(video_io),)
|
|
||||||
|
|
||||||
|
|
||||||
class MinimaxImageToVideoNode(MinimaxTextToVideoNode):
|
class MinimaxImageToVideoNode(comfy_io.ComfyNode):
|
||||||
"""
|
"""
|
||||||
Generates videos synchronously based on an image and prompt, and optional parameters using MiniMax's API.
|
Generates videos synchronously based on an image and prompt, and optional parameters using MiniMax's API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
AVERAGE_DURATION = I2V_AVERAGE_DURATION
|
@classmethod
|
||||||
|
def define_schema(cls) -> comfy_io.Schema:
|
||||||
|
return comfy_io.Schema(
|
||||||
|
node_id="MinimaxImageToVideoNode",
|
||||||
|
display_name="MiniMax Image to Video",
|
||||||
|
category="api node/video/MiniMax",
|
||||||
|
description=cleandoc(cls.__doc__ or ""),
|
||||||
|
inputs=[
|
||||||
|
comfy_io.Image.Input(
|
||||||
|
"image",
|
||||||
|
tooltip="Image to use as first frame of video generation",
|
||||||
|
),
|
||||||
|
comfy_io.String.Input(
|
||||||
|
"prompt_text",
|
||||||
|
multiline=True,
|
||||||
|
default="",
|
||||||
|
tooltip="Text prompt to guide the video generation",
|
||||||
|
),
|
||||||
|
comfy_io.Combo.Input(
|
||||||
|
"model",
|
||||||
|
options=["I2V-01-Director", "I2V-01", "I2V-01-live"],
|
||||||
|
default="I2V-01",
|
||||||
|
tooltip="Model to use for video generation",
|
||||||
|
),
|
||||||
|
comfy_io.Int.Input(
|
||||||
|
"seed",
|
||||||
|
default=0,
|
||||||
|
min=0,
|
||||||
|
max=0xFFFFFFFFFFFFFFFF,
|
||||||
|
step=1,
|
||||||
|
control_after_generate=True,
|
||||||
|
tooltip="The random seed used for creating the noise.",
|
||||||
|
optional=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
outputs=[comfy_io.Video.Output()],
|
||||||
|
hidden=[
|
||||||
|
comfy_io.Hidden.auth_token_comfy_org,
|
||||||
|
comfy_io.Hidden.api_key_comfy_org,
|
||||||
|
comfy_io.Hidden.unique_id,
|
||||||
|
],
|
||||||
|
is_api_node=True,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
async def execute(
|
||||||
return {
|
cls,
|
||||||
"required": {
|
image: torch.Tensor,
|
||||||
"image": (
|
prompt_text: str,
|
||||||
IO.IMAGE,
|
model: str = "I2V-01",
|
||||||
{
|
seed: int = 0,
|
||||||
"tooltip": "Image to use as first frame of video generation"
|
) -> comfy_io.NodeOutput:
|
||||||
},
|
return await _generate_mm_video(
|
||||||
),
|
auth={
|
||||||
"prompt_text": (
|
"auth_token": cls.hidden.auth_token_comfy_org,
|
||||||
"STRING",
|
"comfy_api_key": cls.hidden.api_key_comfy_org,
|
||||||
{
|
|
||||||
"multiline": True,
|
|
||||||
"default": "",
|
|
||||||
"tooltip": "Text prompt to guide the video generation",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
"model": (
|
|
||||||
[
|
|
||||||
"I2V-01-Director",
|
|
||||||
"I2V-01",
|
|
||||||
"I2V-01-live",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
"default": "I2V-01",
|
|
||||||
"tooltip": "Model to use for video generation",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
"optional": {
|
node_id=cls.hidden.unique_id,
|
||||||
"seed": (
|
prompt_text=prompt_text,
|
||||||
IO.INT,
|
seed=seed,
|
||||||
{
|
model=model,
|
||||||
"default": 0,
|
image=image,
|
||||||
"min": 0,
|
subject=None,
|
||||||
"max": 0xFFFFFFFFFFFFFFFF,
|
average_duration=I2V_AVERAGE_DURATION,
|
||||||
"control_after_generate": True,
|
)
|
||||||
"tooltip": "The random seed used for creating the noise.",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
"hidden": {
|
|
||||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
|
||||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
|
||||||
"unique_id": "UNIQUE_ID",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
RETURN_TYPES = ("VIDEO",)
|
|
||||||
DESCRIPTION = "Generates videos from an image and prompts using MiniMax's API"
|
|
||||||
FUNCTION = "generate_video"
|
|
||||||
CATEGORY = "api node/video/MiniMax"
|
|
||||||
API_NODE = True
|
|
||||||
|
|
||||||
|
|
||||||
class MinimaxSubjectToVideoNode(MinimaxTextToVideoNode):
|
class MinimaxSubjectToVideoNode(comfy_io.ComfyNode):
|
||||||
"""
|
"""
|
||||||
Generates videos synchronously based on an image and prompt, and optional parameters using MiniMax's API.
|
Generates videos synchronously based on an image and prompt, and optional parameters using MiniMax's API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
AVERAGE_DURATION = T2V_AVERAGE_DURATION
|
@classmethod
|
||||||
|
def define_schema(cls) -> comfy_io.Schema:
|
||||||
|
return comfy_io.Schema(
|
||||||
|
node_id="MinimaxSubjectToVideoNode",
|
||||||
|
display_name="MiniMax Subject to Video",
|
||||||
|
category="api node/video/MiniMax",
|
||||||
|
description=cleandoc(cls.__doc__ or ""),
|
||||||
|
inputs=[
|
||||||
|
comfy_io.Image.Input(
|
||||||
|
"subject",
|
||||||
|
tooltip="Image of subject to reference for video generation",
|
||||||
|
),
|
||||||
|
comfy_io.String.Input(
|
||||||
|
"prompt_text",
|
||||||
|
multiline=True,
|
||||||
|
default="",
|
||||||
|
tooltip="Text prompt to guide the video generation",
|
||||||
|
),
|
||||||
|
comfy_io.Combo.Input(
|
||||||
|
"model",
|
||||||
|
options=["S2V-01"],
|
||||||
|
default="S2V-01",
|
||||||
|
tooltip="Model to use for video generation",
|
||||||
|
),
|
||||||
|
comfy_io.Int.Input(
|
||||||
|
"seed",
|
||||||
|
default=0,
|
||||||
|
min=0,
|
||||||
|
max=0xFFFFFFFFFFFFFFFF,
|
||||||
|
step=1,
|
||||||
|
control_after_generate=True,
|
||||||
|
tooltip="The random seed used for creating the noise.",
|
||||||
|
optional=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
outputs=[comfy_io.Video.Output()],
|
||||||
|
hidden=[
|
||||||
|
comfy_io.Hidden.auth_token_comfy_org,
|
||||||
|
comfy_io.Hidden.api_key_comfy_org,
|
||||||
|
comfy_io.Hidden.unique_id,
|
||||||
|
],
|
||||||
|
is_api_node=True,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
async def execute(
|
||||||
return {
|
cls,
|
||||||
"required": {
|
subject: torch.Tensor,
|
||||||
"subject": (
|
prompt_text: str,
|
||||||
IO.IMAGE,
|
model: str = "S2V-01",
|
||||||
{
|
seed: int = 0,
|
||||||
"tooltip": "Image of subject to reference video generation"
|
) -> comfy_io.NodeOutput:
|
||||||
},
|
return await _generate_mm_video(
|
||||||
),
|
auth={
|
||||||
"prompt_text": (
|
"auth_token": cls.hidden.auth_token_comfy_org,
|
||||||
"STRING",
|
"comfy_api_key": cls.hidden.api_key_comfy_org,
|
||||||
{
|
|
||||||
"multiline": True,
|
|
||||||
"default": "",
|
|
||||||
"tooltip": "Text prompt to guide the video generation",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
"model": (
|
|
||||||
[
|
|
||||||
"S2V-01",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
"default": "S2V-01",
|
|
||||||
"tooltip": "Model to use for video generation",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
"optional": {
|
node_id=cls.hidden.unique_id,
|
||||||
"seed": (
|
prompt_text=prompt_text,
|
||||||
IO.INT,
|
seed=seed,
|
||||||
{
|
model=model,
|
||||||
"default": 0,
|
image=None,
|
||||||
"min": 0,
|
subject=subject,
|
||||||
"max": 0xFFFFFFFFFFFFFFFF,
|
average_duration=T2V_AVERAGE_DURATION,
|
||||||
"control_after_generate": True,
|
)
|
||||||
"tooltip": "The random seed used for creating the noise.",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
"hidden": {
|
|
||||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
|
||||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
|
||||||
"unique_id": "UNIQUE_ID",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
RETURN_TYPES = ("VIDEO",)
|
|
||||||
DESCRIPTION = "Generates videos from an image and prompts using MiniMax's API"
|
|
||||||
FUNCTION = "generate_video"
|
|
||||||
CATEGORY = "api node/video/MiniMax"
|
|
||||||
API_NODE = True
|
|
||||||
|
|
||||||
|
|
||||||
class MinimaxHailuoVideoNode:
|
class MinimaxHailuoVideoNode(comfy_io.ComfyNode):
|
||||||
"""Generates videos from prompt, with optional start frame using the new MiniMax Hailuo-02 model."""
|
"""Generates videos from prompt, with optional start frame using the new MiniMax Hailuo-02 model."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def define_schema(cls) -> comfy_io.Schema:
|
||||||
return {
|
return comfy_io.Schema(
|
||||||
"required": {
|
node_id="MinimaxHailuoVideoNode",
|
||||||
"prompt_text": (
|
display_name="MiniMax Hailuo Video",
|
||||||
"STRING",
|
category="api node/video/MiniMax",
|
||||||
{
|
description=cleandoc(cls.__doc__ or ""),
|
||||||
"multiline": True,
|
inputs=[
|
||||||
"default": "",
|
comfy_io.String.Input(
|
||||||
"tooltip": "Text prompt to guide the video generation.",
|
"prompt_text",
|
||||||
},
|
multiline=True,
|
||||||
|
default="",
|
||||||
|
tooltip="Text prompt to guide the video generation.",
|
||||||
),
|
),
|
||||||
},
|
comfy_io.Int.Input(
|
||||||
"optional": {
|
"seed",
|
||||||
"seed": (
|
default=0,
|
||||||
IO.INT,
|
min=0,
|
||||||
{
|
max=0xFFFFFFFFFFFFFFFF,
|
||||||
"default": 0,
|
step=1,
|
||||||
"min": 0,
|
control_after_generate=True,
|
||||||
"max": 0xFFFFFFFFFFFFFFFF,
|
tooltip="The random seed used for creating the noise.",
|
||||||
"control_after_generate": True,
|
optional=True,
|
||||||
"tooltip": "The random seed used for creating the noise.",
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
"first_frame_image": (
|
comfy_io.Image.Input(
|
||||||
IO.IMAGE,
|
"first_frame_image",
|
||||||
{
|
tooltip="Optional image to use as the first frame to generate a video.",
|
||||||
"tooltip": "Optional image to use as the first frame to generate a video."
|
optional=True,
|
||||||
},
|
|
||||||
),
|
),
|
||||||
"prompt_optimizer": (
|
comfy_io.Boolean.Input(
|
||||||
IO.BOOLEAN,
|
"prompt_optimizer",
|
||||||
{
|
default=True,
|
||||||
"tooltip": "Optimize prompt to improve generation quality when needed.",
|
tooltip="Optimize prompt to improve generation quality when needed.",
|
||||||
"default": True,
|
optional=True,
|
||||||
},
|
|
||||||
),
|
),
|
||||||
"duration": (
|
comfy_io.Combo.Input(
|
||||||
IO.COMBO,
|
"duration",
|
||||||
{
|
options=[6, 10],
|
||||||
"tooltip": "The length of the output video in seconds.",
|
default=6,
|
||||||
"default": 6,
|
tooltip="The length of the output video in seconds.",
|
||||||
"options": [6, 10],
|
optional=True,
|
||||||
},
|
|
||||||
),
|
),
|
||||||
"resolution": (
|
comfy_io.Combo.Input(
|
||||||
IO.COMBO,
|
"resolution",
|
||||||
{
|
options=["768P", "1080P"],
|
||||||
"tooltip": "The dimensions of the video display. "
|
default="768P",
|
||||||
"1080p corresponds to 1920 x 1080 pixels, 768p corresponds to 1366 x 768 pixels.",
|
tooltip="The dimensions of the video display. 1080p is 1920x1080, 768p is 1366x768.",
|
||||||
"default": "768P",
|
optional=True,
|
||||||
"options": ["768P", "1080P"],
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
},
|
],
|
||||||
"hidden": {
|
outputs=[comfy_io.Video.Output()],
|
||||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
hidden=[
|
||||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
comfy_io.Hidden.auth_token_comfy_org,
|
||||||
"unique_id": "UNIQUE_ID",
|
comfy_io.Hidden.api_key_comfy_org,
|
||||||
},
|
comfy_io.Hidden.unique_id,
|
||||||
|
],
|
||||||
|
is_api_node=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def execute(
|
||||||
|
cls,
|
||||||
|
prompt_text: str,
|
||||||
|
seed: int = 0,
|
||||||
|
first_frame_image: Optional[torch.Tensor] = None, # used for ImageToVideo
|
||||||
|
prompt_optimizer: bool = True,
|
||||||
|
duration: int = 6,
|
||||||
|
resolution: str = "768P",
|
||||||
|
model: str = "MiniMax-Hailuo-02",
|
||||||
|
) -> comfy_io.NodeOutput:
|
||||||
|
auth = {
|
||||||
|
"auth_token": cls.hidden.auth_token_comfy_org,
|
||||||
|
"comfy_api_key": cls.hidden.api_key_comfy_org,
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("VIDEO",)
|
|
||||||
DESCRIPTION = cleandoc(__doc__ or "")
|
|
||||||
FUNCTION = "generate_video"
|
|
||||||
CATEGORY = "api node/video/MiniMax"
|
|
||||||
API_NODE = True
|
|
||||||
|
|
||||||
async def generate_video(
|
|
||||||
self,
|
|
||||||
prompt_text,
|
|
||||||
seed=0,
|
|
||||||
first_frame_image: torch.Tensor=None, # used for ImageToVideo
|
|
||||||
prompt_optimizer=True,
|
|
||||||
duration=6,
|
|
||||||
resolution="768P",
|
|
||||||
model="MiniMax-Hailuo-02",
|
|
||||||
unique_id: Union[str, None]=None,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
if first_frame_image is None:
|
if first_frame_image is None:
|
||||||
validate_string(prompt_text, field_name="prompt_text")
|
validate_string(prompt_text, field_name="prompt_text")
|
||||||
|
|
||||||
@ -408,7 +435,7 @@ class MinimaxHailuoVideoNode:
|
|||||||
# upload image, if passed in
|
# upload image, if passed in
|
||||||
image_url = None
|
image_url = None
|
||||||
if first_frame_image is not None:
|
if first_frame_image is not None:
|
||||||
image_url = (await upload_images_to_comfyapi(first_frame_image, max_images=1, auth_kwargs=kwargs))[0]
|
image_url = (await upload_images_to_comfyapi(first_frame_image, max_images=1, auth_kwargs=auth))[0]
|
||||||
|
|
||||||
video_generate_operation = SynchronousOperation(
|
video_generate_operation = SynchronousOperation(
|
||||||
endpoint=ApiEndpoint(
|
endpoint=ApiEndpoint(
|
||||||
@ -426,7 +453,7 @@ class MinimaxHailuoVideoNode:
|
|||||||
duration=duration,
|
duration=duration,
|
||||||
resolution=resolution,
|
resolution=resolution,
|
||||||
),
|
),
|
||||||
auth_kwargs=kwargs,
|
auth_kwargs=auth,
|
||||||
)
|
)
|
||||||
response = await video_generate_operation.execute()
|
response = await video_generate_operation.execute()
|
||||||
|
|
||||||
@ -447,8 +474,8 @@ class MinimaxHailuoVideoNode:
|
|||||||
failed_statuses=["Fail"],
|
failed_statuses=["Fail"],
|
||||||
status_extractor=lambda x: x.status.value,
|
status_extractor=lambda x: x.status.value,
|
||||||
estimated_duration=average_duration,
|
estimated_duration=average_duration,
|
||||||
node_id=unique_id,
|
node_id=cls.hidden.unique_id,
|
||||||
auth_kwargs=kwargs,
|
auth_kwargs=auth,
|
||||||
)
|
)
|
||||||
task_result = await video_generate_operation.execute()
|
task_result = await video_generate_operation.execute()
|
||||||
|
|
||||||
@ -464,7 +491,7 @@ class MinimaxHailuoVideoNode:
|
|||||||
query_params={"file_id": int(file_id)},
|
query_params={"file_id": int(file_id)},
|
||||||
),
|
),
|
||||||
request=EmptyRequest(),
|
request=EmptyRequest(),
|
||||||
auth_kwargs=kwargs,
|
auth_kwargs=auth,
|
||||||
)
|
)
|
||||||
file_result = await file_retrieve_operation.execute()
|
file_result = await file_retrieve_operation.execute()
|
||||||
|
|
||||||
@ -474,34 +501,31 @@ class MinimaxHailuoVideoNode:
|
|||||||
f"No video was found in the response. Full response: {file_result.model_dump()}"
|
f"No video was found in the response. Full response: {file_result.model_dump()}"
|
||||||
)
|
)
|
||||||
logging.info(f"Generated video URL: {file_url}")
|
logging.info(f"Generated video URL: {file_url}")
|
||||||
if unique_id:
|
if cls.hidden.unique_id:
|
||||||
if hasattr(file_result.file, "backup_download_url"):
|
if hasattr(file_result.file, "backup_download_url"):
|
||||||
message = f"Result URL: {file_url}\nBackup URL: {file_result.file.backup_download_url}"
|
message = f"Result URL: {file_url}\nBackup URL: {file_result.file.backup_download_url}"
|
||||||
else:
|
else:
|
||||||
message = f"Result URL: {file_url}"
|
message = f"Result URL: {file_url}"
|
||||||
PromptServer.instance.send_progress_text(message, unique_id)
|
PromptServer.instance.send_progress_text(message, cls.hidden.unique_id)
|
||||||
|
|
||||||
video_io = await download_url_to_bytesio(file_url)
|
video_io = await download_url_to_bytesio(file_url)
|
||||||
if video_io is None:
|
if video_io is None:
|
||||||
error_msg = f"Failed to download video from {file_url}"
|
error_msg = f"Failed to download video from {file_url}"
|
||||||
logging.error(error_msg)
|
logging.error(error_msg)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
return (VideoFromFile(video_io),)
|
return comfy_io.NodeOutput(VideoFromFile(video_io))
|
||||||
|
|
||||||
|
|
||||||
# A dictionary that contains all nodes you want to export with their names
|
class MinimaxExtension(ComfyExtension):
|
||||||
# NOTE: names should be globally unique
|
@override
|
||||||
NODE_CLASS_MAPPINGS = {
|
async def get_node_list(self) -> list[type[comfy_io.ComfyNode]]:
|
||||||
"MinimaxTextToVideoNode": MinimaxTextToVideoNode,
|
return [
|
||||||
"MinimaxImageToVideoNode": MinimaxImageToVideoNode,
|
MinimaxTextToVideoNode,
|
||||||
# "MinimaxSubjectToVideoNode": MinimaxSubjectToVideoNode,
|
MinimaxImageToVideoNode,
|
||||||
"MinimaxHailuoVideoNode": MinimaxHailuoVideoNode,
|
# MinimaxSubjectToVideoNode,
|
||||||
}
|
MinimaxHailuoVideoNode,
|
||||||
|
]
|
||||||
|
|
||||||
# A dictionary that contains the friendly/humanly readable titles for the nodes
|
|
||||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
async def comfy_entrypoint() -> MinimaxExtension:
|
||||||
"MinimaxTextToVideoNode": "MiniMax Text to Video",
|
return MinimaxExtension()
|
||||||
"MinimaxImageToVideoNode": "MiniMax Image to Video",
|
|
||||||
"MinimaxSubjectToVideoNode": "MiniMax Subject to Video",
|
|
||||||
"MinimaxHailuoVideoNode": "MiniMax Hailuo Video",
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Callable, Optional, TypeVar
|
from typing import Any, Callable, Optional, TypeVar
|
||||||
import torch
|
import torch
|
||||||
|
from typing_extensions import override
|
||||||
from comfy_api_nodes.util.validation_utils import (
|
from comfy_api_nodes.util.validation_utils import (
|
||||||
get_image_dimensions,
|
get_image_dimensions,
|
||||||
validate_image_dimensions,
|
validate_image_dimensions,
|
||||||
@ -26,11 +27,9 @@ from comfy_api_nodes.apinode_utils import (
|
|||||||
upload_images_to_comfyapi,
|
upload_images_to_comfyapi,
|
||||||
upload_video_to_comfyapi,
|
upload_video_to_comfyapi,
|
||||||
)
|
)
|
||||||
from comfy_api_nodes.mapper_utils import model_field_to_node_input
|
|
||||||
|
|
||||||
from comfy_api.input.video_types import VideoInput
|
from comfy_api.input import VideoInput
|
||||||
from comfy.comfy_types.node_typing import IO
|
from comfy_api.latest import ComfyExtension, InputImpl, io as comfy_io
|
||||||
from comfy_api.input_impl import VideoFromFile
|
|
||||||
import av
|
import av
|
||||||
import io
|
import io
|
||||||
|
|
||||||
@ -362,7 +361,7 @@ def trim_video(video: VideoInput, duration_sec: float) -> VideoInput:
|
|||||||
|
|
||||||
# Return as VideoFromFile using the buffer
|
# Return as VideoFromFile using the buffer
|
||||||
output_buffer.seek(0)
|
output_buffer.seek(0)
|
||||||
return VideoFromFile(output_buffer)
|
return InputImpl.VideoFromFile(output_buffer)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Clean up on error
|
# Clean up on error
|
||||||
@ -373,166 +372,150 @@ def trim_video(video: VideoInput, duration_sec: float) -> VideoInput:
|
|||||||
raise RuntimeError(f"Failed to trim video: {str(e)}") from e
|
raise RuntimeError(f"Failed to trim video: {str(e)}") from e
|
||||||
|
|
||||||
|
|
||||||
# --- BaseMoonvalleyVideoNode ---
|
def parse_width_height_from_res(resolution: str):
|
||||||
class BaseMoonvalleyVideoNode:
|
# Accepts a string like "16:9 (1920 x 1080)" and returns width, height as a dict
|
||||||
def parseWidthHeightFromRes(self, resolution: str):
|
res_map = {
|
||||||
# Accepts a string like "16:9 (1920 x 1080)" and returns width, height as a dict
|
"16:9 (1920 x 1080)": {"width": 1920, "height": 1080},
|
||||||
res_map = {
|
"9:16 (1080 x 1920)": {"width": 1080, "height": 1920},
|
||||||
"16:9 (1920 x 1080)": {"width": 1920, "height": 1080},
|
"1:1 (1152 x 1152)": {"width": 1152, "height": 1152},
|
||||||
"9:16 (1080 x 1920)": {"width": 1080, "height": 1920},
|
"4:3 (1536 x 1152)": {"width": 1536, "height": 1152},
|
||||||
"1:1 (1152 x 1152)": {"width": 1152, "height": 1152},
|
"3:4 (1152 x 1536)": {"width": 1152, "height": 1536},
|
||||||
"4:3 (1536 x 1152)": {"width": 1536, "height": 1152},
|
"21:9 (2560 x 1080)": {"width": 2560, "height": 1080},
|
||||||
"3:4 (1152 x 1536)": {"width": 1152, "height": 1536},
|
}
|
||||||
"21:9 (2560 x 1080)": {"width": 2560, "height": 1080},
|
return res_map.get(resolution, {"width": 1920, "height": 1080})
|
||||||
}
|
|
||||||
if resolution in res_map:
|
|
||||||
return res_map[resolution]
|
|
||||||
else:
|
|
||||||
# Default to 1920x1080 if unknown
|
|
||||||
return {"width": 1920, "height": 1080}
|
|
||||||
|
|
||||||
def parseControlParameter(self, value):
|
|
||||||
control_map = {
|
|
||||||
"Motion Transfer": "motion_control",
|
|
||||||
"Canny": "canny_control",
|
|
||||||
"Pose Transfer": "pose_control",
|
|
||||||
"Depth": "depth_control",
|
|
||||||
}
|
|
||||||
if value in control_map:
|
|
||||||
return control_map[value]
|
|
||||||
else:
|
|
||||||
return control_map["Motion Transfer"]
|
|
||||||
|
|
||||||
async def get_response(
|
def parse_control_parameter(value):
|
||||||
self, task_id: str, auth_kwargs: dict[str, str], node_id: Optional[str] = None
|
control_map = {
|
||||||
) -> MoonvalleyPromptResponse:
|
"Motion Transfer": "motion_control",
|
||||||
return await poll_until_finished(
|
"Canny": "canny_control",
|
||||||
auth_kwargs,
|
"Pose Transfer": "pose_control",
|
||||||
ApiEndpoint(
|
"Depth": "depth_control",
|
||||||
path=f"{API_PROMPTS_ENDPOINT}/{task_id}",
|
}
|
||||||
method=HttpMethod.GET,
|
return control_map.get(value, control_map["Motion Transfer"])
|
||||||
request_model=EmptyRequest,
|
|
||||||
response_model=MoonvalleyPromptResponse,
|
|
||||||
),
|
async def get_response(
|
||||||
result_url_extractor=get_video_url_from_response,
|
task_id: str, auth_kwargs: dict[str, str], node_id: Optional[str] = None
|
||||||
node_id=node_id,
|
) -> MoonvalleyPromptResponse:
|
||||||
)
|
return await poll_until_finished(
|
||||||
|
auth_kwargs,
|
||||||
|
ApiEndpoint(
|
||||||
|
path=f"{API_PROMPTS_ENDPOINT}/{task_id}",
|
||||||
|
method=HttpMethod.GET,
|
||||||
|
request_model=EmptyRequest,
|
||||||
|
response_model=MoonvalleyPromptResponse,
|
||||||
|
),
|
||||||
|
result_url_extractor=get_video_url_from_response,
|
||||||
|
node_id=node_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MoonvalleyImg2VideoNode(comfy_io.ComfyNode):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def define_schema(cls) -> comfy_io.Schema:
|
||||||
return {
|
return comfy_io.Schema(
|
||||||
"required": {
|
node_id="MoonvalleyImg2VideoNode",
|
||||||
"prompt": model_field_to_node_input(
|
display_name="Moonvalley Marey Image to Video",
|
||||||
IO.STRING,
|
category="api node/video/Moonvalley Marey",
|
||||||
MoonvalleyTextToVideoRequest,
|
description="Moonvalley Marey Image to Video Node",
|
||||||
"prompt_text",
|
inputs=[
|
||||||
|
comfy_io.Image.Input(
|
||||||
|
"image",
|
||||||
|
tooltip="The reference image used to generate the video",
|
||||||
|
),
|
||||||
|
comfy_io.String.Input(
|
||||||
|
"prompt",
|
||||||
multiline=True,
|
multiline=True,
|
||||||
),
|
),
|
||||||
"negative_prompt": model_field_to_node_input(
|
comfy_io.String.Input(
|
||||||
IO.STRING,
|
|
||||||
MoonvalleyTextToVideoInferenceParams,
|
|
||||||
"negative_prompt",
|
"negative_prompt",
|
||||||
multiline=True,
|
multiline=True,
|
||||||
default="<synthetic> <scene cut> gopro, bright, contrast, static, overexposed, vignette, artifacts, still, noise, texture, scanlines, videogame, 360 camera, VR, transition, flare, saturation, distorted, warped, wide angle, saturated, vibrant, glowing, cross dissolve, cheesy, ugly hands, mutated hands, mutant, disfigured, extra fingers, blown out, horrible, blurry, worst quality, bad, dissolve, melt, fade in, fade out, wobbly, weird, low quality, plastic, stock footage, video camera, boring",
|
default="<synthetic> <scene cut> gopro, bright, contrast, static, overexposed, vignette, "
|
||||||
|
"artifacts, still, noise, texture, scanlines, videogame, 360 camera, VR, transition, "
|
||||||
|
"flare, saturation, distorted, warped, wide angle, saturated, vibrant, glowing, "
|
||||||
|
"cross dissolve, cheesy, ugly hands, mutated hands, mutant, disfigured, extra fingers, "
|
||||||
|
"blown out, horrible, blurry, worst quality, bad, dissolve, melt, fade in, fade out, "
|
||||||
|
"wobbly, weird, low quality, plastic, stock footage, video camera, boring",
|
||||||
|
tooltip="Negative prompt text",
|
||||||
),
|
),
|
||||||
"resolution": (
|
comfy_io.Combo.Input(
|
||||||
IO.COMBO,
|
"resolution",
|
||||||
{
|
options=[
|
||||||
"options": [
|
"16:9 (1920 x 1080)",
|
||||||
"16:9 (1920 x 1080)",
|
"9:16 (1080 x 1920)",
|
||||||
"9:16 (1080 x 1920)",
|
"1:1 (1152 x 1152)",
|
||||||
"1:1 (1152 x 1152)",
|
"4:3 (1536 x 1152)",
|
||||||
"4:3 (1440 x 1080)",
|
"3:4 (1152 x 1536)",
|
||||||
"3:4 (1080 x 1440)",
|
"21:9 (2560 x 1080)",
|
||||||
"21:9 (2560 x 1080)",
|
],
|
||||||
],
|
default="16:9 (1920 x 1080)",
|
||||||
"default": "16:9 (1920 x 1080)",
|
tooltip="Resolution of the output video",
|
||||||
"tooltip": "Resolution of the output video",
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
"prompt_adherence": model_field_to_node_input(
|
comfy_io.Float.Input(
|
||||||
IO.FLOAT,
|
"prompt_adherence",
|
||||||
MoonvalleyTextToVideoInferenceParams,
|
|
||||||
"guidance_scale",
|
|
||||||
default=10.0,
|
default=10.0,
|
||||||
step=1,
|
min=1.0,
|
||||||
min=1,
|
max=20.0,
|
||||||
max=20,
|
step=1.0,
|
||||||
|
tooltip="Guidance scale for generation control",
|
||||||
),
|
),
|
||||||
"seed": model_field_to_node_input(
|
comfy_io.Int.Input(
|
||||||
IO.INT,
|
|
||||||
MoonvalleyTextToVideoInferenceParams,
|
|
||||||
"seed",
|
"seed",
|
||||||
default=9,
|
default=9,
|
||||||
min=0,
|
min=0,
|
||||||
max=4294967295,
|
max=4294967295,
|
||||||
step=1,
|
step=1,
|
||||||
display="number",
|
display_mode=comfy_io.NumberDisplay.number,
|
||||||
tooltip="Random seed value",
|
tooltip="Random seed value",
|
||||||
),
|
),
|
||||||
"steps": model_field_to_node_input(
|
comfy_io.Int.Input(
|
||||||
IO.INT,
|
|
||||||
MoonvalleyTextToVideoInferenceParams,
|
|
||||||
"steps",
|
"steps",
|
||||||
default=100,
|
default=100,
|
||||||
min=1,
|
min=1,
|
||||||
max=100,
|
max=100,
|
||||||
|
step=1,
|
||||||
|
tooltip="Number of denoising steps",
|
||||||
),
|
),
|
||||||
},
|
],
|
||||||
"hidden": {
|
outputs=[comfy_io.Video.Output()],
|
||||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
hidden=[
|
||||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
comfy_io.Hidden.auth_token_comfy_org,
|
||||||
"unique_id": "UNIQUE_ID",
|
comfy_io.Hidden.api_key_comfy_org,
|
||||||
},
|
comfy_io.Hidden.unique_id,
|
||||||
"optional": {
|
],
|
||||||
"image": model_field_to_node_input(
|
is_api_node=True,
|
||||||
IO.IMAGE,
|
)
|
||||||
MoonvalleyTextToVideoRequest,
|
|
||||||
"image_url",
|
|
||||||
tooltip="The reference image used to generate the video",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
RETURN_TYPES = ("STRING",)
|
|
||||||
FUNCTION = "generate"
|
|
||||||
CATEGORY = "api node/video/Moonvalley Marey"
|
|
||||||
API_NODE = True
|
|
||||||
|
|
||||||
def generate(self, **kwargs):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# --- MoonvalleyImg2VideoNode ---
|
|
||||||
class MoonvalleyImg2VideoNode(BaseMoonvalleyVideoNode):
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
async def execute(
|
||||||
return super().INPUT_TYPES()
|
cls,
|
||||||
|
image: torch.Tensor,
|
||||||
RETURN_TYPES = ("VIDEO",)
|
prompt: str,
|
||||||
RETURN_NAMES = ("video",)
|
negative_prompt: str,
|
||||||
DESCRIPTION = "Moonvalley Marey Image to Video Node"
|
resolution: str,
|
||||||
|
prompt_adherence: float,
|
||||||
async def generate(
|
seed: int,
|
||||||
self, prompt, negative_prompt, unique_id: Optional[str] = None, **kwargs
|
steps: int,
|
||||||
):
|
) -> comfy_io.NodeOutput:
|
||||||
image = kwargs.get("image", None)
|
|
||||||
if image is None:
|
|
||||||
raise MoonvalleyApiError("image is required")
|
|
||||||
|
|
||||||
validate_input_image(image, True)
|
validate_input_image(image, True)
|
||||||
validate_prompts(prompt, negative_prompt, MOONVALLEY_MAREY_MAX_PROMPT_LENGTH)
|
validate_prompts(prompt, negative_prompt, MOONVALLEY_MAREY_MAX_PROMPT_LENGTH)
|
||||||
width_height = self.parseWidthHeightFromRes(kwargs.get("resolution"))
|
width_height = parse_width_height_from_res(resolution)
|
||||||
|
|
||||||
|
auth = {
|
||||||
|
"auth_token": cls.hidden.auth_token_comfy_org,
|
||||||
|
"comfy_api_key": cls.hidden.api_key_comfy_org,
|
||||||
|
}
|
||||||
|
|
||||||
inference_params = MoonvalleyTextToVideoInferenceParams(
|
inference_params = MoonvalleyTextToVideoInferenceParams(
|
||||||
negative_prompt=negative_prompt,
|
negative_prompt=negative_prompt,
|
||||||
steps=kwargs.get("steps"),
|
steps=steps,
|
||||||
seed=kwargs.get("seed"),
|
seed=seed,
|
||||||
guidance_scale=kwargs.get("prompt_adherence"),
|
guidance_scale=prompt_adherence,
|
||||||
num_frames=128,
|
num_frames=128,
|
||||||
width=width_height.get("width"),
|
width=width_height["width"],
|
||||||
height=width_height.get("height"),
|
height=width_height["height"],
|
||||||
use_negative_prompts=True,
|
use_negative_prompts=True,
|
||||||
)
|
)
|
||||||
"""Upload image to comfy backend to have a URL available for further processing"""
|
"""Upload image to comfy backend to have a URL available for further processing"""
|
||||||
@ -541,7 +524,7 @@ class MoonvalleyImg2VideoNode(BaseMoonvalleyVideoNode):
|
|||||||
|
|
||||||
image_url = (
|
image_url = (
|
||||||
await upload_images_to_comfyapi(
|
await upload_images_to_comfyapi(
|
||||||
image, max_images=1, auth_kwargs=kwargs, mime_type=mime_type
|
image, max_images=1, auth_kwargs=auth, mime_type=mime_type
|
||||||
)
|
)
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
@ -556,127 +539,102 @@ class MoonvalleyImg2VideoNode(BaseMoonvalleyVideoNode):
|
|||||||
response_model=MoonvalleyPromptResponse,
|
response_model=MoonvalleyPromptResponse,
|
||||||
),
|
),
|
||||||
request=request,
|
request=request,
|
||||||
auth_kwargs=kwargs,
|
auth_kwargs=auth,
|
||||||
)
|
)
|
||||||
task_creation_response = await initial_operation.execute()
|
task_creation_response = await initial_operation.execute()
|
||||||
validate_task_creation_response(task_creation_response)
|
validate_task_creation_response(task_creation_response)
|
||||||
task_id = task_creation_response.id
|
task_id = task_creation_response.id
|
||||||
|
|
||||||
final_response = await self.get_response(
|
final_response = await get_response(
|
||||||
task_id, auth_kwargs=kwargs, node_id=unique_id
|
task_id, auth_kwargs=auth, node_id=cls.hidden.unique_id
|
||||||
)
|
)
|
||||||
video = await download_url_to_video_output(final_response.output_url)
|
video = await download_url_to_video_output(final_response.output_url)
|
||||||
return (video,)
|
return comfy_io.NodeOutput(video)
|
||||||
|
|
||||||
|
|
||||||
# --- MoonvalleyVid2VidNode ---
|
class MoonvalleyVideo2VideoNode(comfy_io.ComfyNode):
|
||||||
class MoonvalleyVideo2VideoNode(BaseMoonvalleyVideoNode):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def define_schema(cls) -> comfy_io.Schema:
|
||||||
return {
|
return comfy_io.Schema(
|
||||||
"required": {
|
node_id="MoonvalleyVideo2VideoNode",
|
||||||
"prompt": model_field_to_node_input(
|
display_name="Moonvalley Marey Video to Video",
|
||||||
IO.STRING,
|
category="api node/video/Moonvalley Marey",
|
||||||
MoonvalleyVideoToVideoRequest,
|
description="",
|
||||||
"prompt_text",
|
inputs=[
|
||||||
|
comfy_io.String.Input(
|
||||||
|
"prompt",
|
||||||
multiline=True,
|
multiline=True,
|
||||||
|
tooltip="Describes the video to generate",
|
||||||
),
|
),
|
||||||
"negative_prompt": model_field_to_node_input(
|
comfy_io.String.Input(
|
||||||
IO.STRING,
|
|
||||||
MoonvalleyVideoToVideoInferenceParams,
|
|
||||||
"negative_prompt",
|
"negative_prompt",
|
||||||
multiline=True,
|
multiline=True,
|
||||||
default="<synthetic> <scene cut> gopro, bright, contrast, static, overexposed, vignette, artifacts, still, noise, texture, scanlines, videogame, 360 camera, VR, transition, flare, saturation, distorted, warped, wide angle, saturated, vibrant, glowing, cross dissolve, cheesy, ugly hands, mutated hands, mutant, disfigured, extra fingers, blown out, horrible, blurry, worst quality, bad, dissolve, melt, fade in, fade out, wobbly, weird, low quality, plastic, stock footage, video camera, boring",
|
default="<synthetic> <scene cut> gopro, bright, contrast, static, overexposed, vignette, "
|
||||||
|
"artifacts, still, noise, texture, scanlines, videogame, 360 camera, VR, transition, "
|
||||||
|
"flare, saturation, distorted, warped, wide angle, saturated, vibrant, glowing, "
|
||||||
|
"cross dissolve, cheesy, ugly hands, mutated hands, mutant, disfigured, extra fingers, "
|
||||||
|
"blown out, horrible, blurry, worst quality, bad, dissolve, melt, fade in, fade out, "
|
||||||
|
"wobbly, weird, low quality, plastic, stock footage, video camera, boring",
|
||||||
|
tooltip="Negative prompt text",
|
||||||
),
|
),
|
||||||
"seed": model_field_to_node_input(
|
comfy_io.Int.Input(
|
||||||
IO.INT,
|
|
||||||
MoonvalleyVideoToVideoInferenceParams,
|
|
||||||
"seed",
|
"seed",
|
||||||
default=9,
|
default=9,
|
||||||
min=0,
|
min=0,
|
||||||
max=4294967295,
|
max=4294967295,
|
||||||
step=1,
|
step=1,
|
||||||
display="number",
|
display_mode=comfy_io.NumberDisplay.number,
|
||||||
tooltip="Random seed value",
|
tooltip="Random seed value",
|
||||||
control_after_generate=False,
|
control_after_generate=False,
|
||||||
),
|
),
|
||||||
"prompt_adherence": model_field_to_node_input(
|
comfy_io.Video.Input(
|
||||||
IO.FLOAT,
|
"video",
|
||||||
MoonvalleyVideoToVideoInferenceParams,
|
tooltip="The reference video used to generate the output video. Must be at least 5 seconds long. "
|
||||||
"guidance_scale",
|
"Videos longer than 5s will be automatically trimmed. Only MP4 format supported.",
|
||||||
default=10.0,
|
),
|
||||||
|
comfy_io.Combo.Input(
|
||||||
|
"control_type",
|
||||||
|
options=["Motion Transfer", "Pose Transfer"],
|
||||||
|
default="Motion Transfer",
|
||||||
|
optional=True,
|
||||||
|
),
|
||||||
|
comfy_io.Int.Input(
|
||||||
|
"motion_intensity",
|
||||||
|
default=100,
|
||||||
|
min=0,
|
||||||
|
max=100,
|
||||||
step=1,
|
step=1,
|
||||||
min=1,
|
tooltip="Only used if control_type is 'Motion Transfer'",
|
||||||
max=20,
|
optional=True,
|
||||||
),
|
),
|
||||||
},
|
],
|
||||||
"hidden": {
|
outputs=[comfy_io.Video.Output()],
|
||||||
"auth_token": "AUTH_TOKEN_COMFY_ORG",
|
hidden=[
|
||||||
"comfy_api_key": "API_KEY_COMFY_ORG",
|
comfy_io.Hidden.auth_token_comfy_org,
|
||||||
"unique_id": "UNIQUE_ID",
|
comfy_io.Hidden.api_key_comfy_org,
|
||||||
},
|
comfy_io.Hidden.unique_id,
|
||||||
"optional": {
|
],
|
||||||
"video": (
|
is_api_node=True,
|
||||||
IO.VIDEO,
|
)
|
||||||
{
|
|
||||||
"default": "",
|
@classmethod
|
||||||
"multiline": False,
|
async def execute(
|
||||||
"tooltip": "The reference video used to generate the output video. Must be at least 5 seconds long. Videos longer than 5s will be automatically trimmed. Only MP4 format supported.",
|
cls,
|
||||||
},
|
prompt: str,
|
||||||
),
|
negative_prompt: str,
|
||||||
"control_type": (
|
seed: int,
|
||||||
["Motion Transfer", "Pose Transfer"],
|
video: Optional[VideoInput] = None,
|
||||||
{"default": "Motion Transfer"},
|
control_type: str = "Motion Transfer",
|
||||||
),
|
motion_intensity: Optional[int] = 100,
|
||||||
"motion_intensity": (
|
) -> comfy_io.NodeOutput:
|
||||||
"INT",
|
auth = {
|
||||||
{
|
"auth_token": cls.hidden.auth_token_comfy_org,
|
||||||
"default": 100,
|
"comfy_api_key": cls.hidden.api_key_comfy_org,
|
||||||
"step": 1,
|
|
||||||
"min": 0,
|
|
||||||
"max": 100,
|
|
||||||
"tooltip": "Only used if control_type is 'Motion Transfer'",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
"image": model_field_to_node_input(
|
|
||||||
IO.IMAGE,
|
|
||||||
MoonvalleyTextToVideoRequest,
|
|
||||||
"image_url",
|
|
||||||
tooltip="The reference image used to generate the video",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("VIDEO",)
|
validated_video = validate_video_to_video_input(video)
|
||||||
RETURN_NAMES = ("video",)
|
video_url = await upload_video_to_comfyapi(validated_video, auth_kwargs=auth)
|
||||||
|
|
||||||
async def generate(
|
|
||||||
self, prompt, negative_prompt, unique_id: Optional[str] = None, **kwargs
|
|
||||||
):
|
|
||||||
video = kwargs.get("video")
|
|
||||||
image = kwargs.get("image", None)
|
|
||||||
|
|
||||||
if not video:
|
|
||||||
raise MoonvalleyApiError("video is required")
|
|
||||||
|
|
||||||
video_url = ""
|
|
||||||
if video:
|
|
||||||
validated_video = validate_video_to_video_input(video)
|
|
||||||
video_url = await upload_video_to_comfyapi(
|
|
||||||
validated_video, auth_kwargs=kwargs
|
|
||||||
)
|
|
||||||
mime_type = "image/png"
|
|
||||||
|
|
||||||
if not image is None:
|
|
||||||
validate_input_image(image, with_frame_conditioning=True)
|
|
||||||
image_url = await upload_images_to_comfyapi(
|
|
||||||
image=image, auth_kwargs=kwargs, max_images=1, mime_type=mime_type
|
|
||||||
)
|
|
||||||
control_type = kwargs.get("control_type")
|
|
||||||
motion_intensity = kwargs.get("motion_intensity")
|
|
||||||
|
|
||||||
"""Validate prompts and inference input"""
|
"""Validate prompts and inference input"""
|
||||||
validate_prompts(prompt, negative_prompt)
|
validate_prompts(prompt, negative_prompt)
|
||||||
@ -688,11 +646,11 @@ class MoonvalleyVideo2VideoNode(BaseMoonvalleyVideoNode):
|
|||||||
|
|
||||||
inference_params = MoonvalleyVideoToVideoInferenceParams(
|
inference_params = MoonvalleyVideoToVideoInferenceParams(
|
||||||
negative_prompt=negative_prompt,
|
negative_prompt=negative_prompt,
|
||||||
seed=kwargs.get("seed"),
|
seed=seed,
|
||||||
control_params=control_params,
|
control_params=control_params,
|
||||||
)
|
)
|
||||||
|
|
||||||
control = self.parseControlParameter(control_type)
|
control = parse_control_parameter(control_type)
|
||||||
|
|
||||||
request = MoonvalleyVideoToVideoRequest(
|
request = MoonvalleyVideoToVideoRequest(
|
||||||
control_type=control,
|
control_type=control,
|
||||||
@ -700,7 +658,6 @@ class MoonvalleyVideo2VideoNode(BaseMoonvalleyVideoNode):
|
|||||||
prompt_text=prompt,
|
prompt_text=prompt,
|
||||||
inference_params=inference_params,
|
inference_params=inference_params,
|
||||||
)
|
)
|
||||||
request.image_url = image_url if not image is None else None
|
|
||||||
|
|
||||||
initial_operation = SynchronousOperation(
|
initial_operation = SynchronousOperation(
|
||||||
endpoint=ApiEndpoint(
|
endpoint=ApiEndpoint(
|
||||||
@ -710,58 +667,125 @@ class MoonvalleyVideo2VideoNode(BaseMoonvalleyVideoNode):
|
|||||||
response_model=MoonvalleyPromptResponse,
|
response_model=MoonvalleyPromptResponse,
|
||||||
),
|
),
|
||||||
request=request,
|
request=request,
|
||||||
auth_kwargs=kwargs,
|
auth_kwargs=auth,
|
||||||
)
|
)
|
||||||
task_creation_response = await initial_operation.execute()
|
task_creation_response = await initial_operation.execute()
|
||||||
validate_task_creation_response(task_creation_response)
|
validate_task_creation_response(task_creation_response)
|
||||||
task_id = task_creation_response.id
|
task_id = task_creation_response.id
|
||||||
|
|
||||||
final_response = await self.get_response(
|
final_response = await get_response(
|
||||||
task_id, auth_kwargs=kwargs, node_id=unique_id
|
task_id, auth_kwargs=auth, node_id=cls.hidden.unique_id
|
||||||
)
|
)
|
||||||
|
|
||||||
video = await download_url_to_video_output(final_response.output_url)
|
video = await download_url_to_video_output(final_response.output_url)
|
||||||
|
return comfy_io.NodeOutput(video)
|
||||||
return (video,)
|
|
||||||
|
|
||||||
|
|
||||||
# --- MoonvalleyTxt2VideoNode ---
|
class MoonvalleyTxt2VideoNode(comfy_io.ComfyNode):
|
||||||
class MoonvalleyTxt2VideoNode(BaseMoonvalleyVideoNode):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
RETURN_TYPES = ("VIDEO",)
|
|
||||||
RETURN_NAMES = ("video",)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def define_schema(cls) -> comfy_io.Schema:
|
||||||
input_types = super().INPUT_TYPES()
|
return comfy_io.Schema(
|
||||||
# Remove image-specific parameters
|
node_id="MoonvalleyTxt2VideoNode",
|
||||||
for param in ["image"]:
|
display_name="Moonvalley Marey Text to Video",
|
||||||
if param in input_types["optional"]:
|
category="api node/video/Moonvalley Marey",
|
||||||
del input_types["optional"][param]
|
description="",
|
||||||
return input_types
|
inputs=[
|
||||||
|
comfy_io.String.Input(
|
||||||
|
"prompt",
|
||||||
|
multiline=True,
|
||||||
|
),
|
||||||
|
comfy_io.String.Input(
|
||||||
|
"negative_prompt",
|
||||||
|
multiline=True,
|
||||||
|
default="<synthetic> <scene cut> gopro, bright, contrast, static, overexposed, vignette, "
|
||||||
|
"artifacts, still, noise, texture, scanlines, videogame, 360 camera, VR, transition, "
|
||||||
|
"flare, saturation, distorted, warped, wide angle, saturated, vibrant, glowing, "
|
||||||
|
"cross dissolve, cheesy, ugly hands, mutated hands, mutant, disfigured, extra fingers, "
|
||||||
|
"blown out, horrible, blurry, worst quality, bad, dissolve, melt, fade in, fade out, "
|
||||||
|
"wobbly, weird, low quality, plastic, stock footage, video camera, boring",
|
||||||
|
tooltip="Negative prompt text",
|
||||||
|
),
|
||||||
|
comfy_io.Combo.Input(
|
||||||
|
"resolution",
|
||||||
|
options=[
|
||||||
|
"16:9 (1920 x 1080)",
|
||||||
|
"9:16 (1080 x 1920)",
|
||||||
|
"1:1 (1152 x 1152)",
|
||||||
|
"4:3 (1536 x 1152)",
|
||||||
|
"3:4 (1152 x 1536)",
|
||||||
|
"21:9 (2560 x 1080)",
|
||||||
|
],
|
||||||
|
default="16:9 (1920 x 1080)",
|
||||||
|
tooltip="Resolution of the output video",
|
||||||
|
),
|
||||||
|
comfy_io.Float.Input(
|
||||||
|
"prompt_adherence",
|
||||||
|
default=10.0,
|
||||||
|
min=1.0,
|
||||||
|
max=20.0,
|
||||||
|
step=1.0,
|
||||||
|
tooltip="Guidance scale for generation control",
|
||||||
|
),
|
||||||
|
comfy_io.Int.Input(
|
||||||
|
"seed",
|
||||||
|
default=9,
|
||||||
|
min=0,
|
||||||
|
max=4294967295,
|
||||||
|
step=1,
|
||||||
|
display_mode=comfy_io.NumberDisplay.number,
|
||||||
|
tooltip="Random seed value",
|
||||||
|
),
|
||||||
|
comfy_io.Int.Input(
|
||||||
|
"steps",
|
||||||
|
default=100,
|
||||||
|
min=1,
|
||||||
|
max=100,
|
||||||
|
step=1,
|
||||||
|
tooltip="Inference steps",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
outputs=[comfy_io.Video.Output()],
|
||||||
|
hidden=[
|
||||||
|
comfy_io.Hidden.auth_token_comfy_org,
|
||||||
|
comfy_io.Hidden.api_key_comfy_org,
|
||||||
|
comfy_io.Hidden.unique_id,
|
||||||
|
],
|
||||||
|
is_api_node=True,
|
||||||
|
)
|
||||||
|
|
||||||
async def generate(
|
@classmethod
|
||||||
self, prompt, negative_prompt, unique_id: Optional[str] = None, **kwargs
|
async def execute(
|
||||||
):
|
cls,
|
||||||
|
prompt: str,
|
||||||
|
negative_prompt: str,
|
||||||
|
resolution: str,
|
||||||
|
prompt_adherence: float,
|
||||||
|
seed: int,
|
||||||
|
steps: int,
|
||||||
|
) -> comfy_io.NodeOutput:
|
||||||
validate_prompts(prompt, negative_prompt, MOONVALLEY_MAREY_MAX_PROMPT_LENGTH)
|
validate_prompts(prompt, negative_prompt, MOONVALLEY_MAREY_MAX_PROMPT_LENGTH)
|
||||||
width_height = self.parseWidthHeightFromRes(kwargs.get("resolution"))
|
width_height = parse_width_height_from_res(resolution)
|
||||||
|
|
||||||
|
auth = {
|
||||||
|
"auth_token": cls.hidden.auth_token_comfy_org,
|
||||||
|
"comfy_api_key": cls.hidden.api_key_comfy_org,
|
||||||
|
}
|
||||||
|
|
||||||
inference_params = MoonvalleyTextToVideoInferenceParams(
|
inference_params = MoonvalleyTextToVideoInferenceParams(
|
||||||
negative_prompt=negative_prompt,
|
negative_prompt=negative_prompt,
|
||||||
steps=kwargs.get("steps"),
|
steps=steps,
|
||||||
seed=kwargs.get("seed"),
|
seed=seed,
|
||||||
guidance_scale=kwargs.get("prompt_adherence"),
|
guidance_scale=prompt_adherence,
|
||||||
num_frames=128,
|
num_frames=128,
|
||||||
width=width_height.get("width"),
|
width=width_height["width"],
|
||||||
height=width_height.get("height"),
|
height=width_height["height"],
|
||||||
)
|
)
|
||||||
request = MoonvalleyTextToVideoRequest(
|
request = MoonvalleyTextToVideoRequest(
|
||||||
prompt_text=prompt, inference_params=inference_params
|
prompt_text=prompt, inference_params=inference_params
|
||||||
)
|
)
|
||||||
|
|
||||||
initial_operation = SynchronousOperation(
|
init_op = SynchronousOperation(
|
||||||
endpoint=ApiEndpoint(
|
endpoint=ApiEndpoint(
|
||||||
path=API_TXT2VIDEO_ENDPOINT,
|
path=API_TXT2VIDEO_ENDPOINT,
|
||||||
method=HttpMethod.POST,
|
method=HttpMethod.POST,
|
||||||
@ -769,29 +793,29 @@ class MoonvalleyTxt2VideoNode(BaseMoonvalleyVideoNode):
|
|||||||
response_model=MoonvalleyPromptResponse,
|
response_model=MoonvalleyPromptResponse,
|
||||||
),
|
),
|
||||||
request=request,
|
request=request,
|
||||||
auth_kwargs=kwargs,
|
auth_kwargs=auth,
|
||||||
)
|
)
|
||||||
task_creation_response = await initial_operation.execute()
|
task_creation_response = await init_op.execute()
|
||||||
validate_task_creation_response(task_creation_response)
|
validate_task_creation_response(task_creation_response)
|
||||||
task_id = task_creation_response.id
|
task_id = task_creation_response.id
|
||||||
|
|
||||||
final_response = await self.get_response(
|
final_response = await get_response(
|
||||||
task_id, auth_kwargs=kwargs, node_id=unique_id
|
task_id, auth_kwargs=auth, node_id=cls.hidden.unique_id
|
||||||
)
|
)
|
||||||
|
|
||||||
video = await download_url_to_video_output(final_response.output_url)
|
video = await download_url_to_video_output(final_response.output_url)
|
||||||
return (video,)
|
return comfy_io.NodeOutput(video)
|
||||||
|
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
class MoonvalleyExtension(ComfyExtension):
|
||||||
"MoonvalleyImg2VideoNode": MoonvalleyImg2VideoNode,
|
@override
|
||||||
"MoonvalleyTxt2VideoNode": MoonvalleyTxt2VideoNode,
|
async def get_node_list(self) -> list[type[comfy_io.ComfyNode]]:
|
||||||
"MoonvalleyVideo2VideoNode": MoonvalleyVideo2VideoNode,
|
return [
|
||||||
}
|
MoonvalleyImg2VideoNode,
|
||||||
|
MoonvalleyTxt2VideoNode,
|
||||||
|
MoonvalleyVideo2VideoNode,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
async def comfy_entrypoint() -> MoonvalleyExtension:
|
||||||
"MoonvalleyImg2VideoNode": "Moonvalley Marey Image to Video",
|
return MoonvalleyExtension()
|
||||||
"MoonvalleyTxt2VideoNode": "Moonvalley Marey Text to Video",
|
|
||||||
"MoonvalleyVideo2VideoNode": "Moonvalley Marey Video to Video",
|
|
||||||
}
|
|
||||||
|
|||||||
@ -2,12 +2,12 @@ import nodes
|
|||||||
import torch
|
import torch
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from einops import rearrange
|
from einops import rearrange
|
||||||
|
from typing_extensions import override
|
||||||
import comfy.model_management
|
import comfy.model_management
|
||||||
|
|
||||||
|
from comfy_api.latest import ComfyExtension, io
|
||||||
|
|
||||||
|
|
||||||
MAX_RESOLUTION = nodes.MAX_RESOLUTION
|
|
||||||
|
|
||||||
CAMERA_DICT = {
|
CAMERA_DICT = {
|
||||||
"base_T_norm": 1.5,
|
"base_T_norm": 1.5,
|
||||||
"base_angle": np.pi/3,
|
"base_angle": np.pi/3,
|
||||||
@ -148,32 +148,47 @@ def get_camera_motion(angle, T, speed, n=81):
|
|||||||
RT = np.stack(RT)
|
RT = np.stack(RT)
|
||||||
return RT
|
return RT
|
||||||
|
|
||||||
class WanCameraEmbedding:
|
class WanCameraEmbedding(io.ComfyNode):
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(cls):
|
def define_schema(cls):
|
||||||
return {
|
return io.Schema(
|
||||||
"required": {
|
node_id="WanCameraEmbedding",
|
||||||
"camera_pose":(["Static","Pan Up","Pan Down","Pan Left","Pan Right","Zoom In","Zoom Out","Anti Clockwise (ACW)", "ClockWise (CW)"],{"default":"Static"}),
|
category="camera",
|
||||||
"width": ("INT", {"default": 832, "min": 16, "max": MAX_RESOLUTION, "step": 16}),
|
inputs=[
|
||||||
"height": ("INT", {"default": 480, "min": 16, "max": MAX_RESOLUTION, "step": 16}),
|
io.Combo.Input(
|
||||||
"length": ("INT", {"default": 81, "min": 1, "max": MAX_RESOLUTION, "step": 4}),
|
"camera_pose",
|
||||||
},
|
options=[
|
||||||
"optional":{
|
"Static",
|
||||||
"speed":("FLOAT",{"default":1.0, "min": 0, "max": 10.0, "step": 0.1}),
|
"Pan Up",
|
||||||
"fx":("FLOAT",{"default":0.5, "min": 0, "max": 1, "step": 0.000000001}),
|
"Pan Down",
|
||||||
"fy":("FLOAT",{"default":0.5, "min": 0, "max": 1, "step": 0.000000001}),
|
"Pan Left",
|
||||||
"cx":("FLOAT",{"default":0.5, "min": 0, "max": 1, "step": 0.01}),
|
"Pan Right",
|
||||||
"cy":("FLOAT",{"default":0.5, "min": 0, "max": 1, "step": 0.01}),
|
"Zoom In",
|
||||||
}
|
"Zoom Out",
|
||||||
|
"Anti Clockwise (ACW)",
|
||||||
|
"ClockWise (CW)",
|
||||||
|
],
|
||||||
|
default="Static",
|
||||||
|
),
|
||||||
|
io.Int.Input("width", default=832, min=16, max=nodes.MAX_RESOLUTION, step=16),
|
||||||
|
io.Int.Input("height", default=480, min=16, max=nodes.MAX_RESOLUTION, step=16),
|
||||||
|
io.Int.Input("length", default=81, min=1, max=nodes.MAX_RESOLUTION, step=4),
|
||||||
|
io.Float.Input("speed", default=1.0, min=0, max=10.0, step=0.1, optional=True),
|
||||||
|
io.Float.Input("fx", default=0.5, min=0, max=1, step=0.000000001, optional=True),
|
||||||
|
io.Float.Input("fy", default=0.5, min=0, max=1, step=0.000000001, optional=True),
|
||||||
|
io.Float.Input("cx", default=0.5, min=0, max=1, step=0.01, optional=True),
|
||||||
|
io.Float.Input("cy", default=0.5, min=0, max=1, step=0.01, optional=True),
|
||||||
|
],
|
||||||
|
outputs=[
|
||||||
|
io.WanCameraEmbedding.Output(display_name="camera_embedding"),
|
||||||
|
io.Int.Output(display_name="width"),
|
||||||
|
io.Int.Output(display_name="height"),
|
||||||
|
io.Int.Output(display_name="length"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
}
|
@classmethod
|
||||||
|
def execute(cls, camera_pose, width, height, length, speed=1.0, fx=0.5, fy=0.5, cx=0.5, cy=0.5) -> io.NodeOutput:
|
||||||
RETURN_TYPES = ("WAN_CAMERA_EMBEDDING","INT","INT","INT")
|
|
||||||
RETURN_NAMES = ("camera_embedding","width","height","length")
|
|
||||||
FUNCTION = "run"
|
|
||||||
CATEGORY = "camera"
|
|
||||||
|
|
||||||
def run(self, camera_pose, width, height, length, speed=1.0, fx=0.5, fy=0.5, cx=0.5, cy=0.5):
|
|
||||||
"""
|
"""
|
||||||
Use Camera trajectory as extrinsic parameters to calculate Plücker embeddings (Sitzmannet al., 2021)
|
Use Camera trajectory as extrinsic parameters to calculate Plücker embeddings (Sitzmannet al., 2021)
|
||||||
Adapted from https://github.com/aigc-apps/VideoX-Fun/blob/main/comfyui/comfyui_nodes.py
|
Adapted from https://github.com/aigc-apps/VideoX-Fun/blob/main/comfyui/comfyui_nodes.py
|
||||||
@ -210,9 +225,15 @@ class WanCameraEmbedding:
|
|||||||
control_camera_video = control_camera_video.contiguous().view(b, f // 4, 4, c, h, w).transpose(2, 3)
|
control_camera_video = control_camera_video.contiguous().view(b, f // 4, 4, c, h, w).transpose(2, 3)
|
||||||
control_camera_video = control_camera_video.contiguous().view(b, f // 4, c * 4, h, w).transpose(1, 2)
|
control_camera_video = control_camera_video.contiguous().view(b, f // 4, c * 4, h, w).transpose(1, 2)
|
||||||
|
|
||||||
return (control_camera_video, width, height, length)
|
return io.NodeOutput(control_camera_video, width, height, length)
|
||||||
|
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
class CameraTrajectoryExtension(ComfyExtension):
|
||||||
"WanCameraEmbedding": WanCameraEmbedding,
|
@override
|
||||||
}
|
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
||||||
|
return [
|
||||||
|
WanCameraEmbedding,
|
||||||
|
]
|
||||||
|
|
||||||
|
async def comfy_entrypoint() -> CameraTrajectoryExtension:
|
||||||
|
return CameraTrajectoryExtension()
|
||||||
|
|||||||
@ -1,25 +1,41 @@
|
|||||||
from kornia.filters import canny
|
from kornia.filters import canny
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
import comfy.model_management
|
import comfy.model_management
|
||||||
|
from comfy_api.latest import ComfyExtension, io
|
||||||
|
|
||||||
|
|
||||||
class Canny:
|
class Canny(io.ComfyNode):
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def define_schema(cls):
|
||||||
return {"required": {"image": ("IMAGE",),
|
return io.Schema(
|
||||||
"low_threshold": ("FLOAT", {"default": 0.4, "min": 0.01, "max": 0.99, "step": 0.01}),
|
node_id="Canny",
|
||||||
"high_threshold": ("FLOAT", {"default": 0.8, "min": 0.01, "max": 0.99, "step": 0.01})
|
category="image/preprocessors",
|
||||||
}}
|
inputs=[
|
||||||
|
io.Image.Input("image"),
|
||||||
|
io.Float.Input("low_threshold", default=0.4, min=0.01, max=0.99, step=0.01),
|
||||||
|
io.Float.Input("high_threshold", default=0.8, min=0.01, max=0.99, step=0.01),
|
||||||
|
],
|
||||||
|
outputs=[io.Image.Output()],
|
||||||
|
)
|
||||||
|
|
||||||
RETURN_TYPES = ("IMAGE",)
|
@classmethod
|
||||||
FUNCTION = "detect_edge"
|
def detect_edge(cls, image, low_threshold, high_threshold):
|
||||||
|
# Deprecated: use the V3 schema's `execute` method instead of this.
|
||||||
|
return cls.execute(image, low_threshold, high_threshold)
|
||||||
|
|
||||||
CATEGORY = "image/preprocessors"
|
@classmethod
|
||||||
|
def execute(cls, image, low_threshold, high_threshold) -> io.NodeOutput:
|
||||||
def detect_edge(self, image, low_threshold, high_threshold):
|
|
||||||
output = canny(image.to(comfy.model_management.get_torch_device()).movedim(-1, 1), low_threshold, high_threshold)
|
output = canny(image.to(comfy.model_management.get_torch_device()).movedim(-1, 1), low_threshold, high_threshold)
|
||||||
img_out = output[1].to(comfy.model_management.intermediate_device()).repeat(1, 3, 1, 1).movedim(1, -1)
|
img_out = output[1].to(comfy.model_management.intermediate_device()).repeat(1, 3, 1, 1).movedim(1, -1)
|
||||||
return (img_out,)
|
return io.NodeOutput(img_out)
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
|
||||||
"Canny": Canny,
|
class CannyExtension(ComfyExtension):
|
||||||
}
|
@override
|
||||||
|
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
||||||
|
return [Canny]
|
||||||
|
|
||||||
|
|
||||||
|
async def comfy_entrypoint() -> CannyExtension:
|
||||||
|
return CannyExtension()
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
from comfy_api.latest import ComfyExtension, io
|
||||||
|
|
||||||
|
|
||||||
# https://github.com/WeichenFan/CFG-Zero-star
|
# https://github.com/WeichenFan/CFG-Zero-star
|
||||||
def optimized_scale(positive, negative):
|
def optimized_scale(positive, negative):
|
||||||
positive_flat = positive.reshape(positive.shape[0], -1)
|
positive_flat = positive.reshape(positive.shape[0], -1)
|
||||||
@ -16,17 +21,20 @@ def optimized_scale(positive, negative):
|
|||||||
|
|
||||||
return st_star.reshape([positive.shape[0]] + [1] * (positive.ndim - 1))
|
return st_star.reshape([positive.shape[0]] + [1] * (positive.ndim - 1))
|
||||||
|
|
||||||
class CFGZeroStar:
|
class CFGZeroStar(io.ComfyNode):
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def define_schema(cls) -> io.Schema:
|
||||||
return {"required": {"model": ("MODEL",),
|
return io.Schema(
|
||||||
}}
|
node_id="CFGZeroStar",
|
||||||
RETURN_TYPES = ("MODEL",)
|
category="advanced/guidance",
|
||||||
RETURN_NAMES = ("patched_model",)
|
inputs=[
|
||||||
FUNCTION = "patch"
|
io.Model.Input("model"),
|
||||||
CATEGORY = "advanced/guidance"
|
],
|
||||||
|
outputs=[io.Model.Output(display_name="patched_model")],
|
||||||
|
)
|
||||||
|
|
||||||
def patch(self, model):
|
@classmethod
|
||||||
|
def execute(cls, model) -> io.NodeOutput:
|
||||||
m = model.clone()
|
m = model.clone()
|
||||||
def cfg_zero_star(args):
|
def cfg_zero_star(args):
|
||||||
guidance_scale = args['cond_scale']
|
guidance_scale = args['cond_scale']
|
||||||
@ -38,21 +46,24 @@ class CFGZeroStar:
|
|||||||
|
|
||||||
return out + uncond_p * (alpha - 1.0) + guidance_scale * uncond_p * (1.0 - alpha)
|
return out + uncond_p * (alpha - 1.0) + guidance_scale * uncond_p * (1.0 - alpha)
|
||||||
m.set_model_sampler_post_cfg_function(cfg_zero_star)
|
m.set_model_sampler_post_cfg_function(cfg_zero_star)
|
||||||
return (m, )
|
return io.NodeOutput(m)
|
||||||
|
|
||||||
class CFGNorm:
|
class CFGNorm(io.ComfyNode):
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def define_schema(cls) -> io.Schema:
|
||||||
return {"required": {"model": ("MODEL",),
|
return io.Schema(
|
||||||
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}),
|
node_id="CFGNorm",
|
||||||
}}
|
category="advanced/guidance",
|
||||||
RETURN_TYPES = ("MODEL",)
|
inputs=[
|
||||||
RETURN_NAMES = ("patched_model",)
|
io.Model.Input("model"),
|
||||||
FUNCTION = "patch"
|
io.Float.Input("strength", default=1.0, min=0.0, max=100.0, step=0.01),
|
||||||
CATEGORY = "advanced/guidance"
|
],
|
||||||
EXPERIMENTAL = True
|
outputs=[io.Model.Output(display_name="patched_model")],
|
||||||
|
is_experimental=True,
|
||||||
|
)
|
||||||
|
|
||||||
def patch(self, model, strength):
|
@classmethod
|
||||||
|
def execute(cls, model, strength) -> io.NodeOutput:
|
||||||
m = model.clone()
|
m = model.clone()
|
||||||
def cfg_norm(args):
|
def cfg_norm(args):
|
||||||
cond_p = args['cond_denoised']
|
cond_p = args['cond_denoised']
|
||||||
@ -64,9 +75,17 @@ class CFGNorm:
|
|||||||
return pred_text_ * scale * strength
|
return pred_text_ * scale * strength
|
||||||
|
|
||||||
m.set_model_sampler_post_cfg_function(cfg_norm)
|
m.set_model_sampler_post_cfg_function(cfg_norm)
|
||||||
return (m, )
|
return io.NodeOutput(m)
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
|
||||||
"CFGZeroStar": CFGZeroStar,
|
class CfgExtension(ComfyExtension):
|
||||||
"CFGNorm": CFGNorm,
|
@override
|
||||||
}
|
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
||||||
|
return [
|
||||||
|
CFGZeroStar,
|
||||||
|
CFGNorm,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def comfy_entrypoint() -> CfgExtension:
|
||||||
|
return CfgExtension()
|
||||||
|
|||||||
@ -1,15 +1,25 @@
|
|||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
|
from comfy_api.latest import ComfyExtension, io
|
||||||
|
|
||||||
|
|
||||||
class CLIPTextEncodeControlnet:
|
class CLIPTextEncodeControlnet(io.ComfyNode):
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def define_schema(cls) -> io.Schema:
|
||||||
return {"required": {"clip": ("CLIP", ), "conditioning": ("CONDITIONING", ), "text": ("STRING", {"multiline": True, "dynamicPrompts": True})}}
|
return io.Schema(
|
||||||
RETURN_TYPES = ("CONDITIONING",)
|
node_id="CLIPTextEncodeControlnet",
|
||||||
FUNCTION = "encode"
|
category="_for_testing/conditioning",
|
||||||
|
inputs=[
|
||||||
|
io.Clip.Input("clip"),
|
||||||
|
io.Conditioning.Input("conditioning"),
|
||||||
|
io.String.Input("text", multiline=True, dynamic_prompts=True),
|
||||||
|
],
|
||||||
|
outputs=[io.Conditioning.Output()],
|
||||||
|
is_experimental=True,
|
||||||
|
)
|
||||||
|
|
||||||
CATEGORY = "_for_testing/conditioning"
|
@classmethod
|
||||||
|
def execute(cls, clip, conditioning, text) -> io.NodeOutput:
|
||||||
def encode(self, clip, conditioning, text):
|
|
||||||
tokens = clip.tokenize(text)
|
tokens = clip.tokenize(text)
|
||||||
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
|
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
|
||||||
c = []
|
c = []
|
||||||
@ -18,32 +28,41 @@ class CLIPTextEncodeControlnet:
|
|||||||
n[1]['cross_attn_controlnet'] = cond
|
n[1]['cross_attn_controlnet'] = cond
|
||||||
n[1]['pooled_output_controlnet'] = pooled
|
n[1]['pooled_output_controlnet'] = pooled
|
||||||
c.append(n)
|
c.append(n)
|
||||||
return (c, )
|
return io.NodeOutput(c)
|
||||||
|
|
||||||
class T5TokenizerOptions:
|
class T5TokenizerOptions(io.ComfyNode):
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def define_schema(cls) -> io.Schema:
|
||||||
return {
|
return io.Schema(
|
||||||
"required": {
|
node_id="T5TokenizerOptions",
|
||||||
"clip": ("CLIP", ),
|
category="_for_testing/conditioning",
|
||||||
"min_padding": ("INT", {"default": 0, "min": 0, "max": 10000, "step": 1}),
|
inputs=[
|
||||||
"min_length": ("INT", {"default": 0, "min": 0, "max": 10000, "step": 1}),
|
io.Clip.Input("clip"),
|
||||||
}
|
io.Int.Input("min_padding", default=0, min=0, max=10000, step=1),
|
||||||
}
|
io.Int.Input("min_length", default=0, min=0, max=10000, step=1),
|
||||||
|
],
|
||||||
|
outputs=[io.Clip.Output()],
|
||||||
|
is_experimental=True,
|
||||||
|
)
|
||||||
|
|
||||||
CATEGORY = "_for_testing/conditioning"
|
@classmethod
|
||||||
RETURN_TYPES = ("CLIP",)
|
def execute(cls, clip, min_padding, min_length) -> io.NodeOutput:
|
||||||
FUNCTION = "set_options"
|
|
||||||
|
|
||||||
def set_options(self, clip, min_padding, min_length):
|
|
||||||
clip = clip.clone()
|
clip = clip.clone()
|
||||||
for t5_type in ["t5xxl", "pile_t5xl", "t5base", "mt5xl", "umt5xxl"]:
|
for t5_type in ["t5xxl", "pile_t5xl", "t5base", "mt5xl", "umt5xxl"]:
|
||||||
clip.set_tokenizer_option("{}_min_padding".format(t5_type), min_padding)
|
clip.set_tokenizer_option("{}_min_padding".format(t5_type), min_padding)
|
||||||
clip.set_tokenizer_option("{}_min_length".format(t5_type), min_length)
|
clip.set_tokenizer_option("{}_min_length".format(t5_type), min_length)
|
||||||
|
|
||||||
return (clip, )
|
return io.NodeOutput(clip)
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
|
||||||
"CLIPTextEncodeControlnet": CLIPTextEncodeControlnet,
|
class CondExtension(ComfyExtension):
|
||||||
"T5TokenizerOptions": T5TokenizerOptions,
|
@override
|
||||||
}
|
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
||||||
|
return [
|
||||||
|
CLIPTextEncodeControlnet,
|
||||||
|
T5TokenizerOptions,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def comfy_entrypoint() -> CondExtension:
|
||||||
|
return CondExtension()
|
||||||
|
|||||||
@ -1,25 +1,32 @@
|
|||||||
|
from typing_extensions import override
|
||||||
import nodes
|
import nodes
|
||||||
import torch
|
import torch
|
||||||
import comfy.model_management
|
import comfy.model_management
|
||||||
import comfy.utils
|
import comfy.utils
|
||||||
import comfy.latent_formats
|
import comfy.latent_formats
|
||||||
|
|
||||||
|
from comfy_api.latest import ComfyExtension, io
|
||||||
|
|
||||||
class EmptyCosmosLatentVideo:
|
|
||||||
|
class EmptyCosmosLatentVideo(io.ComfyNode):
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def define_schema(cls) -> io.Schema:
|
||||||
return {"required": { "width": ("INT", {"default": 1280, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
|
return io.Schema(
|
||||||
"height": ("INT", {"default": 704, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
|
node_id="EmptyCosmosLatentVideo",
|
||||||
"length": ("INT", {"default": 121, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
category="latent/video",
|
||||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 4096})}}
|
inputs=[
|
||||||
RETURN_TYPES = ("LATENT",)
|
io.Int.Input("width", default=1280, min=16, max=nodes.MAX_RESOLUTION, step=16),
|
||||||
FUNCTION = "generate"
|
io.Int.Input("height", default=704, min=16, max=nodes.MAX_RESOLUTION, step=16),
|
||||||
|
io.Int.Input("length", default=121, min=1, max=nodes.MAX_RESOLUTION, step=8),
|
||||||
|
io.Int.Input("batch_size", default=1, min=1, max=4096),
|
||||||
|
],
|
||||||
|
outputs=[io.Latent.Output()],
|
||||||
|
)
|
||||||
|
|
||||||
CATEGORY = "latent/video"
|
@classmethod
|
||||||
|
def execute(cls, width, height, length, batch_size=1) -> io.NodeOutput:
|
||||||
def generate(self, width, height, length, batch_size=1):
|
|
||||||
latent = torch.zeros([batch_size, 16, ((length - 1) // 8) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
|
latent = torch.zeros([batch_size, 16, ((length - 1) // 8) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
|
||||||
return ({"samples": latent}, )
|
return io.NodeOutput({"samples": latent})
|
||||||
|
|
||||||
|
|
||||||
def vae_encode_with_padding(vae, image, width, height, length, padding=0):
|
def vae_encode_with_padding(vae, image, width, height, length, padding=0):
|
||||||
@ -33,31 +40,31 @@ def vae_encode_with_padding(vae, image, width, height, length, padding=0):
|
|||||||
return latent_temp[:, :, :latent_len]
|
return latent_temp[:, :, :latent_len]
|
||||||
|
|
||||||
|
|
||||||
class CosmosImageToVideoLatent:
|
class CosmosImageToVideoLatent(io.ComfyNode):
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def define_schema(cls) -> io.Schema:
|
||||||
return {"required": {"vae": ("VAE", ),
|
return io.Schema(
|
||||||
"width": ("INT", {"default": 1280, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
|
node_id="CosmosImageToVideoLatent",
|
||||||
"height": ("INT", {"default": 704, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
|
category="conditioning/inpaint",
|
||||||
"length": ("INT", {"default": 121, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
inputs=[
|
||||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
|
io.Vae.Input("vae"),
|
||||||
},
|
io.Int.Input("width", default=1280, min=16, max=nodes.MAX_RESOLUTION, step=16),
|
||||||
"optional": {"start_image": ("IMAGE", ),
|
io.Int.Input("height", default=704, min=16, max=nodes.MAX_RESOLUTION, step=16),
|
||||||
"end_image": ("IMAGE", ),
|
io.Int.Input("length", default=121, min=1, max=nodes.MAX_RESOLUTION, step=8),
|
||||||
}}
|
io.Int.Input("batch_size", default=1, min=1, max=4096),
|
||||||
|
io.Image.Input("start_image", optional=True),
|
||||||
|
io.Image.Input("end_image", optional=True),
|
||||||
|
],
|
||||||
|
outputs=[io.Latent.Output()],
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
RETURN_TYPES = ("LATENT",)
|
def execute(cls, vae, width, height, length, batch_size, start_image=None, end_image=None) -> io.NodeOutput:
|
||||||
FUNCTION = "encode"
|
|
||||||
|
|
||||||
CATEGORY = "conditioning/inpaint"
|
|
||||||
|
|
||||||
def encode(self, vae, width, height, length, batch_size, start_image=None, end_image=None):
|
|
||||||
latent = torch.zeros([1, 16, ((length - 1) // 8) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
|
latent = torch.zeros([1, 16, ((length - 1) // 8) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
|
||||||
if start_image is None and end_image is None:
|
if start_image is None and end_image is None:
|
||||||
out_latent = {}
|
out_latent = {}
|
||||||
out_latent["samples"] = latent
|
out_latent["samples"] = latent
|
||||||
return (out_latent,)
|
return io.NodeOutput(out_latent)
|
||||||
|
|
||||||
mask = torch.ones([latent.shape[0], 1, ((length - 1) // 8) + 1, latent.shape[-2], latent.shape[-1]], device=comfy.model_management.intermediate_device())
|
mask = torch.ones([latent.shape[0], 1, ((length - 1) // 8) + 1, latent.shape[-2], latent.shape[-1]], device=comfy.model_management.intermediate_device())
|
||||||
|
|
||||||
@ -74,33 +81,33 @@ class CosmosImageToVideoLatent:
|
|||||||
out_latent = {}
|
out_latent = {}
|
||||||
out_latent["samples"] = latent.repeat((batch_size, ) + (1,) * (latent.ndim - 1))
|
out_latent["samples"] = latent.repeat((batch_size, ) + (1,) * (latent.ndim - 1))
|
||||||
out_latent["noise_mask"] = mask.repeat((batch_size, ) + (1,) * (mask.ndim - 1))
|
out_latent["noise_mask"] = mask.repeat((batch_size, ) + (1,) * (mask.ndim - 1))
|
||||||
return (out_latent,)
|
return io.NodeOutput(out_latent)
|
||||||
|
|
||||||
class CosmosPredict2ImageToVideoLatent:
|
class CosmosPredict2ImageToVideoLatent(io.ComfyNode):
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def define_schema(cls) -> io.Schema:
|
||||||
return {"required": {"vae": ("VAE", ),
|
return io.Schema(
|
||||||
"width": ("INT", {"default": 848, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
|
node_id="CosmosPredict2ImageToVideoLatent",
|
||||||
"height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
|
category="conditioning/inpaint",
|
||||||
"length": ("INT", {"default": 93, "min": 1, "max": nodes.MAX_RESOLUTION, "step": 4}),
|
inputs=[
|
||||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
|
io.Vae.Input("vae"),
|
||||||
},
|
io.Int.Input("width", default=848, min=16, max=nodes.MAX_RESOLUTION, step=16),
|
||||||
"optional": {"start_image": ("IMAGE", ),
|
io.Int.Input("height", default=480, min=16, max=nodes.MAX_RESOLUTION, step=16),
|
||||||
"end_image": ("IMAGE", ),
|
io.Int.Input("length", default=93, min=1, max=nodes.MAX_RESOLUTION, step=4),
|
||||||
}}
|
io.Int.Input("batch_size", default=1, min=1, max=4096),
|
||||||
|
io.Image.Input("start_image", optional=True),
|
||||||
|
io.Image.Input("end_image", optional=True),
|
||||||
|
],
|
||||||
|
outputs=[io.Latent.Output()],
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
RETURN_TYPES = ("LATENT",)
|
def execute(cls, vae, width, height, length, batch_size, start_image=None, end_image=None) -> io.NodeOutput:
|
||||||
FUNCTION = "encode"
|
|
||||||
|
|
||||||
CATEGORY = "conditioning/inpaint"
|
|
||||||
|
|
||||||
def encode(self, vae, width, height, length, batch_size, start_image=None, end_image=None):
|
|
||||||
latent = torch.zeros([1, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
|
latent = torch.zeros([1, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
|
||||||
if start_image is None and end_image is None:
|
if start_image is None and end_image is None:
|
||||||
out_latent = {}
|
out_latent = {}
|
||||||
out_latent["samples"] = latent
|
out_latent["samples"] = latent
|
||||||
return (out_latent,)
|
return io.NodeOutput(out_latent)
|
||||||
|
|
||||||
mask = torch.ones([latent.shape[0], 1, ((length - 1) // 4) + 1, latent.shape[-2], latent.shape[-1]], device=comfy.model_management.intermediate_device())
|
mask = torch.ones([latent.shape[0], 1, ((length - 1) // 4) + 1, latent.shape[-2], latent.shape[-1]], device=comfy.model_management.intermediate_device())
|
||||||
|
|
||||||
@ -119,10 +126,18 @@ class CosmosPredict2ImageToVideoLatent:
|
|||||||
latent = latent_format.process_out(latent) * mask + latent * (1.0 - mask)
|
latent = latent_format.process_out(latent) * mask + latent * (1.0 - mask)
|
||||||
out_latent["samples"] = latent.repeat((batch_size, ) + (1,) * (latent.ndim - 1))
|
out_latent["samples"] = latent.repeat((batch_size, ) + (1,) * (latent.ndim - 1))
|
||||||
out_latent["noise_mask"] = mask.repeat((batch_size, ) + (1,) * (mask.ndim - 1))
|
out_latent["noise_mask"] = mask.repeat((batch_size, ) + (1,) * (mask.ndim - 1))
|
||||||
return (out_latent,)
|
return io.NodeOutput(out_latent)
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
|
||||||
"EmptyCosmosLatentVideo": EmptyCosmosLatentVideo,
|
class CosmosExtension(ComfyExtension):
|
||||||
"CosmosImageToVideoLatent": CosmosImageToVideoLatent,
|
@override
|
||||||
"CosmosPredict2ImageToVideoLatent": CosmosPredict2ImageToVideoLatent,
|
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
||||||
}
|
return [
|
||||||
|
EmptyCosmosLatentVideo,
|
||||||
|
CosmosImageToVideoLatent,
|
||||||
|
CosmosPredict2ImageToVideoLatent,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def comfy_entrypoint() -> CosmosExtension:
|
||||||
|
return CosmosExtension()
|
||||||
|
|||||||
@ -128,6 +128,28 @@ class EmptyHunyuanImageLatent:
|
|||||||
latent = torch.zeros([batch_size, 64, height // 32, width // 32], device=comfy.model_management.intermediate_device())
|
latent = torch.zeros([batch_size, 64, height // 32, width // 32], device=comfy.model_management.intermediate_device())
|
||||||
return ({"samples":latent}, )
|
return ({"samples":latent}, )
|
||||||
|
|
||||||
|
class HunyuanRefinerLatent:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": {"positive": ("CONDITIONING", ),
|
||||||
|
"negative": ("CONDITIONING", ),
|
||||||
|
"latent": ("LATENT", ),
|
||||||
|
"noise_augmentation": ("FLOAT", {"default": 0.10, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||||
|
}}
|
||||||
|
|
||||||
|
RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
|
||||||
|
RETURN_NAMES = ("positive", "negative", "latent")
|
||||||
|
|
||||||
|
FUNCTION = "execute"
|
||||||
|
|
||||||
|
def execute(self, positive, negative, latent, noise_augmentation):
|
||||||
|
latent = latent["samples"]
|
||||||
|
positive = node_helpers.conditioning_set_values(positive, {"concat_latent_image": latent, "noise_augmentation": noise_augmentation})
|
||||||
|
negative = node_helpers.conditioning_set_values(negative, {"concat_latent_image": latent, "noise_augmentation": noise_augmentation})
|
||||||
|
out_latent = {}
|
||||||
|
out_latent["samples"] = torch.zeros([latent.shape[0], 32, latent.shape[-3], latent.shape[-2], latent.shape[-1]], device=comfy.model_management.intermediate_device())
|
||||||
|
return (positive, negative, out_latent)
|
||||||
|
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
"CLIPTextEncodeHunyuanDiT": CLIPTextEncodeHunyuanDiT,
|
"CLIPTextEncodeHunyuanDiT": CLIPTextEncodeHunyuanDiT,
|
||||||
@ -135,4 +157,5 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
"EmptyHunyuanLatentVideo": EmptyHunyuanLatentVideo,
|
"EmptyHunyuanLatentVideo": EmptyHunyuanLatentVideo,
|
||||||
"HunyuanImageToVideo": HunyuanImageToVideo,
|
"HunyuanImageToVideo": HunyuanImageToVideo,
|
||||||
"EmptyHunyuanImageLatent": EmptyHunyuanImageLatent,
|
"EmptyHunyuanImageLatent": EmptyHunyuanImageLatent,
|
||||||
|
"HunyuanRefinerLatent": HunyuanRefinerLatent,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
comfyui-frontend-package==1.25.11
|
comfyui-frontend-package==1.26.11
|
||||||
comfyui-workflow-templates==0.1.81
|
comfyui-workflow-templates==0.1.81
|
||||||
comfyui-embedded-docs==0.2.6
|
comfyui-embedded-docs==0.2.6
|
||||||
torch
|
torch
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user