609 lines
24 KiB
Python
609 lines
24 KiB
Python
"""Modified from https://github.com/kijai/ComfyUI-EasyAnimateWrapper/blob/main/nodes.py
|
|
"""
|
|
import gc
|
|
import os
|
|
|
|
import comfy.model_management as mm
|
|
import cv2
|
|
import folder_paths
|
|
import numpy as np
|
|
import torch
|
|
from comfy.utils import ProgressBar
|
|
from diffusers import (DDIMScheduler, DPMSolverMultistepScheduler, PNDMScheduler,
|
|
EulerAncestralDiscreteScheduler, EulerDiscreteScheduler)
|
|
from einops import rearrange
|
|
from omegaconf import OmegaConf
|
|
from PIL import Image
|
|
from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection
|
|
from safetensors.torch import load_file as load_safetensors
|
|
from huggingface_hub import snapshot_download
|
|
|
|
from ..ruyi.data.bucket_sampler import ASPECT_RATIO_512, get_closest_ratio
|
|
from ..ruyi.models.autoencoder_magvit import AutoencoderKLMagvit
|
|
from ..ruyi.models.transformer3d import HunyuanTransformer3DModel
|
|
from ..ruyi.pipeline.pipeline_ruyi_inpaint import RuyiInpaintPipeline
|
|
from ..ruyi.utils.lora_utils import merge_lora, unmerge_lora
|
|
from ..ruyi.utils.utils import get_image_to_video_latent
|
|
|
|
# The directory of scripts
|
|
script_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
|
class Ruyi_LoadModel:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"model": (
|
|
[
|
|
"Ruyi-Mini-7B",
|
|
],
|
|
{ "default": "Ruyi-Mini-7B", }
|
|
),
|
|
"auto_download": (
|
|
["yes", "no"],
|
|
{
|
|
"default": "yes"
|
|
}
|
|
),
|
|
"auto_update": (
|
|
["yes", "no"],
|
|
{
|
|
"default": "yes"
|
|
}
|
|
),
|
|
|
|
"fp8_quant_mode": (
|
|
[ "none", "lite", "strong", "extreme"],
|
|
{ "default": "none"}
|
|
),
|
|
"fp8_data_type": (
|
|
[ "auto", "fp8_e4m3fn", "fp8_e5m2"],
|
|
{ "default": "auto", "tooltip": 'do not use "fp8_e5m2" with "extreme" mode'}
|
|
),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("RUYI_MODEL",)
|
|
RETURN_NAMES = ("ruyi_model",)
|
|
FUNCTION = "load_model"
|
|
CATEGORY = "Ruyi"
|
|
|
|
RUYI_MODEL_TYPE_DICT = {
|
|
"Ruyi-Mini-7B": "Inpaint"
|
|
}
|
|
|
|
def get_model_type(self, model_name):
|
|
return self.RUYI_MODEL_TYPE_DICT.get(model_name, "Inpaint")
|
|
|
|
def try_setup_pipeline(self, model_path, weight_dtype, config):
|
|
try:
|
|
# Init processbar
|
|
pbar = ProgressBar(6)
|
|
|
|
# Get Vae
|
|
vae = AutoencoderKLMagvit.from_pretrained(
|
|
model_path,
|
|
subfolder="vae"
|
|
).to(weight_dtype)
|
|
# Update pbar
|
|
pbar.update(1)
|
|
|
|
# Get Transformer
|
|
transformer_additional_kwargs = OmegaConf.to_container(config['transformer_additional_kwargs'])
|
|
transformer = HunyuanTransformer3DModel.from_pretrained_2d(
|
|
model_path,
|
|
subfolder="transformer",
|
|
transformer_additional_kwargs=transformer_additional_kwargs
|
|
).to(weight_dtype)
|
|
# Update pbar
|
|
pbar.update(1)
|
|
|
|
if self.fp8_quant_mode != 'none':
|
|
count_f8 = 0
|
|
fp8_type = torch.float8_e5m2 if self.fp8_data_type == 'fp8_e5m2' else torch.float8_e4m3fn
|
|
if self.fp8_quant_mode != 'extreme':
|
|
|
|
shape_size = 2816 ** 2
|
|
if self.fp8_quant_mode == 'strong': shape_size -= 1
|
|
|
|
for module in transformer.modules():
|
|
if module.__class__.__name__ in ["Linear"]:
|
|
x,y = module.weight.shape
|
|
if x * y > shape_size:
|
|
module.to(fp8_type)
|
|
count_f8 += 1
|
|
else:
|
|
for module in transformer.modules():
|
|
if len(list(module.modules())) == 1 and list(module.named_parameters()):
|
|
if module.__class__.__name__ not in ["Embedding",'LayerNorm','Conv2d','NonDynamicallyQuantizableLinear']:
|
|
module.to(fp8_type)
|
|
count_f8 += 1
|
|
|
|
print (f'FP8: {count_f8} layers converted to {fp8_type}')
|
|
# Update pbar
|
|
pbar.update(1)
|
|
|
|
# Load Clip
|
|
clip_image_encoder = CLIPVisionModelWithProjection.from_pretrained(
|
|
model_path, subfolder="image_encoder"
|
|
).to(weight_dtype)
|
|
clip_image_processor = CLIPImageProcessor.from_pretrained(
|
|
model_path, subfolder="image_encoder"
|
|
)
|
|
# Update pbar
|
|
pbar.update(1)
|
|
|
|
# Load sampler and create pipeline
|
|
Choosen_Scheduler = DDIMScheduler
|
|
scheduler = Choosen_Scheduler.from_pretrained(
|
|
model_path,
|
|
subfolder="scheduler"
|
|
)
|
|
pipeline = RuyiInpaintPipeline.from_pretrained(
|
|
model_path,
|
|
vae=vae,
|
|
transformer=transformer,
|
|
scheduler=scheduler,
|
|
torch_dtype=weight_dtype,
|
|
clip_image_encoder=clip_image_encoder,
|
|
clip_image_processor=clip_image_processor,
|
|
)
|
|
|
|
# Load embeddings
|
|
embeddings = load_safetensors(os.path.join(model_path, "embeddings.safetensors"))
|
|
pipeline.embeddings = embeddings
|
|
# Update pbar
|
|
pbar.update(1)
|
|
|
|
return pipeline
|
|
except Exception as e:
|
|
print("[Ruyi] Setup pipeline failed:", e)
|
|
return None
|
|
|
|
def load_model(self, model, auto_download, auto_update, fp8_quant_mode='none', fp8_data_type='auto'):
|
|
self.fp8_quant_mode = fp8_quant_mode
|
|
self.fp8_data_type = fp8_data_type
|
|
|
|
# Init weight_dtype and device
|
|
device = mm.get_torch_device()
|
|
offload_device = mm.unet_offload_device()
|
|
|
|
# Init model name and type
|
|
model_name = model
|
|
model_type = self.get_model_type(model_name)
|
|
weight_dtype = torch.bfloat16
|
|
|
|
# Load config
|
|
config_path = os.path.join(script_directory, "config", "default.yaml")
|
|
config = OmegaConf.load(config_path)
|
|
|
|
# Check for update
|
|
repo_id = f"IamCreateAI/{model_name}"
|
|
model_path = os.path.join(folder_paths.models_dir, "Ruyi", model_name)
|
|
if auto_download == "yes" and auto_update == "yes":
|
|
print(f"Checking for {model_name} updates ...")
|
|
|
|
# Download the model
|
|
snapshot_download(repo_id=repo_id, local_dir=model_path)
|
|
|
|
# Init model
|
|
pipeline = self.try_setup_pipeline(model_path, weight_dtype, config)
|
|
if pipeline is None and auto_download == "yes":
|
|
mm.soft_empty_cache()
|
|
gc.collect()
|
|
|
|
# Download the model
|
|
snapshot_download(repo_id=repo_id, local_dir=model_path)
|
|
|
|
pipeline = self.try_setup_pipeline(model_path, weight_dtype, config)
|
|
|
|
if pipeline is None:
|
|
message = (f"[ Load Model {model_name} Failed ]\n"
|
|
f"Please download Ruyi model from huggingface repo '{repo_id}',\n"
|
|
f"And put it into '{model_path}'.")
|
|
if auto_download == "no":
|
|
message += "\n\nOr just set auto_download to 'yes'."
|
|
raise FileNotFoundError(message)
|
|
|
|
pipeline.enable_model_cpu_offload()
|
|
ruyi_model = {
|
|
'pipeline': pipeline,
|
|
'dtype': weight_dtype,
|
|
'model_path': model_path,
|
|
'model_type': model_type,
|
|
'loras': [],
|
|
'strength_model': [],
|
|
'plugins': {}
|
|
}
|
|
return (ruyi_model,)
|
|
|
|
|
|
class Ruyi_LoadLora:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"ruyi_model": ("RUYI_MODEL",),
|
|
"lora_name": (folder_paths.get_filename_list("loras"), {"default": None,}),
|
|
"strength_model": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, "step": 0.01}),
|
|
}
|
|
}
|
|
RETURN_TYPES = ("RUYI_MODEL",)
|
|
RETURN_NAMES = ("ruyi_model",)
|
|
FUNCTION = "load_lora"
|
|
CATEGORY = "Ruyi"
|
|
|
|
def load_lora(self, ruyi_model, lora_name, strength_model):
|
|
if lora_name is not None:
|
|
return (
|
|
{
|
|
'pipeline': ruyi_model["pipeline"],
|
|
'dtype': ruyi_model["dtype"],
|
|
'model_path': ruyi_model["model_path"],
|
|
'model_type': ruyi_model["model_type"],
|
|
'loras': ruyi_model.get("loras", []) + [folder_paths.get_full_path("loras", lora_name)],
|
|
'strength_model': ruyi_model.get("strength_model", []) + [strength_model],
|
|
'plugins': ruyi_model["plugins"],
|
|
},
|
|
)
|
|
else:
|
|
return (ruyi_model,)
|
|
|
|
|
|
class Ruyi_TeaCache:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"ruyi_model": ("RUYI_MODEL",),
|
|
"enable": ("BOOLEAN", {"default": True, "tooltip": "Enable TeaCache."}),
|
|
"threshold": (
|
|
"FLOAT",
|
|
{
|
|
"default": 0.10, "min": 0.0, "max": 0.50, "step": 0.01,
|
|
"tooltip": "TeaCache threshold. 0.10 caches 6 ~ 8 steps, 0.15 caches 10 ~ 12 steps normally."
|
|
}
|
|
),
|
|
"skip_start_steps": ("INT", {"default": 3, "min": 1, "step": 1, "tooltip": "First n steps do not use TeaCache."}),
|
|
"skip_end_steps": ("INT", {"default": 1, "min": 1, "step": 1, "tooltip": "Last n steps do not use TeaCache."}),
|
|
"offload_cpu": ("BOOLEAN", {"default": True, "Tooltip": "Offload TeaCache tensors to cpu to save GPU memory."}),
|
|
}
|
|
}
|
|
RETURN_TYPES = ("RUYI_MODEL",)
|
|
RETURN_NAMES = ("ruyi_model",)
|
|
FUNCTION = "tea_cache"
|
|
CATEGORY = "Ruyi"
|
|
|
|
def tea_cache(self, ruyi_model, enable, threshold, skip_start_steps, skip_end_steps, offload_cpu):
|
|
tea_cache_parameters = {
|
|
"enable": enable,
|
|
"threshold": threshold,
|
|
"skip_start_steps": skip_start_steps,
|
|
"skip_end_steps": skip_end_steps,
|
|
"offload_cpu": offload_cpu
|
|
}
|
|
|
|
return (
|
|
{
|
|
'pipeline': ruyi_model["pipeline"],
|
|
'dtype': ruyi_model["dtype"],
|
|
'model_path': ruyi_model["model_path"],
|
|
'model_type': ruyi_model["model_type"],
|
|
'loras': ruyi_model["loras"],
|
|
'strength_model': ruyi_model["strength_model"],
|
|
'plugins': {"tea_cache": tea_cache_parameters} | ruyi_model["plugins"],
|
|
},
|
|
)
|
|
|
|
|
|
class Ruyi_EnhanceAVideo:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"ruyi_model": ("RUYI_MODEL",),
|
|
"enable": ("BOOLEAN", {"default": True, "tooltip": "Enable Enhance-A-Video."}),
|
|
"weight": (
|
|
"FLOAT",
|
|
{
|
|
"default": 1.0, "min": 0, "max": 100,
|
|
"tooltip": "The weight of Enhance-A-Video. Should be smaller than 5 or 10 to get better results."
|
|
}
|
|
),
|
|
"skip_start_steps": ("INT", {"default": 0, "min": 0, "step": 1, "tooltip": "First n steps do not use Enhance-A-Video."}),
|
|
"skip_end_steps": ("INT", {"default": 0, "min": 0, "step": 1, "tooltip": "First n steps do not use Enhance-A-Video."})
|
|
}
|
|
}
|
|
RETURN_TYPES = ("RUYI_MODEL",)
|
|
RETURN_NAMES = ("ruyi_model",)
|
|
FUNCTION = "enhance_a_video"
|
|
CATEGORY = "Ruyi"
|
|
|
|
def enhance_a_video(self, ruyi_model, enable, weight, skip_start_steps, skip_end_steps):
|
|
enhance_a_video_parameters = {
|
|
"enable": enable,
|
|
"weight": weight,
|
|
"skip_start_steps": skip_start_steps,
|
|
"skip_end_steps": skip_end_steps,
|
|
}
|
|
|
|
return (
|
|
{
|
|
'pipeline': ruyi_model["pipeline"],
|
|
'dtype': ruyi_model["dtype"],
|
|
'model_path': ruyi_model["model_path"],
|
|
'model_type': ruyi_model["model_type"],
|
|
'loras': ruyi_model["loras"],
|
|
'strength_model': ruyi_model["strength_model"],
|
|
'plugins': {"enhance_a_video": enhance_a_video_parameters} | ruyi_model["plugins"],
|
|
},
|
|
)
|
|
|
|
|
|
class Ruyi_I2VSampler:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"ruyi_model": (
|
|
"RUYI_MODEL",
|
|
),
|
|
"video_length": (
|
|
"INT", {"default": 72, "min": 8, "max": 120, "step": 8}
|
|
),
|
|
"base_resolution": (
|
|
"INT", {"default": 512, "min": 384, "max": 1024, "step": 16}
|
|
),
|
|
"seed": (
|
|
"INT", {"default": 42, "min": 0, "max": 0xffffffffffffffff}
|
|
),
|
|
"steps": (
|
|
"INT", {"default": 25, "min": 1, "max": 200, "step": 1}
|
|
),
|
|
"cfg": (
|
|
"FLOAT", {"default": 7.0, "min": 1.0, "max": 20.0, "step": 0.01}
|
|
),
|
|
"scheduler": (
|
|
[
|
|
"Euler",
|
|
"Euler A",
|
|
"DPM++",
|
|
"PNDM",
|
|
"DDIM",
|
|
],
|
|
{
|
|
"default": 'DDIM'
|
|
}
|
|
),
|
|
"motion": (
|
|
[ "1", "2", "3", "4", "auto" ],
|
|
{ "default": "2" }
|
|
),
|
|
"camera_direction": (
|
|
[ "static", "left", "right", "up", "down", "auto" ],
|
|
{ "default": "static" }
|
|
),
|
|
"GPU_memory_mode": (
|
|
["normal_mode", "low_memory_mode"],
|
|
{
|
|
"default": "normal_mode",
|
|
}
|
|
),
|
|
"GPU_offload_steps": (
|
|
[ "0", "1", "5", "7", "10" ],
|
|
{ "default": "0" }
|
|
),
|
|
"start_img": (
|
|
"IMAGE",
|
|
),
|
|
},
|
|
"optional":{
|
|
"end_img": ("IMAGE",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
RETURN_NAMES =("images",)
|
|
FUNCTION = "process"
|
|
CATEGORY = "Ruyi"
|
|
|
|
def tensor2pil(self, image):
|
|
return Image.fromarray(np.clip(255. * image.cpu().numpy(), 0, 255).astype(np.uint8))
|
|
|
|
def numpy2pil(self, image):
|
|
return Image.fromarray(np.clip(255. * image, 0, 255).astype(np.uint8))
|
|
|
|
def to_pil(self, image):
|
|
if isinstance(image, Image.Image):
|
|
return image
|
|
if isinstance(image, torch.Tensor):
|
|
return self.tensor2pil(image)
|
|
if isinstance(image, np.ndarray):
|
|
return self.numpy2pil(image)
|
|
raise ValueError(f"Cannot convert {type(image)} to PIL.Image")
|
|
|
|
def get_control_embeddings(self, pipeline, aspect_ratio, motion, camera_direction):
|
|
# Default keys
|
|
p_default_key = "p.default"
|
|
n_default_key = "n.default"
|
|
|
|
# Load embeddings
|
|
if motion == "auto":
|
|
motion = "0"
|
|
p_key = f"p.{aspect_ratio.replace(':', 'x')}movie{motion}{camera_direction}"
|
|
embeddings = pipeline.embeddings
|
|
|
|
# Get embeddings
|
|
positive_embeds = embeddings.get(f"{p_key}.emb1", embeddings[f"{p_default_key}.emb1"])
|
|
positive_attention_mask = embeddings.get(f"{p_key}.mask1", embeddings[f"{p_default_key}.mask1"])
|
|
positive_embeds_2 = embeddings.get(f"{p_key}.emb2", embeddings[f"{p_default_key}.emb2"])
|
|
positive_attention_mask_2 = embeddings.get(f"{p_key}.mask2", embeddings[f"{p_default_key}.mask2"])
|
|
|
|
negative_embeds = embeddings[f"{n_default_key}.emb1"]
|
|
negative_attention_mask = embeddings[f"{n_default_key}.mask1"]
|
|
negative_embeds_2 = embeddings[f"{n_default_key}.emb2"]
|
|
negative_attention_mask_2 = embeddings[f"{n_default_key}.mask2"]
|
|
|
|
return {
|
|
"positive_embeds": positive_embeds,
|
|
"positive_attention_mask": positive_attention_mask,
|
|
"positive_embeds_2": positive_embeds_2,
|
|
"positive_attention_mask_2": positive_attention_mask_2,
|
|
|
|
"negative_embeds": negative_embeds,
|
|
"negative_attention_mask": negative_attention_mask,
|
|
"negative_embeds_2": negative_embeds_2,
|
|
"negative_attention_mask_2": negative_attention_mask_2,
|
|
}
|
|
|
|
def get_scheduler(self, model_path, scheduler_name):
|
|
if scheduler_name == "DPM++":
|
|
noise_scheduler = DPMSolverMultistepScheduler.from_pretrained(model_path, subfolder='scheduler')
|
|
elif scheduler_name == "Euler":
|
|
noise_scheduler = EulerDiscreteScheduler.from_pretrained(model_path, subfolder='scheduler')
|
|
elif scheduler_name == "Euler A":
|
|
noise_scheduler = EulerAncestralDiscreteScheduler.from_pretrained(model_path, subfolder='scheduler')
|
|
elif scheduler_name == "PNDM":
|
|
noise_scheduler = PNDMScheduler.from_pretrained(model_path, subfolder='scheduler')
|
|
elif scheduler_name == "DDIM":
|
|
noise_scheduler = DDIMScheduler.from_pretrained(model_path, subfolder='scheduler')
|
|
|
|
return noise_scheduler
|
|
|
|
def initialize_plugins(self, pipeline, parameters, num_steps):
|
|
# TeaCache
|
|
if "tea_cache" in parameters:
|
|
tea_cache_parameters = parameters["tea_cache"]
|
|
pipeline.transformer.tea_cache.initialize(
|
|
tea_cache_parameters["enable"],
|
|
tea_cache_parameters["threshold"],
|
|
tea_cache_parameters["skip_start_steps"],
|
|
tea_cache_parameters["skip_end_steps"],
|
|
num_steps,
|
|
tea_cache_parameters["offload_cpu"],
|
|
)
|
|
else:
|
|
pipeline.transformer.tea_cache.disable()
|
|
|
|
# Enhance-A-Video
|
|
if "enhance_a_video" in parameters:
|
|
enhance_a_video_parameters = parameters["enhance_a_video"]
|
|
pipeline.transformer.enhance_a_video.initialize(
|
|
enhance_a_video_parameters["enable"],
|
|
enhance_a_video_parameters["weight"],
|
|
enhance_a_video_parameters["skip_start_steps"],
|
|
enhance_a_video_parameters["skip_end_steps"],
|
|
num_steps,
|
|
)
|
|
else:
|
|
pipeline.transformer.enhance_a_video.disable()
|
|
|
|
def process(
|
|
self, ruyi_model, video_length, base_resolution, seed, steps, cfg, scheduler,
|
|
motion, camera_direction, GPU_memory_mode, GPU_offload_steps,
|
|
start_img, end_img=None
|
|
):
|
|
device = mm.get_torch_device()
|
|
offload_device = mm.unet_offload_device()
|
|
|
|
mm.soft_empty_cache()
|
|
gc.collect()
|
|
|
|
start_img = [self.to_pil(_start_img) for _start_img in start_img] if start_img is not None else None
|
|
end_img = [self.to_pil(_end_img) for _end_img in end_img] if end_img is not None else None
|
|
|
|
# Count most suitable height and width
|
|
aspect_ratio_sample_size = {key : [x / 512 * base_resolution for x in ASPECT_RATIO_512[key]] for key in ASPECT_RATIO_512.keys()}
|
|
original_width, original_height = start_img[0].size if type(start_img) is list else Image.open(start_img).size
|
|
closest_size, closest_ratio = get_closest_ratio(original_height, original_width, ratios=aspect_ratio_sample_size)
|
|
height, width = [int(x / 16) * 16 for x in closest_size]
|
|
aspect_ratio = "16:9" # Do not change, currently "16:9" works better
|
|
|
|
# Get Pipeline
|
|
pipeline = ruyi_model['pipeline']
|
|
model_path = ruyi_model['model_path']
|
|
|
|
# Set GPU memory mode
|
|
if GPU_memory_mode == "low_memory_mode" and pipeline.model_cpu_offload_flag:
|
|
# Switch to low_memory_mode
|
|
pipeline.enable_sequential_cpu_offload()
|
|
elif GPU_memory_mode == "normal_mode" and not pipeline.model_cpu_offload_flag:
|
|
# Switch to normal_mode
|
|
pipeline.enable_model_cpu_offload()
|
|
|
|
# Set GPU offload steps
|
|
pipeline.transformer.hidden_cache_size = int(GPU_offload_steps)
|
|
|
|
# Load Sampler
|
|
pipeline.scheduler = self.get_scheduler(model_path, scheduler)
|
|
|
|
# Set random seed
|
|
generator= torch.Generator(device).manual_seed(seed)
|
|
|
|
# Load control embeddings
|
|
embeddings = self.get_control_embeddings(pipeline, aspect_ratio, motion, camera_direction)
|
|
|
|
# Initialize plugins
|
|
plugin_parameters = ruyi_model["plugins"]
|
|
self.initialize_plugins(pipeline, plugin_parameters, steps)
|
|
|
|
# Inference
|
|
with torch.no_grad(), torch.autocast(device_type=device.type, dtype=pipeline.transformer.dtype):
|
|
video_length = int(video_length // pipeline.vae.mini_batch_encoder * pipeline.vae.mini_batch_encoder) if video_length != 1 else 1
|
|
input_video, input_video_mask, clip_image = get_image_to_video_latent(start_img, end_img, video_length=video_length, sample_size=(height, width))
|
|
|
|
for _lora_path, _lora_weight in zip(ruyi_model.get("loras", []), ruyi_model.get("strength_model", [])):
|
|
pipeline = merge_lora(pipeline, _lora_path, _lora_weight)
|
|
|
|
sample = pipeline(
|
|
prompt_embeds = embeddings["positive_embeds"],
|
|
prompt_attention_mask = embeddings["positive_attention_mask"],
|
|
prompt_embeds_2 = embeddings["positive_embeds_2"],
|
|
prompt_attention_mask_2 = embeddings["positive_attention_mask_2"],
|
|
|
|
negative_prompt_embeds = embeddings["negative_embeds"],
|
|
negative_prompt_attention_mask = embeddings["negative_attention_mask"],
|
|
negative_prompt_embeds_2 = embeddings["negative_embeds_2"],
|
|
negative_prompt_attention_mask_2 = embeddings["negative_attention_mask_2"],
|
|
|
|
video_length = video_length,
|
|
height = height,
|
|
width = width,
|
|
generator = generator,
|
|
guidance_scale = cfg,
|
|
num_inference_steps = steps,
|
|
|
|
video = input_video,
|
|
mask_video = input_video_mask,
|
|
clip_image = clip_image,
|
|
comfyui_progressbar = True,
|
|
).videos
|
|
videos = rearrange(sample, "b c t h w -> (b t) h w c")
|
|
|
|
for _lora_path, _lora_weight in zip(ruyi_model.get("loras", []), ruyi_model.get("strength_model", [])):
|
|
pipeline = unmerge_lora(pipeline, _lora_path, _lora_weight)
|
|
|
|
# Log information
|
|
if pipeline.transformer.tea_cache.enabled:
|
|
print("TeaCache cached steps:", pipeline.transformer.tea_cache.skip_count)
|
|
|
|
return (videos,)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"Ruyi_LoadModel": Ruyi_LoadModel,
|
|
"Ruyi_LoadLora": Ruyi_LoadLora,
|
|
"Ruyi_TeaCache": Ruyi_TeaCache,
|
|
"Ruyi_EnhanceAVideo": Ruyi_EnhanceAVideo,
|
|
"Ruyi_I2VSampler": Ruyi_I2VSampler,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"Ruyi_LoadModel": "Load Model",
|
|
"Ruyi_LoadLora": "Load LoRA",
|
|
"Ruyi_TeaCache": "TeaCache for Ruyi",
|
|
"Ruyi_EnhanceAVideo": "Enhance-A-Video for Ruyi",
|
|
"Ruyi_I2VSampler": "Sampler for Image to Video",
|
|
}
|