mirror of
https://github.com/storytold/storyteller-ml.git
synced 2026-10-15 00:03:06 +00:00
more extensions
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# ComfyUI-Advanced-ControlNet
|
||||
Nodes for scheduling ControlNet strength across timesteps and batched latents, as well as applying custom weights and attention masks. The ControlNet nodes here fully support sliding context sampling, like the one used in the [ComfyUI-AnimateDiff-Evolved](https://github.com/Kosinkadink/ComfyUI-AnimateDiff-Evolved) nodes. Currently supports ControlNets, T2IAdapters, ControlLoRAs, ControlLLLite, and SparseCtrls.
|
||||
Nodes for scheduling ControlNet strength across timesteps and batched latents, as well as applying custom weights and attention masks. The ControlNet nodes here fully support sliding context sampling, like the one used in the [ComfyUI-AnimateDiff-Evolved](https://github.com/Kosinkadink/ComfyUI-AnimateDiff-Evolved) nodes. Currently supports ControlNets, T2IAdapters, ControlLoRAs, ControlLLLite, SparseCtrls, and SVD-ControlNets.
|
||||
|
||||
Custom weights allow replication of the "My prompt is more important" feature of Auto1111's sd-webui ControlNet extension.
|
||||
|
||||
@@ -12,6 +12,8 @@ ControlNet preprocessors are available through [comfyui_controlnet_aux](https://
|
||||
- ControlNet, T2IAdapter, and ControlLoRA support for sliding context windows.
|
||||
- ControlLLLite support (requires model_optional to be passed into and out of Apply Advanced ControlNet node)
|
||||
- SparseCtrl support
|
||||
- SVD-ControlNet support
|
||||
- Stable Video Diffusion ControlNets trained by **CiaraRowles**: [Depth](https://huggingface.co/CiaraRowles/temporal-controlnet-depth-svd-v1/tree/main/controlnet), [Lineart](https://huggingface.co/CiaraRowles/temporal-controlnet-lineart-svd-v1/tree/main/controlnet)
|
||||
|
||||
## Table of Contents:
|
||||
- [Scheduling Explanation](#scheduling-explanation)
|
||||
|
||||
+311
-23
@@ -12,6 +12,7 @@ from model_patcher import ModelPatcher
|
||||
|
||||
from .control_sparsectrl import SparseControlNet, SparseCtrlMotionWrapper, SparseMethod, SparseSettings, SparseSpreadMethod, PreprocSparseRGBWrapper
|
||||
from .control_lllite import LLLiteModule, LLLitePatch
|
||||
from .control_svd import svd_unet_config_from_diffusers_unet, SVDControlNet, svd_unet_to_diffusers
|
||||
from .utils import (AdvancedControlBase, TimestepKeyframeGroup, LatentKeyframeGroup, ControlWeightType, ControlWeights, WeightTypeException,
|
||||
manual_cast_clean_groupnorm, disable_weight_init_clean_groupnorm, prepare_mask_batch, get_properly_arranged_t2i_weights, load_torch_file_with_dict_factory)
|
||||
from .logger import logger
|
||||
@@ -64,7 +65,7 @@ class ControlNetAdvanced(ControlNet, AdvancedControlBase):
|
||||
# prepare mask_cond_hint
|
||||
self.prepare_mask_cond_hint(x_noisy=x_noisy, t=t, cond=cond, batched_number=batched_number, dtype=dtype)
|
||||
|
||||
context = cond['c_crossattn']
|
||||
context = cond.get('crossattn_controlnet', cond['c_crossattn'])
|
||||
# uses 'y' in new ComfyUI update
|
||||
y = cond.get('y', None)
|
||||
if y is None: # TODO: remove this in the future since no longer used by newest ComfyUI
|
||||
@@ -168,6 +169,71 @@ class ControlLoraAdvanced(ControlLora, AdvancedControlBase):
|
||||
global_average_pooling=v.global_average_pooling, device=v.device)
|
||||
|
||||
|
||||
class SVDControlNetAdvanced(ControlNetAdvanced):
|
||||
def __init__(self, control_model: SVDControlNet, timestep_keyframes: TimestepKeyframeGroup, global_average_pooling=False, device=None, load_device=None, manual_cast_dtype=None):
|
||||
super().__init__(control_model=control_model, timestep_keyframes=timestep_keyframes, global_average_pooling=global_average_pooling, device=device, load_device=load_device, manual_cast_dtype=manual_cast_dtype)
|
||||
|
||||
def set_cond_hint(self, *args, **kwargs):
|
||||
to_return = super().set_cond_hint(*args, **kwargs)
|
||||
# cond hint for SVD-ControlNet needs to be scaled between (-1, 1) instead of (0, 1)
|
||||
self.cond_hint_original = self.cond_hint_original * 2.0 - 1.0
|
||||
return to_return
|
||||
|
||||
def get_control_advanced(self, x_noisy, t, cond, batched_number):
|
||||
control_prev = None
|
||||
if self.previous_controlnet is not None:
|
||||
control_prev = self.previous_controlnet.get_control(x_noisy, t, cond, batched_number)
|
||||
|
||||
if self.timestep_range is not None:
|
||||
if t[0] > self.timestep_range[0] or t[0] < self.timestep_range[1]:
|
||||
if control_prev is not None:
|
||||
return control_prev
|
||||
else:
|
||||
return None
|
||||
|
||||
dtype = self.control_model.dtype
|
||||
if self.manual_cast_dtype is not None:
|
||||
dtype = self.manual_cast_dtype
|
||||
|
||||
output_dtype = x_noisy.dtype
|
||||
# make cond_hint appropriate dimensions
|
||||
# TODO: change this to not require cond_hint upscaling every step when self.sub_idxs are present
|
||||
if self.sub_idxs is not None or self.cond_hint is None or x_noisy.shape[2] * 8 != self.cond_hint.shape[2] or x_noisy.shape[3] * 8 != self.cond_hint.shape[3]:
|
||||
if self.cond_hint is not None:
|
||||
del self.cond_hint
|
||||
self.cond_hint = None
|
||||
# if self.cond_hint_original length greater or equal to real latent count, subdivide it before scaling
|
||||
if self.sub_idxs is not None and self.cond_hint_original.size(0) >= self.full_latent_length:
|
||||
self.cond_hint = comfy.utils.common_upscale(self.cond_hint_original[self.sub_idxs], x_noisy.shape[3] * 8, x_noisy.shape[2] * 8, 'nearest-exact', "center").to(dtype).to(self.device)
|
||||
else:
|
||||
self.cond_hint = comfy.utils.common_upscale(self.cond_hint_original, x_noisy.shape[3] * 8, x_noisy.shape[2] * 8, 'nearest-exact', "center").to(dtype).to(self.device)
|
||||
if x_noisy.shape[0] != self.cond_hint.shape[0]:
|
||||
self.cond_hint = broadcast_image_to(self.cond_hint, x_noisy.shape[0], batched_number)
|
||||
|
||||
# prepare mask_cond_hint
|
||||
self.prepare_mask_cond_hint(x_noisy=x_noisy, t=t, cond=cond, batched_number=batched_number, dtype=dtype)
|
||||
|
||||
context = cond.get('crossattn_controlnet', cond['c_crossattn'])
|
||||
# uses 'y' in new ComfyUI update
|
||||
y = cond.get('y', None)
|
||||
if y is not None:
|
||||
y = y.to(dtype)
|
||||
timestep = self.model_sampling_current.timestep(t)
|
||||
x_noisy = self.model_sampling_current.calculate_input(t, x_noisy)
|
||||
# concat c_concat if exists (should exist for SVD), doubling channels to 8
|
||||
if cond.get('c_concat', None) is not None:
|
||||
x_noisy = torch.cat([x_noisy] + [cond['c_concat']], dim=1)
|
||||
|
||||
control = self.control_model(x=x_noisy.to(dtype), hint=self.cond_hint, timesteps=timestep.float(), context=context.to(dtype), y=y, cond=cond)
|
||||
return self.control_merge(None, control, control_prev, output_dtype)
|
||||
|
||||
def copy(self):
|
||||
c = SVDControlNetAdvanced(self.control_model, self.timestep_keyframes, global_average_pooling=self.global_average_pooling, load_device=self.load_device, manual_cast_dtype=self.manual_cast_dtype)
|
||||
self.copy_to(c)
|
||||
self.copy_to_advanced(c)
|
||||
return c
|
||||
|
||||
|
||||
class SparseCtrlAdvanced(ControlNetAdvanced):
|
||||
def __init__(self, control_model, timestep_keyframes: TimestepKeyframeGroup, sparse_settings: SparseSettings=None, global_average_pooling=False, device=None, load_device=None, manual_cast_dtype=None):
|
||||
super().__init__(control_model=control_model, timestep_keyframes=timestep_keyframes, global_average_pooling=global_average_pooling, device=device, load_device=load_device, manual_cast_dtype=manual_cast_dtype)
|
||||
@@ -282,27 +348,19 @@ class SparseCtrlAdvanced(ControlNetAdvanced):
|
||||
return c
|
||||
|
||||
|
||||
class ControlLLLiteAdvanced(ControlBase, AdvancedControlBase):
|
||||
# This ControlNet is more of an attention patch than a traditional controlnet
|
||||
def __init__(self, patch: LLLitePatch, timestep_keyframes: TimestepKeyframeGroup, device=None):
|
||||
class ReferenceAdvanced(ControlBase, AdvancedControlBase):
|
||||
def __init__(self, timestep_keyframes: TimestepKeyframeGroup, device=None):
|
||||
super().__init__(device)
|
||||
AdvancedControlBase.__init__(self, super(), timestep_keyframes=timestep_keyframes, weights_default=ControlWeights.controllllite(), require_model=True)
|
||||
self.patch = patch.clone_with_control(self)
|
||||
|
||||
# TODO: save attn patches here
|
||||
|
||||
def patch_model(self, model: ModelPatcher):
|
||||
model.set_model_attn1_patch(self.patch)
|
||||
model.set_model_attn2_patch(self.patch)
|
||||
|
||||
def set_cond_hint(self, *args, **kwargs):
|
||||
to_return = super().set_cond_hint(*args, **kwargs)
|
||||
# cond hint for LLLite needs to be scaled between (-1, 1) instead of (0, 1)
|
||||
self.cond_hint_original = self.cond_hint_original * 2.0 - 1.0
|
||||
return to_return
|
||||
# TODO: do model patching here
|
||||
pass
|
||||
|
||||
def pre_run_advanced(self, *args, **kwargs):
|
||||
AdvancedControlBase.pre_run_advanced(self, *args, **kwargs)
|
||||
self.patch.set_control(self)
|
||||
#logger.warn(f"in pre_run_advanced: {id(self)}")
|
||||
# TODO: set control on patches
|
||||
|
||||
def get_control_advanced(self, x_noisy: Tensor, t, cond, batched_number: int):
|
||||
# normal ControlNet stuff
|
||||
@@ -332,24 +390,121 @@ class ControlLLLiteAdvanced(ControlBase, AdvancedControlBase):
|
||||
# done preparing; model patches will take care of everything now.
|
||||
# return normal controlnet stuff
|
||||
return control_prev
|
||||
|
||||
def cleanup_advanced(self):
|
||||
super().cleanup_advanced()
|
||||
# TODO: cleanup patches here
|
||||
|
||||
def copy(self):
|
||||
c = ReferenceAdvanced(self.timestep_keyframes)
|
||||
self.copy_to(c)
|
||||
self.copy_to_advanced(c)
|
||||
return c
|
||||
|
||||
|
||||
class ControlLLLiteAdvanced(ControlBase, AdvancedControlBase):
|
||||
# This ControlNet is more of an attention patch than a traditional controlnet
|
||||
def __init__(self, patch_attn1: LLLitePatch, patch_attn2: LLLitePatch, timestep_keyframes: TimestepKeyframeGroup, device=None):
|
||||
super().__init__(device)
|
||||
AdvancedControlBase.__init__(self, super(), timestep_keyframes=timestep_keyframes, weights_default=ControlWeights.controllllite(), require_model=True)
|
||||
self.patch_attn1 = patch_attn1.set_control(self)
|
||||
self.patch_attn2 = patch_attn2.set_control(self)
|
||||
self.latent_dims_div2 = None
|
||||
self.latent_dims_div4 = None
|
||||
|
||||
def patch_model(self, model: ModelPatcher):
|
||||
model.set_model_attn1_patch(self.patch_attn1)
|
||||
model.set_model_attn2_patch(self.patch_attn2)
|
||||
|
||||
def set_cond_hint(self, *args, **kwargs):
|
||||
to_return = super().set_cond_hint(*args, **kwargs)
|
||||
# cond hint for LLLite needs to be scaled between (-1, 1) instead of (0, 1)
|
||||
self.cond_hint_original = self.cond_hint_original * 2.0 - 1.0
|
||||
return to_return
|
||||
|
||||
def pre_run_advanced(self, *args, **kwargs):
|
||||
AdvancedControlBase.pre_run_advanced(self, *args, **kwargs)
|
||||
#logger.error(f"in cn: {id(self.patch_attn1)},{id(self.patch_attn2)}")
|
||||
self.patch_attn1.set_control(self)
|
||||
self.patch_attn2.set_control(self)
|
||||
#logger.warn(f"in pre_run_advanced: {id(self)}")
|
||||
|
||||
def get_control_advanced(self, x_noisy: Tensor, t, cond, batched_number: int):
|
||||
# normal ControlNet stuff
|
||||
control_prev = None
|
||||
if self.previous_controlnet is not None:
|
||||
control_prev = self.previous_controlnet.get_control(x_noisy, t, cond, batched_number)
|
||||
|
||||
if self.timestep_range is not None:
|
||||
if t[0] > self.timestep_range[0] or t[0] < self.timestep_range[1]:
|
||||
return control_prev
|
||||
|
||||
dtype = x_noisy.dtype
|
||||
# prepare cond_hint
|
||||
if self.sub_idxs is not None or self.cond_hint is None or x_noisy.shape[2] * 8 != self.cond_hint.shape[2] or x_noisy.shape[3] * 8 != self.cond_hint.shape[3]:
|
||||
if self.cond_hint is not None:
|
||||
del self.cond_hint
|
||||
self.cond_hint = None
|
||||
# if self.cond_hint_original length greater or equal to real latent count, subdivide it before scaling
|
||||
if self.sub_idxs is not None and self.cond_hint_original.size(0) >= self.full_latent_length:
|
||||
self.cond_hint = comfy.utils.common_upscale(self.cond_hint_original[self.sub_idxs], x_noisy.shape[3] * 8, x_noisy.shape[2] * 8, 'nearest-exact', "center").to(dtype).to(self.device)
|
||||
else:
|
||||
self.cond_hint = comfy.utils.common_upscale(self.cond_hint_original, x_noisy.shape[3] * 8, x_noisy.shape[2] * 8, 'nearest-exact', "center").to(dtype).to(self.device)
|
||||
if x_noisy.shape[0] != self.cond_hint.shape[0]:
|
||||
self.cond_hint = broadcast_image_to(self.cond_hint, x_noisy.shape[0], batched_number)
|
||||
# some special logic here compared to other controlnets:
|
||||
# * The cond_emb in attn patches will divide latent dims by 2 or 4, integer
|
||||
# * Due to this loss, the cond_emb will become smaller than x input if latent dims are not divisble by 2 or 4
|
||||
divisible_by_2_h = x_noisy.shape[2]%2==0
|
||||
divisible_by_2_w = x_noisy.shape[3]%2==0
|
||||
if not (divisible_by_2_h and divisible_by_2_w):
|
||||
#logger.warn(f"{x_noisy.shape} not divisible by 2!")
|
||||
new_h = (x_noisy.shape[2]//2)*2
|
||||
new_w = (x_noisy.shape[3]//2)*2
|
||||
if not divisible_by_2_h:
|
||||
new_h += 2
|
||||
if not divisible_by_2_w:
|
||||
new_w += 2
|
||||
self.latent_dims_div2 = (new_h, new_w)
|
||||
divisible_by_4_h = x_noisy.shape[2]%4==0
|
||||
divisible_by_4_w = x_noisy.shape[3]%4==0
|
||||
if not (divisible_by_4_h and divisible_by_4_w):
|
||||
#logger.warn(f"{x_noisy.shape} not divisible by 4!")
|
||||
new_h = (x_noisy.shape[2]//4)*4
|
||||
new_w = (x_noisy.shape[3]//4)*4
|
||||
if not divisible_by_4_h:
|
||||
new_h += 4
|
||||
if not divisible_by_4_w:
|
||||
new_w += 4
|
||||
self.latent_dims_div4 = (new_h, new_w)
|
||||
# prepare mask
|
||||
self.prepare_mask_cond_hint(x_noisy=x_noisy, t=t, cond=cond, batched_number=batched_number)
|
||||
# done preparing; model patches will take care of everything now.
|
||||
# return normal controlnet stuff
|
||||
return control_prev
|
||||
|
||||
def cleanup_advanced(self):
|
||||
super().cleanup_advanced()
|
||||
self.patch.cleanup()
|
||||
self.patch_attn1.cleanup()
|
||||
self.patch_attn2.cleanup()
|
||||
self.latent_dims_div2 = None
|
||||
self.latent_dims_div4 = None
|
||||
|
||||
def copy(self):
|
||||
c = ControlLLLiteAdvanced(self.patch, self.timestep_keyframes)
|
||||
c = ControlLLLiteAdvanced(self.patch_attn1, self.patch_attn2, self.timestep_keyframes)
|
||||
self.copy_to(c)
|
||||
self.copy_to_advanced(c)
|
||||
return c
|
||||
|
||||
# deepcopy needs to properly keep track of objects to work between model.clone calls!
|
||||
def __deepcopy__(self, *args, **kwargs):
|
||||
return self
|
||||
# def __deepcopy__(self, *args, **kwargs):
|
||||
# self.cleanup_advanced()
|
||||
# return self
|
||||
|
||||
# def get_models(self):
|
||||
# # get_models is called once at the start of every KSampler run - use to reset already_patched status
|
||||
# out = super().get_models()
|
||||
# logger.error(f"in get_models! {id(self)}")
|
||||
# return out
|
||||
|
||||
|
||||
@@ -360,8 +515,9 @@ def load_controlnet(ckpt_path, timestep_keyframe: TimestepKeyframeGroup=None, mo
|
||||
controlnet_type = ControlWeightType.DEFAULT
|
||||
has_controlnet_key = False
|
||||
has_motion_modules_key = False
|
||||
has_temporal_res_block_key = False
|
||||
for key in controlnet_data:
|
||||
# LLLLite check
|
||||
# LLLite check
|
||||
if "lllite" in key:
|
||||
controlnet_type = ControlWeightType.CONTROLLLLITE
|
||||
break
|
||||
@@ -370,14 +526,23 @@ def load_controlnet(ckpt_path, timestep_keyframe: TimestepKeyframeGroup=None, mo
|
||||
has_motion_modules_key = True
|
||||
elif "controlnet" in key:
|
||||
has_controlnet_key = True
|
||||
# SVD-ControlNet check
|
||||
elif "temporal_res_block" in key:
|
||||
has_temporal_res_block_key = True
|
||||
if has_controlnet_key and has_motion_modules_key:
|
||||
controlnet_type = ControlWeightType.SPARSECTRL
|
||||
elif has_controlnet_key and has_temporal_res_block_key:
|
||||
controlnet_type = ControlWeightType.SVD_CONTROLNET
|
||||
|
||||
if controlnet_type != ControlWeightType.DEFAULT:
|
||||
if controlnet_type == ControlWeightType.CONTROLLLLITE:
|
||||
control = load_controllllite(ckpt_path, controlnet_data=controlnet_data, timestep_keyframe=timestep_keyframe)
|
||||
elif controlnet_type == ControlWeightType.SPARSECTRL:
|
||||
control = load_sparsectrl(ckpt_path, controlnet_data=controlnet_data, timestep_keyframe=timestep_keyframe, model=model)
|
||||
elif controlnet_type == ControlWeightType.SVD_CONTROLNET:
|
||||
control = load_svdcontrolnet(ckpt_path, controlnet_data=controlnet_data, timestep_keyframe=timestep_keyframe)
|
||||
#raise Exception(f"SVD-ControlNet is not supported yet!")
|
||||
#control = comfy_cn.load_controlnet(ckpt_path, model=model)
|
||||
# otherwise, load vanilla ControlNet
|
||||
else:
|
||||
try:
|
||||
@@ -602,6 +767,129 @@ def load_controllllite(ckpt_path: str, controlnet_data: dict[str, Tensor]=None,
|
||||
|
||||
#logger.info(f"loaded {ckpt_path} successfully, {len(modules)} modules")
|
||||
|
||||
patch = LLLitePatch(modules=modules)
|
||||
control = ControlLLLiteAdvanced(patch=patch, timestep_keyframes=timestep_keyframe)
|
||||
patch_attn1 = LLLitePatch(modules=modules, patch_type=LLLitePatch.ATTN1)
|
||||
patch_attn2 = LLLitePatch(modules=modules, patch_type=LLLitePatch.ATTN2)
|
||||
control = ControlLLLiteAdvanced(patch_attn1=patch_attn1, patch_attn2=patch_attn2, timestep_keyframes=timestep_keyframe)
|
||||
return control
|
||||
|
||||
|
||||
def load_svdcontrolnet(ckpt_path: str, controlnet_data: dict[str, Tensor]=None, timestep_keyframe: TimestepKeyframeGroup=None, model=None):
|
||||
if controlnet_data is None:
|
||||
controlnet_data = comfy.utils.load_torch_file(ckpt_path, safe_load=True)
|
||||
|
||||
controlnet_config = None
|
||||
if "controlnet_cond_embedding.conv_in.weight" in controlnet_data: #diffusers format
|
||||
unet_dtype = comfy.model_management.unet_dtype()
|
||||
controlnet_config = svd_unet_config_from_diffusers_unet(controlnet_data, unet_dtype)
|
||||
diffusers_keys = svd_unet_to_diffusers(controlnet_config)
|
||||
diffusers_keys["controlnet_mid_block.weight"] = "middle_block_out.0.weight"
|
||||
diffusers_keys["controlnet_mid_block.bias"] = "middle_block_out.0.bias"
|
||||
|
||||
count = 0
|
||||
loop = True
|
||||
while loop:
|
||||
suffix = [".weight", ".bias"]
|
||||
for s in suffix:
|
||||
k_in = "controlnet_down_blocks.{}{}".format(count, s)
|
||||
k_out = "zero_convs.{}.0{}".format(count, s)
|
||||
if k_in not in controlnet_data:
|
||||
loop = False
|
||||
break
|
||||
diffusers_keys[k_in] = k_out
|
||||
count += 1
|
||||
|
||||
count = 0
|
||||
loop = True
|
||||
while loop:
|
||||
suffix = [".weight", ".bias"]
|
||||
for s in suffix:
|
||||
if count == 0:
|
||||
k_in = "controlnet_cond_embedding.conv_in{}".format(s)
|
||||
else:
|
||||
k_in = "controlnet_cond_embedding.blocks.{}{}".format(count - 1, s)
|
||||
k_out = "input_hint_block.{}{}".format(count * 2, s)
|
||||
if k_in not in controlnet_data:
|
||||
k_in = "controlnet_cond_embedding.conv_out{}".format(s)
|
||||
loop = False
|
||||
diffusers_keys[k_in] = k_out
|
||||
count += 1
|
||||
|
||||
new_sd = {}
|
||||
for k in diffusers_keys:
|
||||
if k in controlnet_data:
|
||||
new_sd[diffusers_keys[k]] = controlnet_data.pop(k)
|
||||
|
||||
leftover_keys = controlnet_data.keys()
|
||||
if len(leftover_keys) > 0:
|
||||
spatial_leftover_keys = []
|
||||
temporal_leftover_keys = []
|
||||
other_leftover_keys = []
|
||||
for key in leftover_keys:
|
||||
if "spatial" in key:
|
||||
spatial_leftover_keys.append(key)
|
||||
elif "temporal" in key:
|
||||
temporal_leftover_keys.append(key)
|
||||
else:
|
||||
other_leftover_keys.append(key)
|
||||
logger.warn(f"spatial_leftover_keys ({len(spatial_leftover_keys)}): {spatial_leftover_keys}")
|
||||
logger.warn(f"temporal_leftover_keys ({len(temporal_leftover_keys)}): {temporal_leftover_keys}")
|
||||
logger.warn(f"other_leftover_keys ({len(other_leftover_keys)}): {other_leftover_keys}")
|
||||
#print("leftover keys:", leftover_keys)
|
||||
controlnet_data = new_sd
|
||||
|
||||
pth_key = 'control_model.zero_convs.0.0.weight'
|
||||
pth = False
|
||||
key = 'zero_convs.0.0.weight'
|
||||
if pth_key in controlnet_data:
|
||||
pth = True
|
||||
key = pth_key
|
||||
prefix = "control_model."
|
||||
elif key in controlnet_data:
|
||||
prefix = ""
|
||||
else:
|
||||
raise ValueError("The provided model is not a valid SVD-ControlNet model! [ErrorCode: MUSTARD]")
|
||||
|
||||
if controlnet_config is None:
|
||||
unet_dtype = comfy.model_management.unet_dtype()
|
||||
controlnet_config = comfy.model_detection.model_config_from_unet(controlnet_data, prefix, unet_dtype, True).unet_config
|
||||
load_device = comfy.model_management.get_torch_device()
|
||||
manual_cast_dtype = comfy.model_management.unet_manual_cast(unet_dtype, load_device)
|
||||
if manual_cast_dtype is not None:
|
||||
controlnet_config["operations"] = comfy.ops.manual_cast
|
||||
controlnet_config.pop("out_channels")
|
||||
controlnet_config["hint_channels"] = controlnet_data["{}input_hint_block.0.weight".format(prefix)].shape[1]
|
||||
control_model = SVDControlNet(**controlnet_config)
|
||||
|
||||
if pth:
|
||||
if 'difference' in controlnet_data:
|
||||
if model is not None:
|
||||
comfy.model_management.load_models_gpu([model])
|
||||
model_sd = model.model_state_dict()
|
||||
for x in controlnet_data:
|
||||
c_m = "control_model."
|
||||
if x.startswith(c_m):
|
||||
sd_key = "diffusion_model.{}".format(x[len(c_m):])
|
||||
if sd_key in model_sd:
|
||||
cd = controlnet_data[x]
|
||||
cd += model_sd[sd_key].type(cd.dtype).to(cd.device)
|
||||
else:
|
||||
print("WARNING: Loaded a diff controlnet without a model. It will very likely not work.")
|
||||
|
||||
class WeightsLoader(torch.nn.Module):
|
||||
pass
|
||||
w = WeightsLoader()
|
||||
w.control_model = control_model
|
||||
missing, unexpected = w.load_state_dict(controlnet_data, strict=False)
|
||||
else:
|
||||
missing, unexpected = control_model.load_state_dict(controlnet_data, strict=False)
|
||||
if len(missing) > 0 or len(unexpected) > 0:
|
||||
logger.info(f"SVD-ControlNet: {missing}, {unexpected}")
|
||||
|
||||
global_average_pooling = False
|
||||
filename = os.path.splitext(ckpt_path)[0]
|
||||
if filename.endswith("_shuffle") or filename.endswith("_shuffle_fp16"): #TODO: smarter way of enabling global_average_pooling
|
||||
global_average_pooling = True
|
||||
|
||||
control = SVDControlNetAdvanced(control_model, timestep_keyframes=timestep_keyframe, global_average_pooling=global_average_pooling, load_device=load_device, manual_cast_dtype=manual_cast_dtype)
|
||||
return control
|
||||
|
||||
|
||||
+36
-8
@@ -11,7 +11,7 @@ import comfy.utils
|
||||
from comfy.controlnet import ControlBase
|
||||
|
||||
from .logger import logger
|
||||
from .utils import AdvancedControlBase, prepare_mask_batch
|
||||
from .utils import AdvancedControlBase, deepcopy_with_sharing, prepare_mask_batch
|
||||
|
||||
|
||||
def extra_options_to_module_prefix(extra_options):
|
||||
@@ -38,12 +38,16 @@ def extra_options_to_module_prefix(extra_options):
|
||||
|
||||
|
||||
class LLLitePatch:
|
||||
def __init__(self, modules: dict[str, 'LLLiteModule'], control: Union[AdvancedControlBase, ControlBase]=None):
|
||||
ATTN1 = "attn1"
|
||||
ATTN2 = "attn2"
|
||||
def __init__(self, modules: dict[str, 'LLLiteModule'], patch_type: str, control: Union[AdvancedControlBase, ControlBase]=None):
|
||||
self.modules = modules
|
||||
self.control = control
|
||||
self.patch_type = patch_type
|
||||
#logger.error(f"create LLLitePatch: {id(self)},{control}")
|
||||
|
||||
def __call__(self, q, k, v, extra_options):
|
||||
#logger.error(f"in __call__: {id(self)}")
|
||||
# determine if have anything to run
|
||||
if self.control.timestep_range is not None:
|
||||
# it turns out comparing single-value tensors to floats is extremely slow
|
||||
@@ -78,21 +82,37 @@ class LLLitePatch:
|
||||
self.modules[d] = self.modules[d].to(device)
|
||||
return self
|
||||
|
||||
def set_control(self, control: Union[AdvancedControlBase, ControlBase]):
|
||||
def set_control(self, control: Union[AdvancedControlBase, ControlBase]) -> 'LLLitePatch':
|
||||
self.control = control
|
||||
#logger.error(f"set control for LLLitePatch: {id(self)},{id(control)}")
|
||||
return self
|
||||
#logger.error(f"set control for LLLitePatch: {id(self)}, cn: {id(control)}")
|
||||
|
||||
def clone_with_control(self, control: AdvancedControlBase):
|
||||
#logger.error(f"clone-set control for LLLitePatch: {id(self)},{id(control)}")
|
||||
return LLLitePatch(self.modules, control)
|
||||
return LLLitePatch(self.modules, self.patch_type, control)
|
||||
|
||||
def cleanup(self):
|
||||
#del self.control
|
||||
#self.control = None
|
||||
#total_cleaned = 0
|
||||
for module in self.modules.values():
|
||||
module.cleanup()
|
||||
# total_cleaned += 1
|
||||
#logger.info(f"cleaned modules: {total_cleaned}, {id(self)}")
|
||||
#logger.error(f"cleanup LLLitePatch: {id(self)}")
|
||||
|
||||
# make sure deepcopy does not copy control, and deepcopied LLLitePatch should be assigned to control
|
||||
def __deepcopy__(self, memo):
|
||||
self.cleanup()
|
||||
to_return: LLLitePatch = deepcopy_with_sharing(self, shared_attribute_names = ['control'], memo=memo)
|
||||
#logger.warn(f"patch {id(self)} turned into {id(to_return)}")
|
||||
try:
|
||||
if self.patch_type == self.ATTN1:
|
||||
to_return.control.patch_attn1 = to_return
|
||||
elif self.patch_type == self.ATTN2:
|
||||
to_return.control.patch_attn2 = to_return
|
||||
except Exception:
|
||||
pass
|
||||
return to_return
|
||||
|
||||
|
||||
# TODO: use comfy.ops to support fp8 properly
|
||||
class LLLiteModule(torch.nn.Module):
|
||||
@@ -159,6 +179,7 @@ class LLLiteModule(torch.nn.Module):
|
||||
self.prev_sub_idxs = None
|
||||
|
||||
def cleanup(self):
|
||||
del self.cond_emb
|
||||
self.cond_emb = None
|
||||
self.cx_shape = None
|
||||
self.prev_batch = 0
|
||||
@@ -167,9 +188,15 @@ class LLLiteModule(torch.nn.Module):
|
||||
def forward(self, x: Tensor, control: Union[AdvancedControlBase, ControlBase]):
|
||||
mask = None
|
||||
mask_tk = None
|
||||
#logger.info(x.shape)
|
||||
if self.cond_emb is None or control.sub_idxs != self.prev_sub_idxs or x.shape[0] != self.prev_batch:
|
||||
# print(f"cond_emb is None, {self.name}")
|
||||
cx = self.conditioning1(control.cond_hint.to(x.device, dtype=x.dtype))
|
||||
cond_hint = control.cond_hint.to(x.device, dtype=x.dtype)
|
||||
if control.latent_dims_div2 is not None and x.shape[-1] != 1280:
|
||||
cond_hint = comfy.utils.common_upscale(cond_hint, control.latent_dims_div2[0] * 8, control.latent_dims_div2[1] * 8, 'nearest-exact', "center").to(x.device, dtype=x.dtype)
|
||||
elif control.latent_dims_div4 is not None and x.shape[-1] == 1280:
|
||||
cond_hint = comfy.utils.common_upscale(cond_hint, control.latent_dims_div4[0] * 8, control.latent_dims_div4[1] * 8, 'nearest-exact', "center").to(x.device, dtype=x.dtype)
|
||||
cx = self.conditioning1(cond_hint)
|
||||
self.cx_shape = cx.shape
|
||||
if not self.is_conv2d:
|
||||
# reshape / b,c,h,w -> b,h*w,c
|
||||
@@ -211,6 +238,7 @@ class LLLiteModule(torch.nn.Module):
|
||||
elif mask_tk is not None:
|
||||
mask = mask * mask_tk
|
||||
|
||||
#logger.info(f"cs: {cx.shape}, x: {x.shape}, is_conv2d: {self.is_conv2d}")
|
||||
cx = torch.cat([cx, self.down(x)], dim=1 if self.is_conv2d else 2)
|
||||
cx = self.mid(cx)
|
||||
cx = self.up(cx)
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ import torch
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image, ImageOps
|
||||
from .utils import ControlWeights, LatentKeyframeGroup, TimestepKeyframeGroup, TimestepKeyframe
|
||||
from .utils import ControlWeights, LatentKeyframeGroup, TimestepKeyframeGroup, TimestepKeyframe, BIGMAX
|
||||
from .logger import logger
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ class LoadImagesFromDirectory:
|
||||
"directory": ("STRING", {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"start_index": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"start_index": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -2,7 +2,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
from collections.abc import Iterable
|
||||
|
||||
from .utils import LatentKeyframe, LatentKeyframeGroup
|
||||
from .utils import LatentKeyframe, LatentKeyframeGroup, BIGMIN, BIGMAX
|
||||
from .utils import StrengthInterpolation as SI
|
||||
from .logger import logger
|
||||
|
||||
@@ -12,7 +12,7 @@ class LatentKeyframeNode:
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"batch_index": ("INT", {"default": 0, "min": -1000, "max": 1000, "step": 1}),
|
||||
"batch_index": ("INT", {"default": 0, "min": BIGMIN, "max": BIGMAX, "step": 1}),
|
||||
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ),
|
||||
},
|
||||
"optional": {
|
||||
@@ -163,8 +163,8 @@ class LatentKeyframeInterpolationNode:
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"batch_index_from": ("INT", {"default": 0, "min": -10000, "max": 10000, "step": 1}),
|
||||
"batch_index_to_excl": ("INT", {"default": 0, "min": -10000, "max": 10000, "step": 1}),
|
||||
"batch_index_from": ("INT", {"default": 0, "min": BIGMIN, "max": BIGMAX, "step": 1}),
|
||||
"batch_index_to_excl": ("INT", {"default": 0, "min": BIGMIN, "max": BIGMAX, "step": 1}),
|
||||
"strength_from": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ),
|
||||
"strength_to": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}, ),
|
||||
"interpolation": ([SI.LINEAR, SI.EASE_IN, SI.EASE_OUT, SI.EASE_IN_OUT], ),
|
||||
|
||||
+6
-2
@@ -3,6 +3,7 @@ from torch import Tensor
|
||||
import folder_paths
|
||||
from nodes import VAEEncode
|
||||
import comfy.utils
|
||||
from comfy.sd import VAE
|
||||
|
||||
from .utils import TimestepKeyframeGroup
|
||||
from .control_sparsectrl import SparseMethod, SparseIndexMethod, SparseSettings, SparseSpreadMethod, PreprocSparseRGBWrapper
|
||||
@@ -148,12 +149,15 @@ class RgbSparseCtrlPreprocessor:
|
||||
|
||||
CATEGORY = "Adv-ControlNet 🛂🅐🅒🅝/SparseCtrl/preprocess"
|
||||
|
||||
def preprocess_images(self, vae, image: Tensor, latent_size: Tensor):
|
||||
def preprocess_images(self, vae: VAE, image: Tensor, latent_size: Tensor):
|
||||
# first, resize image to match latents
|
||||
image = image.movedim(-1,1)
|
||||
image = comfy.utils.common_upscale(image, latent_size["samples"].shape[3] * 8, latent_size["samples"].shape[2] * 8, 'nearest-exact', "center")
|
||||
image = image.movedim(1,-1)
|
||||
# then, vae encode
|
||||
image = VAEEncode.vae_encode_crop_pixels(image)
|
||||
try:
|
||||
image = vae.vae_encode_crop_pixels(image)
|
||||
except Exception:
|
||||
image = VAEEncode.vae_encode_crop_pixels(image)
|
||||
encoded = vae.encode(image[:,:,:,:3])
|
||||
return (PreprocSparseRGBWrapper(condhint=encoded),)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from copy import deepcopy
|
||||
from typing import Callable, Union
|
||||
import torch
|
||||
from torch import Tensor
|
||||
@@ -10,6 +11,9 @@ from comfy.model_patcher import ModelPatcher
|
||||
|
||||
from .logger import logger
|
||||
|
||||
BIGMIN = -(2**63-1)
|
||||
BIGMAX = (2**63-1)
|
||||
|
||||
def load_torch_file_with_dict_factory(controlnet_data: dict[str, Tensor], orig_load_torch_file: Callable):
|
||||
def load_torch_file_with_dict(*args, **kwargs):
|
||||
# immediately restore load_torch_file to original version
|
||||
@@ -34,6 +38,7 @@ class ControlWeightType:
|
||||
CONTROLNET = "controlnet"
|
||||
CONTROLLORA = "controllora"
|
||||
CONTROLLLLITE = "controllllite"
|
||||
SVD_CONTROLNET = "svd_controlnet"
|
||||
SPARSECTRL = "sparsectrl"
|
||||
|
||||
|
||||
@@ -259,6 +264,44 @@ def linear_conversion(x, x_min=0.0, x_max=1.0, new_min=0.0, new_max=1.0):
|
||||
return (((x - x_min)/(x_max - x_min)) * (new_max - new_min)) + new_min
|
||||
|
||||
|
||||
# from https://stackoverflow.com/a/24621200
|
||||
def deepcopy_with_sharing(obj, shared_attribute_names, memo=None):
|
||||
'''
|
||||
Deepcopy an object, except for a given list of attributes, which should
|
||||
be shared between the original object and its copy.
|
||||
|
||||
obj is some object
|
||||
shared_attribute_names: A list of strings identifying the attributes that
|
||||
should be shared between the original and its copy.
|
||||
memo is the dictionary passed into __deepcopy__. Ignore this argument if
|
||||
not calling from within __deepcopy__.
|
||||
'''
|
||||
assert isinstance(shared_attribute_names, (list, tuple))
|
||||
|
||||
shared_attributes = {k: getattr(obj, k) for k in shared_attribute_names}
|
||||
|
||||
if hasattr(obj, '__deepcopy__'):
|
||||
# Do hack to prevent infinite recursion in call to deepcopy
|
||||
deepcopy_method = obj.__deepcopy__
|
||||
obj.__deepcopy__ = None
|
||||
|
||||
for attr in shared_attribute_names:
|
||||
del obj.__dict__[attr]
|
||||
|
||||
clone = deepcopy(obj)
|
||||
|
||||
for attr, val in shared_attributes.items():
|
||||
setattr(obj, attr, val)
|
||||
setattr(clone, attr, val)
|
||||
|
||||
if hasattr(obj, '__deepcopy__'):
|
||||
# Undo hack
|
||||
obj.__deepcopy__ = deepcopy_method
|
||||
del clone.__deepcopy__
|
||||
|
||||
return clone
|
||||
|
||||
|
||||
class WeightTypeException(TypeError):
|
||||
"Raised when weight not compatible with AdvancedControlBase object"
|
||||
pass
|
||||
|
||||
@@ -46,7 +46,8 @@ NOTE: you can also use custom locations for models/motion loras by making use of
|
||||
- Infinite animation length support via sliding context windows across whole unet (Context Options) and/or within motion module (View Options)
|
||||
- Scheduling Context Options to change across different points in the sampling process
|
||||
- FreeInit and FreeNoise support (FreeInit is under iteration opts, FreeNoise is in SampleSettings' noise_type dropdown)
|
||||
- Mixable Motion LoRAs from [original AnimateDiff repository](https://github.com/guoyww/animatediff/) implemented. Caveat: only really work on v2-based motion models like ```mm_sd_v15_v2```, ```mm-p_0.5.pth```, and ```mm-p_0.75.pth```
|
||||
- Mixable Motion LoRAs from [original AnimateDiff repository](https://github.com/guoyww/animatediff/) implemented. Caveat: the original loras really only work on v2-based motion models like ```mm_sd_v15_v2```, ```mm-p_0.5.pth```, and ```mm-p_0.75.pth```.
|
||||
- UPDATE: New motion LoRAs without the v2 limitation can now be trained via the [AnimateDiff-MotionDirector repo](https://github.com/ExponentialML/AnimateDiff-MotionDirector). Shoutout to ExponentialML for implementing MotionDirector for AnimateDiff purposes!
|
||||
- Prompt travel using BatchPromptSchedule node from [ComfyUI_FizzNodes](https://github.com/FizzleDorf/ComfyUI_FizzNodes)
|
||||
- Scale and Effect multival inputs to control motion amount and motion model influence on generation.
|
||||
- Can be float, list of floats, or masks
|
||||
@@ -57,15 +58,17 @@ NOTE: you can also use custom locations for models/motion loras by making use of
|
||||
- NOTE: You will need to use ```autoselect``` or ```linear (HotshotXL/default)``` beta_schedule, the sweetspot for context_length or total frames (when not using context) is 8 frames, and you will need to use an SDXL checkpoint.
|
||||
- AnimateDiff-SDXL support, with corresponding model. Currently, a beta version is out, which you can find info about at [AnimateDiff](https://github.com/guoyww/AnimateDiff/).
|
||||
- NOTE: You will need to use ```autoselect``` or ```linear (AnimateDiff-SDXL)``` beta_schedule. Other than that, same rules of thumb apply to AnimateDiff-SDXL as AnimateDiff.
|
||||
- [AnimateLCM](https://github.com/G-U-N/AnimateLCM) support
|
||||
- NOTE: You will need to use ```autoselect``` or ```lcm``` or ```lcm[100_ots]``` beta_schedule. To use fully with LCM, be sure to use appropriate LCM lora, use the ```lcm``` sampler_name in KSampler nodes, and lower cfg to somewhere around 1.0 to 2.0. Don't forget to decrease steps (minimum = ~4 steps), since LCM converges faster (less steps). Increase step count to increase detail as desired.
|
||||
- AnimateDiff Keyframes to change Scale and Effect at different points in the sampling process.
|
||||
- fp8 support; requires newest ComfyUI and torch >= 2.1 (decreases VRAM usage, but changes outputs)
|
||||
- Mac M1/M2/M3 support
|
||||
- Usage of Context Options and Sample Settings outside of AnimateDiff via Gen2 Use Evolved Sampling node
|
||||
|
||||
## Upcoming Features
|
||||
- Maskable Motion LoRA
|
||||
- Maskable SD LoRA (and perhaps maskable SD Models as well)
|
||||
- [PIA](https://github.com/open-mmlab/PIA) support
|
||||
- Motion LoRA training (experimental)
|
||||
- Anything else AnimateDiff-related that comes out
|
||||
|
||||
|
||||
|
||||
+3
@@ -10,8 +10,10 @@ from .utils_motion import get_sorted_list_via_attr
|
||||
class ContextFuseMethod:
|
||||
FLAT = "flat"
|
||||
PYRAMID = "pyramid"
|
||||
RELATIVE = "relative"
|
||||
|
||||
LIST = [PYRAMID, FLAT]
|
||||
LIST_STATIC = [PYRAMID, RELATIVE, FLAT]
|
||||
|
||||
|
||||
class ContextType:
|
||||
@@ -331,6 +333,7 @@ def create_weights_pyramid(length: int, **kwargs) -> list[float]:
|
||||
FUSE_MAPPING = {
|
||||
ContextFuseMethod.FLAT: create_weights_flat,
|
||||
ContextFuseMethod.PYRAMID: create_weights_pyramid,
|
||||
ContextFuseMethod.RELATIVE: create_weights_pyramid,
|
||||
}
|
||||
|
||||
|
||||
|
||||
+8
-3
@@ -13,7 +13,7 @@ from comfy.model_base import BaseModel
|
||||
|
||||
from .ad_settings import AnimateDiffSettings
|
||||
from .context import ContextOptions, ContextOptions, ContextOptionsGroup
|
||||
from .motion_module_ad import AnimateDiffModel, has_mid_block, normalize_ad_state_dict
|
||||
from .motion_module_ad import AnimateDiffModel, AnimateDiffFormat, has_mid_block, normalize_ad_state_dict
|
||||
from .logger import logger
|
||||
from .utils_motion import ADKeyframe, ADKeyframeGroup, MotionCompatibilityError, get_combined_multival, normalize_min_max
|
||||
from .motion_lora import MotionLoraInfo, MotionLoraList
|
||||
@@ -365,7 +365,8 @@ def load_motion_module_gen1(model_name: str, model: ModelPatcher, motion_lora: M
|
||||
ad_wrapper = AnimateDiffModel(mm_state_dict=mm_state_dict, mm_info=mm_info)
|
||||
ad_wrapper.to(model.model_dtype())
|
||||
ad_wrapper.to(model.offload_device)
|
||||
load_result = ad_wrapper.load_state_dict(mm_state_dict)
|
||||
is_animatelcm = mm_info.mm_format==AnimateDiffFormat.ANIMATELCM
|
||||
load_result = ad_wrapper.load_state_dict(mm_state_dict, strict=not is_animatelcm)
|
||||
# TODO: report load_result of motion_module loading?
|
||||
# wrap motion_module into a ModelPatcher, to allow motion lora patches
|
||||
motion_model = MotionModelPatcher(model=ad_wrapper, load_device=model.load_device, offload_device=model.offload_device)
|
||||
@@ -389,7 +390,11 @@ def load_motion_module_gen2(model_name: str, motion_model_settings: AnimateDiffS
|
||||
ad_wrapper = AnimateDiffModel(mm_state_dict=mm_state_dict, mm_info=mm_info)
|
||||
ad_wrapper.to(comfy.model_management.unet_dtype())
|
||||
ad_wrapper.to(comfy.model_management.unet_offload_device())
|
||||
load_result = ad_wrapper.load_state_dict(mm_state_dict)
|
||||
is_animatelcm = mm_info.mm_format==AnimateDiffFormat.ANIMATELCM
|
||||
load_result = ad_wrapper.load_state_dict(mm_state_dict, strict=not is_animatelcm)
|
||||
# TODO: manually check load_results for AnimateLCM models
|
||||
if is_animatelcm:
|
||||
pass
|
||||
# TODO: report load_result of motion_module loading?
|
||||
# wrap motion_module into a ModelPatcher, to allow motion lora patches
|
||||
motion_model = MotionModelPatcher(model=ad_wrapper, load_device=comfy.model_management.get_torch_device(),
|
||||
|
||||
+75
-19
@@ -15,7 +15,7 @@ from comfy.utils import repeat_to_batch_size
|
||||
import comfy.ops
|
||||
import comfy.model_management
|
||||
|
||||
from .context import ContextOptions, get_context_weights, get_context_windows
|
||||
from .context import ContextFuseMethod, ContextOptions, get_context_weights, get_context_windows
|
||||
from .utils_motion import CrossAttentionMM, MotionCompatibilityError, extend_to_batch_size, prepare_mask_batch
|
||||
from .utils_model import BetaSchedules, ModelTypeSD
|
||||
from .logger import logger
|
||||
@@ -31,6 +31,7 @@ def zero_module(module):
|
||||
class AnimateDiffFormat:
|
||||
ANIMATEDIFF = "AnimateDiff"
|
||||
HOTSHOTXL = "HotshotXL"
|
||||
ANIMATELCM = "AnimateLCM"
|
||||
|
||||
|
||||
class AnimateDiffVersion:
|
||||
@@ -58,6 +59,14 @@ def is_hotshotxl(mm_state_dict: dict[str, Tensor]) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def is_animatelcm(mm_state_dict: dict[str, Tensor]) -> bool:
|
||||
# use lack of ANY pos_encoder keys to determine if animatelcm model
|
||||
for key in mm_state_dict.keys():
|
||||
if "pos_encoder" in key:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_down_block_max(mm_state_dict: dict[str, Tensor]) -> int:
|
||||
# keep track of biggest down_block count in module
|
||||
biggest_block = 0
|
||||
@@ -81,11 +90,14 @@ def has_mid_block(mm_state_dict: dict[str, Tensor]):
|
||||
return False
|
||||
|
||||
|
||||
def get_position_encoding_max_len(mm_state_dict: dict[str, Tensor], mm_name: str) -> int:
|
||||
def get_position_encoding_max_len(mm_state_dict: dict[str, Tensor], mm_name: str, mm_format: str) -> Union[int, None]:
|
||||
# use pos_encoder.pe entries to determine max length - [1, {max_length}, {320|640|1280}]
|
||||
for key in mm_state_dict.keys():
|
||||
if key.endswith("pos_encoder.pe"):
|
||||
return mm_state_dict[key].size(1) # get middle dim
|
||||
# AnimateLCM models should have no pos_encoder entries, and assumed to be 64
|
||||
if mm_format == AnimateDiffFormat.ANIMATELCM:
|
||||
return 64
|
||||
raise MotionCompatibilityError(f"No pos_encoder.pe found in mm_state_dict - {mm_name} is not a valid AnimateDiff motion module!")
|
||||
|
||||
|
||||
@@ -98,6 +110,11 @@ def find_hotshot_module_num(key: str) -> Union[int, None]:
|
||||
|
||||
|
||||
def normalize_ad_state_dict(mm_state_dict: dict[str, Tensor], mm_name: str) -> Tuple[dict[str, Tensor], AnimateDiffInfo]:
|
||||
# from pathlib import Path
|
||||
# with open(Path(__file__).parent.parent.parent / f"keys_{mm_name}.txt", "w") as afile:
|
||||
# for key, value in mm_state_dict.items():
|
||||
# afile.write(f"{key}:\t{value.shape}\n")
|
||||
|
||||
# remove all non-temporal keys (in case model has extra stuff in it)
|
||||
for key in list(mm_state_dict.keys()):
|
||||
if "temporal" not in key:
|
||||
@@ -115,11 +132,13 @@ def normalize_ad_state_dict(mm_state_dict: dict[str, Tensor], mm_name: str) -> T
|
||||
mm_format = AnimateDiffFormat.ANIMATEDIFF
|
||||
if is_hotshotxl(mm_state_dict):
|
||||
mm_format = AnimateDiffFormat.HOTSHOTXL
|
||||
if is_animatelcm(mm_state_dict):
|
||||
mm_format = AnimateDiffFormat.ANIMATELCM
|
||||
# determine the model's version
|
||||
mm_version = AnimateDiffVersion.V1
|
||||
if has_mid_block(mm_state_dict):
|
||||
mm_version = AnimateDiffVersion.V2
|
||||
elif sd_type==ModelTypeSD.SD1_5 and get_position_encoding_max_len(mm_state_dict, mm_name)==32:
|
||||
elif sd_type==ModelTypeSD.SD1_5 and get_position_encoding_max_len(mm_state_dict, mm_name, mm_format)==32:
|
||||
mm_version = AnimateDiffVersion.V3
|
||||
info = AnimateDiffInfo(sd_type=sd_type, mm_format=mm_format, mm_version=mm_version, mm_name=mm_name)
|
||||
# convert to AnimateDiff format, if needed
|
||||
@@ -157,7 +176,8 @@ class AnimateDiffModel(nn.Module):
|
||||
self.down_blocks: Iterable[MotionModule] = nn.ModuleList([])
|
||||
self.up_blocks: Iterable[MotionModule] = nn.ModuleList([])
|
||||
self.mid_block: Union[MotionModule, None] = None
|
||||
self.encoding_max_len = get_position_encoding_max_len(mm_state_dict, mm_info.mm_name)
|
||||
self.encoding_max_len = get_position_encoding_max_len(mm_state_dict, mm_info.mm_name, mm_info.mm_format)
|
||||
self.has_position_encoding = self.encoding_max_len is not None
|
||||
# determine ops to use (to support fp8 properly)
|
||||
if comfy.model_management.unet_manual_cast(comfy.model_management.unet_dtype(), comfy.model_management.get_torch_device()) is None:
|
||||
ops = comfy.ops.disable_weight_init
|
||||
@@ -170,20 +190,31 @@ class AnimateDiffModel(nn.Module):
|
||||
layer_channels = (320, 640, 1280, 1280)
|
||||
# fill out down/up blocks and middle block, if present
|
||||
for c in layer_channels:
|
||||
self.down_blocks.append(MotionModule(c, temporal_position_encoding_max_len=self.encoding_max_len, block_type=BlockType.DOWN, ops=ops))
|
||||
self.down_blocks.append(MotionModule(c, temporal_position_encoding=self.has_position_encoding,
|
||||
temporal_position_encoding_max_len=self.encoding_max_len, block_type=BlockType.DOWN, ops=ops))
|
||||
for c in reversed(layer_channels):
|
||||
self.up_blocks.append(MotionModule(c, temporal_position_encoding_max_len=self.encoding_max_len, block_type=BlockType.UP, ops=ops))
|
||||
self.up_blocks.append(MotionModule(c, temporal_position_encoding=self.has_position_encoding,
|
||||
temporal_position_encoding_max_len=self.encoding_max_len, block_type=BlockType.UP, ops=ops))
|
||||
if has_mid_block(mm_state_dict):
|
||||
self.mid_block = MotionModule(1280, temporal_position_encoding_max_len=self.encoding_max_len, block_type=BlockType.MID, ops=ops)
|
||||
self.mid_block = MotionModule(1280, temporal_position_encoding=self.has_position_encoding,
|
||||
temporal_position_encoding_max_len=self.encoding_max_len, block_type=BlockType.MID, ops=ops)
|
||||
self.AD_video_length: int = 24
|
||||
|
||||
def get_device_debug(self):
|
||||
return self.down_blocks[0].motion_modules[0].temporal_transformer.proj_in.weight.device
|
||||
|
||||
def is_length_valid_for_encoding_max_len(self, length: int):
|
||||
if self.encoding_max_len is None:
|
||||
return True
|
||||
return length <= self.encoding_max_len
|
||||
|
||||
def get_best_beta_schedule(self, log=False) -> str:
|
||||
to_return = None
|
||||
if self.mm_info.sd_type == ModelTypeSD.SD1_5:
|
||||
to_return = BetaSchedules.SQRT_LINEAR
|
||||
if self.mm_info.mm_format == AnimateDiffFormat.ANIMATELCM:
|
||||
to_return = BetaSchedules.LCM # while LCM_100 is the intended schedule, I find LCM to have much less flicker
|
||||
else:
|
||||
to_return = BetaSchedules.SQRT_LINEAR
|
||||
elif self.mm_info.sd_type == ModelTypeSD.SDXL:
|
||||
if self.mm_info.mm_format == AnimateDiffFormat.HOTSHOTXL:
|
||||
to_return = BetaSchedules.LINEAR
|
||||
@@ -352,22 +383,28 @@ class AnimateDiffModel(nn.Module):
|
||||
|
||||
|
||||
class MotionModule(nn.Module):
|
||||
def __init__(self, in_channels, temporal_position_encoding_max_len=24, block_type: str=BlockType.DOWN, ops=comfy.ops.disable_weight_init):
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
temporal_position_encoding=True,
|
||||
temporal_position_encoding_max_len=24,
|
||||
block_type: str=BlockType.DOWN,
|
||||
ops=comfy.ops.disable_weight_init
|
||||
):
|
||||
super().__init__()
|
||||
if block_type == BlockType.MID:
|
||||
# mid blocks contain only a single VanillaTemporalModule
|
||||
self.motion_modules: Iterable[VanillaTemporalModule] = nn.ModuleList([get_motion_module(in_channels, temporal_position_encoding_max_len, ops=ops)])
|
||||
self.motion_modules: Iterable[VanillaTemporalModule] = nn.ModuleList([get_motion_module(in_channels, temporal_position_encoding, temporal_position_encoding_max_len, ops=ops)])
|
||||
else:
|
||||
# down blocks contain two VanillaTemporalModules
|
||||
self.motion_modules: Iterable[VanillaTemporalModule] = nn.ModuleList(
|
||||
[
|
||||
get_motion_module(in_channels, temporal_position_encoding_max_len, ops=ops),
|
||||
get_motion_module(in_channels, temporal_position_encoding_max_len, ops=ops)
|
||||
get_motion_module(in_channels, temporal_position_encoding, temporal_position_encoding_max_len, ops=ops),
|
||||
get_motion_module(in_channels, temporal_position_encoding, temporal_position_encoding_max_len, ops=ops)
|
||||
]
|
||||
)
|
||||
# up blocks contain one additional VanillaTemporalModule
|
||||
if block_type == BlockType.UP:
|
||||
self.motion_modules.append(get_motion_module(in_channels, temporal_position_encoding_max_len, ops=ops))
|
||||
self.motion_modules.append(get_motion_module(in_channels, temporal_position_encoding, temporal_position_encoding_max_len, ops=ops))
|
||||
|
||||
def set_video_length(self, video_length: int, full_length: int):
|
||||
for motion_module in self.motion_modules:
|
||||
@@ -398,8 +435,8 @@ class MotionModule(nn.Module):
|
||||
motion_module.reset_temp_vars()
|
||||
|
||||
|
||||
def get_motion_module(in_channels, temporal_position_encoding_max_len, ops=comfy.ops.disable_weight_init):
|
||||
return VanillaTemporalModule(in_channels=in_channels, temporal_position_encoding_max_len=temporal_position_encoding_max_len, ops=ops)
|
||||
def get_motion_module(in_channels, temporal_position_encoding, temporal_position_encoding_max_len, ops=comfy.ops.disable_weight_init):
|
||||
return VanillaTemporalModule(in_channels=in_channels, temporal_position_encoding=temporal_position_encoding, temporal_position_encoding_max_len=temporal_position_encoding_max_len, ops=ops)
|
||||
|
||||
|
||||
class VanillaTemporalModule(nn.Module):
|
||||
@@ -776,11 +813,9 @@ class TemporalTransformerBlock(nn.Module):
|
||||
hidden_states = rearrange(hidden_states, "(b f) d c -> b f d c", f=video_length)
|
||||
value_final = torch.zeros_like(hidden_states)
|
||||
count_final = torch.zeros_like(hidden_states)
|
||||
# bias_final = [0.0] * video_length
|
||||
batched_conds = hidden_states.size(1) // video_length
|
||||
for sub_idxs in views:
|
||||
weights = get_context_weights(len(sub_idxs), view_options.fuse_method) * batched_conds
|
||||
weights_tensor = torch.Tensor(weights).to(device=hidden_states.device).unsqueeze(0).unsqueeze(-1).unsqueeze(-1)
|
||||
|
||||
sub_hidden_states = rearrange(hidden_states[:, sub_idxs], "b f d c -> (b f) d c")
|
||||
for attention_block, norm in zip(self.attention_blocks, self.norms):
|
||||
norm_hidden_states = norm(sub_hidden_states).to(sub_hidden_states.dtype)
|
||||
@@ -797,14 +832,35 @@ class TemporalTransformerBlock(nn.Module):
|
||||
)
|
||||
sub_hidden_states = rearrange(sub_hidden_states, "(b f) d c -> b f d c", f=len(sub_idxs))
|
||||
|
||||
# if view_options.fuse_method == ContextFuseMethod.RELATIVE:
|
||||
# for pos, idx in enumerate(sub_idxs):
|
||||
# # bias is the influence of a specific index in relation to the whole context window
|
||||
# bias = 1 - abs(idx - (sub_idxs[0] + sub_idxs[-1]) / 2) / ((sub_idxs[-1] - sub_idxs[0] + 1e-2) / 2)
|
||||
# bias = max(1e-2, bias)
|
||||
# # take weighted averate relative to total bias of current idx
|
||||
# bias_total = bias_final[idx]
|
||||
# prev_weight = torch.tensor([bias_total / (bias_total + bias)],
|
||||
# dtype=value_final.dtype, device=value_final.device).unsqueeze(0).unsqueeze(-1).unsqueeze(-1)
|
||||
# #prev_weight = torch.cat([prev_weight]*value_final.shape[1], dim=1)
|
||||
# new_weight = torch.tensor([bias / (bias_total + bias)],
|
||||
# dtype=value_final.dtype, device=value_final.device).unsqueeze(0).unsqueeze(-1).unsqueeze(-1)
|
||||
# #new_weight = torch.cat([new_weight]*value_final.shape[1], dim=1)
|
||||
# test = value_final[:, idx:idx+1, :, :]
|
||||
# value_final[:, idx:idx+1, :, :] = value_final[:, idx:idx+1, :, :] * prev_weight + sub_hidden_states[:, pos:pos+1, : ,:] * new_weight
|
||||
# bias_final[idx] = bias_total + bias
|
||||
# else:
|
||||
weights = get_context_weights(len(sub_idxs), view_options.fuse_method) * batched_conds
|
||||
weights_tensor = torch.Tensor(weights).to(device=hidden_states.device).unsqueeze(0).unsqueeze(-1).unsqueeze(-1)
|
||||
value_final[:, sub_idxs] += sub_hidden_states * weights_tensor
|
||||
count_final[:, sub_idxs] += weights_tensor
|
||||
|
||||
# get weighted average of sub_hidden_states
|
||||
# get weighted average of sub_hidden_states, if fuse method requires it
|
||||
# if view_options.fuse_method != ContextFuseMethod.RELATIVE:
|
||||
hidden_states = value_final / count_final
|
||||
hidden_states = rearrange(hidden_states, "b f d c -> (b f) d c")
|
||||
del value_final
|
||||
del count_final
|
||||
# del bias_final
|
||||
|
||||
hidden_states = self.ff(self.ff_norm(hidden_states)) + hidden_states
|
||||
|
||||
|
||||
+28
-45
@@ -1,65 +1,28 @@
|
||||
from pathlib import Path
|
||||
import torch
|
||||
|
||||
import comfy.sample as comfy_sample
|
||||
from comfy.model_patcher import ModelPatcher
|
||||
|
||||
from .logger import logger
|
||||
from .utils_model import BetaSchedules, get_available_motion_loras, get_available_motion_models, get_motion_lora_path
|
||||
from .motion_lora import MotionLoraInfo, MotionLoraList
|
||||
from .model_injection import InjectionParams, ModelPatcherAndInjector, AnimateDiffSettings, load_motion_module_gen1
|
||||
from .sample_settings import SampleSettings, SeedNoiseGeneration
|
||||
from .sampling import motion_sample_factory
|
||||
|
||||
from .nodes_gen1 import (AnimateDiffLoaderGen1, LegacyAnimateDiffLoaderWithContext, AnimateDiffModelSettings,
|
||||
AnimateDiffModelSettingsSimple, AnimateDiffModelSettingsAdvanced, AnimateDiffModelSettingsAdvancedAttnStrengths)
|
||||
from .nodes_gen2 import UseEvolvedSamplingNode, ApplyAnimateDiffModelNode, ApplyAnimateDiffModelBasicNode, LoadAnimateDiffModelNode, ADKeyframeNode
|
||||
from .nodes_multival import MultivalDynamicNode, MultivalFloatNode, MultivalScaledMaskNode
|
||||
from .nodes_sample import FreeInitOptionsNode, NoiseLayerAddWeightedNode, SampleSettingsNode, NoiseLayerAddNode, NoiseLayerReplaceNode, IterationOptionsNode
|
||||
from .nodes_multival import MultivalDynamicNode, MultivalScaledMaskNode
|
||||
from .nodes_sample import (FreeInitOptionsNode, NoiseLayerAddWeightedNode, SampleSettingsNode, NoiseLayerAddNode, NoiseLayerReplaceNode, IterationOptionsNode,
|
||||
CustomCFGNode, CustomCFGKeyframeNode)
|
||||
from .nodes_sigma_schedule import (SigmaScheduleNode, RawSigmaScheduleNode, WeightedAverageSigmaScheduleNode, InterpolatedWeightedAverageSigmaScheduleNode, SplitAndCombineSigmaScheduleNode)
|
||||
from .nodes_context import (LegacyLoopedUniformContextOptionsNode, LoopedUniformContextOptionsNode, LoopedUniformViewOptionsNode, StandardUniformContextOptionsNode, StandardStaticContextOptionsNode, BatchedContextOptionsNode,
|
||||
StandardStaticViewOptionsNode, StandardUniformViewOptionsNode, ViewAsContextOptionsNode)
|
||||
from .nodes_ad_settings import AnimateDiffSettingsNode, ManualAdjustPENode, SweetspotStretchPENode, FullStretchPENode
|
||||
from .nodes_extras import AnimateDiffUnload, EmptyLatentImageLarge, CheckpointLoaderSimpleWithNoiseSelect
|
||||
from .nodes_deprecated import AnimateDiffLoader_Deprecated, AnimateDiffLoaderAdvanced_Deprecated, AnimateDiffCombine_Deprecated
|
||||
from .nodes_lora import AnimateDiffLoraLoader, MaskedLoraLoader
|
||||
|
||||
from .logger import logger
|
||||
|
||||
# override comfy_sample.sample with animatediff-support version
|
||||
comfy_sample.sample = motion_sample_factory(comfy_sample.sample)
|
||||
comfy_sample.sample_custom = motion_sample_factory(comfy_sample.sample_custom, is_custom=True)
|
||||
|
||||
|
||||
class AnimateDiffLoraLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"lora_name": (get_available_motion_loras(),),
|
||||
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}),
|
||||
},
|
||||
"optional": {
|
||||
"prev_motion_lora": ("MOTION_LORA",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MOTION_LORA",)
|
||||
CATEGORY = "Animate Diff 🎭🅐🅓"
|
||||
FUNCTION = "load_motion_lora"
|
||||
|
||||
def load_motion_lora(self, lora_name: str, strength: float, prev_motion_lora: MotionLoraList=None):
|
||||
if prev_motion_lora is None:
|
||||
prev_motion_lora = MotionLoraList()
|
||||
else:
|
||||
prev_motion_lora = prev_motion_lora.clone()
|
||||
# check if motion lora with name exists
|
||||
lora_path = get_motion_lora_path(lora_name)
|
||||
if not Path(lora_path).is_file():
|
||||
raise FileNotFoundError(f"Motion lora with name '{lora_name}' not found.")
|
||||
# create motion lora info to be loaded in AnimateDiff Loader
|
||||
lora_info = MotionLoraInfo(name=lora_name, strength=strength)
|
||||
prev_motion_lora.add_lora(lora_info)
|
||||
|
||||
return (prev_motion_lora,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
# Unencapsulated
|
||||
"ADE_AnimateDiffLoRALoader": AnimateDiffLoraLoader,
|
||||
@@ -91,6 +54,14 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ADE_AdjustPESweetspotStretch": SweetspotStretchPENode,
|
||||
"ADE_AdjustPEFullStretch": FullStretchPENode,
|
||||
"ADE_AdjustPEManual": ManualAdjustPENode,
|
||||
# Sample Settings
|
||||
"ADE_CustomCFG": CustomCFGNode,
|
||||
"ADE_CustomCFGKeyframe": CustomCFGKeyframeNode,
|
||||
"ADE_SigmaSchedule": SigmaScheduleNode,
|
||||
"ADE_RawSigmaSchedule": RawSigmaScheduleNode,
|
||||
"ADE_SigmaScheduleWeightedAverage": WeightedAverageSigmaScheduleNode,
|
||||
"ADE_SigmaScheduleWeightedAverageInterp": InterpolatedWeightedAverageSigmaScheduleNode,
|
||||
"ADE_SigmaScheduleSplitAndCombine": SplitAndCombineSigmaScheduleNode,
|
||||
# Extras Nodes
|
||||
"ADE_AnimateDiffUnload": AnimateDiffUnload,
|
||||
"ADE_EmptyLatentImageLarge": EmptyLatentImageLarge,
|
||||
@@ -107,6 +78,8 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ADE_ApplyAnimateDiffModelSimple": ApplyAnimateDiffModelBasicNode,
|
||||
"ADE_ApplyAnimateDiffModel": ApplyAnimateDiffModelNode,
|
||||
"ADE_LoadAnimateDiffModel": LoadAnimateDiffModelNode,
|
||||
# MaskedLoraLoader
|
||||
#"ADE_MaskedLoadLora": MaskedLoraLoader,
|
||||
# Deprecated Nodes
|
||||
"AnimateDiffLoaderV1": AnimateDiffLoader_Deprecated,
|
||||
"ADE_AnimateDiffLoaderV1Advanced": AnimateDiffLoaderAdvanced_Deprecated,
|
||||
@@ -143,6 +116,14 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ADE_AdjustPESweetspotStretch": "Adjust PE [Sweetspot Stretch] 🎭🅐🅓",
|
||||
"ADE_AdjustPEFullStretch": "Adjust PE [Full Stretch] 🎭🅐🅓",
|
||||
"ADE_AdjustPEManual": "Adjust PE [Manual] 🎭🅐🅓",
|
||||
# Sample Settings
|
||||
"ADE_CustomCFG": "Custom CFG 🎭🅐🅓",
|
||||
"ADE_CustomCFGKeyframe": "Custom CFG Keyframe 🎭🅐🅓",
|
||||
"ADE_SigmaSchedule": "Create Sigma Schedule 🎭🅐🅓",
|
||||
"ADE_RawSigmaSchedule": "Create Raw Sigma Schedule 🎭🅐🅓",
|
||||
"ADE_SigmaScheduleWeightedAverage": "Sigma Schedule Weighted Mean 🎭🅐🅓",
|
||||
"ADE_SigmaScheduleWeightedAverageInterp": "Sigma Schedule Interpolated Mean 🎭🅐🅓",
|
||||
"ADE_SigmaScheduleSplitAndCombine": "Sigma Schedule Split Combine 🎭🅐🅓",
|
||||
# Extras Nodes
|
||||
"ADE_AnimateDiffUnload": "AnimateDiff Unload 🎭🅐🅓",
|
||||
"ADE_EmptyLatentImageLarge": "Empty Latent Image (Big Batch) 🎭🅐🅓",
|
||||
@@ -159,8 +140,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ADE_ApplyAnimateDiffModelSimple": "Apply AnimateDiff Model 🎭🅐🅓②",
|
||||
"ADE_ApplyAnimateDiffModel": "Apply AnimateDiff Model (Adv.) 🎭🅐🅓②",
|
||||
"ADE_LoadAnimateDiffModel": "Load AnimateDiff Model 🎭🅐🅓②",
|
||||
# MaskedLoraLoader
|
||||
#"ADE_MaskedLoadLora": "Load LoRA (Masked) 🎭🅐🅓",
|
||||
# Deprecated Nodes
|
||||
"AnimateDiffLoaderV1": "AnimateDiff Loader [DEPRECATED] 🎭🅐🅓",
|
||||
"ADE_AnimateDiffLoaderV1Advanced": "AnimateDiff Loader (Advanced) [DEPRECATED] 🎭🅐🅓",
|
||||
"ADE_AnimateDiffCombine": "DO NOT USE, USE VideoCombine from ComfyUI-VideoHelperSuite instead! AnimateDiff Combine [DEPRECATED, DO NOT USE] 🎭🅐🅓",
|
||||
"ADE_AnimateDiffCombine": "AnimateDiff Combine [DEPRECATED, Use Video Combine (VHS) Instead!] 🎭🅐🅓",
|
||||
}
|
||||
|
||||
+1
-1
@@ -145,7 +145,7 @@ class StandardStaticContextOptionsNode:
|
||||
"context_overlap": ("INT", {"default": 4, "min": 0, "max": OVERLAP_MAX}),
|
||||
},
|
||||
"optional": {
|
||||
"fuse_method": (ContextFuseMethod.LIST,),
|
||||
"fuse_method": (ContextFuseMethod.LIST_STATIC,),
|
||||
"use_on_equal_length": ("BOOLEAN", {"default": False},),
|
||||
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"guarantee_steps": ("INT", {"default": 1, "min": 0, "max": BIGMAX}),
|
||||
|
||||
+6
-4
@@ -12,7 +12,7 @@ from PIL.PngImagePlugin import PngInfo
|
||||
import folder_paths
|
||||
from comfy.model_patcher import ModelPatcher
|
||||
|
||||
from .context import ContextSchedules, ContextOptions
|
||||
from .context import ContextOptionsGroup, ContextOptions, ContextSchedules
|
||||
from .logger import logger
|
||||
from .utils_model import Folders, BetaSchedules, get_available_motion_models
|
||||
from .model_injection import ModelPatcherAndInjector, InjectionParams, MotionModelGroup, load_motion_module_gen1
|
||||
@@ -109,16 +109,18 @@ class AnimateDiffLoaderAdvanced_Deprecated:
|
||||
model_name=model_name,
|
||||
apply_v2_properly=False,
|
||||
)
|
||||
# set context settings
|
||||
params.set_context(
|
||||
context_group = ContextOptionsGroup()
|
||||
context_group.add(
|
||||
ContextOptions(
|
||||
context_length=context_length,
|
||||
context_stride=context_stride,
|
||||
context_overlap=context_overlap,
|
||||
context_schedule=context_schedule,
|
||||
closed_loop=closed_loop,
|
||||
)
|
||||
)
|
||||
)
|
||||
# set context settings
|
||||
params.set_context(context_options=context_group)
|
||||
# inject for use in sampling code
|
||||
model = ModelPatcherAndInjector(model)
|
||||
model.motion_models = MotionModelGroup(motion_model)
|
||||
|
||||
+2
-2
@@ -6,13 +6,13 @@ from comfy.model_patcher import ModelPatcher
|
||||
from comfy.sd import load_checkpoint_guess_config
|
||||
|
||||
from .logger import logger
|
||||
from .utils_model import IsChangedHelper, BetaSchedules
|
||||
from .utils_model import BetaSchedules
|
||||
from .model_injection import get_vanilla_model_patcher
|
||||
|
||||
|
||||
class AnimateDiffUnload:
|
||||
def __init__(self) -> None:
|
||||
self.change = IsChangedHelper()
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
+14
-7
@@ -81,13 +81,20 @@ class AnimateDiffLoaderGen1:
|
||||
model.sample_settings = sample_settings if sample_settings is not None else SampleSettings()
|
||||
model.motion_injection_params = params
|
||||
|
||||
# save model sampling from BetaSchedule as object patch
|
||||
# if autoselect, get suggested beta_schedule from motion model
|
||||
if beta_schedule == BetaSchedules.AUTOSELECT and not model.motion_models.is_empty():
|
||||
beta_schedule = model.motion_models[0].model.get_best_beta_schedule(log=True)
|
||||
new_model_sampling = BetaSchedules.to_model_sampling(beta_schedule, model)
|
||||
if new_model_sampling is not None:
|
||||
model.add_object_patch("model_sampling", new_model_sampling)
|
||||
if model.sample_settings.custom_cfg is not None:
|
||||
logger.info("[Sample Settings] custom_cfg is set; will override any KSampler cfg values or patches.")
|
||||
|
||||
if model.sample_settings.sigma_schedule is not None:
|
||||
logger.info("[Sample Settings] sigma_schedule is set; will override beta_schedule.")
|
||||
model.add_object_patch("model_sampling", model.sample_settings.sigma_schedule.clone().model_sampling)
|
||||
else:
|
||||
# save model sampling from BetaSchedule as object patch
|
||||
# if autoselect, get suggested beta_schedule from motion model
|
||||
if beta_schedule == BetaSchedules.AUTOSELECT and not model.motion_models.is_empty():
|
||||
beta_schedule = model.motion_models[0].model.get_best_beta_schedule(log=True)
|
||||
new_model_sampling = BetaSchedules.to_model_sampling(beta_schedule, model)
|
||||
if new_model_sampling is not None:
|
||||
model.add_object_patch("model_sampling", new_model_sampling)
|
||||
|
||||
del motion_model
|
||||
return (model,)
|
||||
|
||||
+14
-7
@@ -59,13 +59,20 @@ class UseEvolvedSamplingNode:
|
||||
model.sample_settings = sample_settings if sample_settings is not None else SampleSettings()
|
||||
model.motion_injection_params = params
|
||||
|
||||
# save model_sampling from BetaSchedule as object patch
|
||||
# if autoselect, get suggested beta_schedule from motion model
|
||||
if beta_schedule == BetaSchedules.AUTOSELECT and not model.motion_models.is_empty():
|
||||
beta_schedule = model.motion_models[0].model.get_best_beta_schedule(log=True)
|
||||
new_model_sampling = BetaSchedules.to_model_sampling(beta_schedule, model)
|
||||
if new_model_sampling is not None:
|
||||
model.add_object_patch("model_sampling", new_model_sampling)
|
||||
if model.sample_settings.custom_cfg is not None:
|
||||
logger.info("[Sample Settings] custom_cfg is set; will override any KSampler cfg values or patches.")
|
||||
|
||||
if model.sample_settings.sigma_schedule is not None:
|
||||
logger.info("[Sample Settings] sigma_schedule is set; will override beta_schedule.")
|
||||
model.add_object_patch("model_sampling", model.sample_settings.sigma_schedule.clone().model_sampling)
|
||||
else:
|
||||
# save model_sampling from BetaSchedule as object patch
|
||||
# if autoselect, get suggested beta_schedule from motion model
|
||||
if beta_schedule == BetaSchedules.AUTOSELECT and not model.motion_models.is_empty():
|
||||
beta_schedule = model.motion_models[0].model.get_best_beta_schedule(log=True)
|
||||
new_model_sampling = BetaSchedules.to_model_sampling(beta_schedule, model)
|
||||
if new_model_sampling is not None:
|
||||
model.add_object_patch("model_sampling", new_model_sampling)
|
||||
|
||||
del m_models
|
||||
return (model,)
|
||||
|
||||
+44
-37
@@ -4,7 +4,7 @@ from typing import Union
|
||||
import torch
|
||||
from torch import Tensor
|
||||
|
||||
from .utils_motion import linear_conversion, normalize_min_max
|
||||
from .utils_motion import linear_conversion, normalize_min_max, extend_to_batch_size
|
||||
|
||||
|
||||
class ScaleType:
|
||||
@@ -41,24 +41,64 @@ class MultivalDynamicNode:
|
||||
if len(float_val) < mask_optional.shape[0]:
|
||||
# copies last entry enough times to match mask shape
|
||||
float_val = float_val + float_val[-1]*(mask_optional.shape[0]-len(float_val))
|
||||
if mask_optional.shape[0] < len(float_val):
|
||||
mask_optional = extend_to_batch_size(mask_optional, len(float_val))
|
||||
float_val = float_val[:mask_optional.shape[0]]
|
||||
float_val: Tensor = torch.tensor(float_val).unsqueeze(-1).unsqueeze(-1)
|
||||
# now that inputs are normalized, figure out what value to actually return
|
||||
if mask_optional is not None:
|
||||
mask_optional = mask_optional.clone()
|
||||
mask_optional = mask_optional * float_val
|
||||
if float_is_iterable:
|
||||
mask_optional = mask_optional[:] * float_val.to(mask_optional.dtype).to(mask_optional.device)
|
||||
else:
|
||||
mask_optional = mask_optional * float_val
|
||||
return (mask_optional,)
|
||||
else:
|
||||
if not float_is_iterable:
|
||||
return (float_val,)
|
||||
# create a dummy mask of b,h,w=float_len,1,1 (sigle pixel)
|
||||
# purpose is for float input to work with mask code, without special cases
|
||||
float_len = len(float_val) if float_is_iterable else 1
|
||||
float_len = float_val.shape[0] if float_is_iterable else 1
|
||||
shape = (float_len,1,1)
|
||||
mask_optional = torch.ones(shape)
|
||||
mask_optional = mask_optional * float_val
|
||||
mask_optional = mask_optional[:] * float_val.to(mask_optional.dtype).to(mask_optional.device)
|
||||
return (mask_optional,)
|
||||
|
||||
|
||||
class MultivalScaledMaskNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"min_float_val": ("FLOAT", {"default": 0.0, "min": 0.0, "step": 0.001}),
|
||||
"max_float_val": ("FLOAT", {"default": 1.0, "min": 0.0, "step": 0.001}),
|
||||
"mask": ("MASK",),
|
||||
},
|
||||
"optional": {
|
||||
"scaling": (ScaleType.LIST,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MULTIVAL",)
|
||||
CATEGORY = "Animate Diff 🎭🅐🅓/multival"
|
||||
FUNCTION = "create_multival"
|
||||
|
||||
def create_multival(self, min_float_val: float, max_float_val: float, mask: Tensor, scaling: str=ScaleType.ABSOLUTE):
|
||||
# TODO: allow min_float_val and max_float_val to be list[float]
|
||||
if isinstance(min_float_val, Iterable):
|
||||
raise ValueError(f"min_float_val must be type float (no lists allowed here), not {type(min_float_val).__name__}.")
|
||||
if isinstance(max_float_val, Iterable):
|
||||
raise ValueError(f"max_float_val must be type float (no lists allowed here), not {type(max_float_val).__name__}.")
|
||||
|
||||
if scaling == ScaleType.ABSOLUTE:
|
||||
mask = linear_conversion(mask.clone(), new_min=min_float_val, new_max=max_float_val)
|
||||
elif scaling == ScaleType.RELATIVE:
|
||||
mask = normalize_min_max(mask.clone(), new_min=min_float_val, new_max=max_float_val)
|
||||
else:
|
||||
raise ValueError(f"scaling '{scaling}' not recognized.")
|
||||
return MultivalDynamicNode.create_multival(self, mask_optional=mask)
|
||||
|
||||
|
||||
class MultivalDynamicFloatInputNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -94,36 +134,3 @@ class MultivalFloatNode:
|
||||
|
||||
def create_multival(self, float_val: Union[float, list[float]]=None):
|
||||
return MultivalDynamicNode.create_multival(self, float_val=float_val)
|
||||
|
||||
|
||||
class MultivalScaledMaskNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"min_float_val": ("FLOAT", {"default": 0.0, "min": 0.0, "step": 0.001}),
|
||||
"max_float_val": ("FLOAT", {"default": 1.0, "min": 0.0, "step": 0.001}),
|
||||
"mask": ("MASK",),
|
||||
},
|
||||
"optional": {
|
||||
"scaling": (ScaleType.LIST,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MULTIVAL",)
|
||||
CATEGORY = "Animate Diff 🎭🅐🅓/multival"
|
||||
FUNCTION = "create_multival"
|
||||
|
||||
def create_multival(self, min_float_val: float, max_float_val: float, mask: Tensor, scaling: str=ScaleType.ABSOLUTE):
|
||||
if isinstance(min_float_val, Iterable):
|
||||
raise ValueError(f"min_float_val must be type float (no lists allowed here), not {type(min_float_val).__name__}.")
|
||||
if isinstance(max_float_val, Iterable):
|
||||
raise ValueError(f"max_float_val must be type float (no lists allowed here), not {type(max_float_val).__name__}.")
|
||||
|
||||
if scaling == ScaleType.ABSOLUTE:
|
||||
mask = linear_conversion(mask.clone(), new_min=min_float_val, new_max=max_float_val)
|
||||
elif scaling == ScaleType.RELATIVE:
|
||||
mask = normalize_min_max(mask.clone(), new_min=min_float_val, new_max=max_float_val)
|
||||
else:
|
||||
raise ValueError(f"scaling '{scaling}' not recognized.")
|
||||
return MultivalDynamicNode.create_multival(self, mask_optional=mask)
|
||||
|
||||
+65
-10
@@ -1,8 +1,11 @@
|
||||
from typing import Union
|
||||
from torch import Tensor
|
||||
|
||||
from .freeinit import FreeInitFilter
|
||||
from .sample_settings import FreeInitOptions, IterationOptions, NoiseLayerAdd, NoiseLayerAddWeighted, NoiseLayerGroup, NoiseLayerReplace, NoiseLayerType, SeedNoiseGeneration, SampleSettings
|
||||
from .utils_model import BIGMIN, BIGMAX
|
||||
from .sample_settings import (FreeInitOptions, IterationOptions,
|
||||
NoiseLayerAdd, NoiseLayerAddWeighted, NoiseLayerGroup, NoiseLayerReplace, NoiseLayerType,
|
||||
SeedNoiseGeneration, SampleSettings, CustomCFGKeyframeGroup, CustomCFGKeyframe)
|
||||
from .utils_model import BIGMIN, BIGMAX, SigmaSchedule
|
||||
|
||||
|
||||
class SampleSettingsNode:
|
||||
@@ -20,18 +23,22 @@ class SampleSettingsNode:
|
||||
"iteration_opts": ("ITERATION_OPTS",),
|
||||
"seed_override": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "forceInput": True}),
|
||||
"adapt_denoise_steps": ("BOOLEAN", {"default": False},),
|
||||
"custom_cfg": ("CUSTOM_CFG",),
|
||||
"sigma_schedule": ("SIGMA_SCHEDULE",),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RETURN_TYPES = ("SAMPLE_SETTINGS",)
|
||||
RETURN_NAMES = ("settings",)
|
||||
CATEGORY = "Animate Diff 🎭🅐🅓"
|
||||
FUNCTION = "create_settings"
|
||||
|
||||
def create_settings(self, batch_offset: int, noise_type: str, seed_gen: str, seed_offset: int, noise_layers: NoiseLayerGroup=None,
|
||||
iteration_opts: IterationOptions=None, seed_override: int=None, adapt_denoise_steps=False):
|
||||
iteration_opts: IterationOptions=None, seed_override: int=None, adapt_denoise_steps=False,
|
||||
custom_cfg: CustomCFGKeyframeGroup=None, sigma_schedule: SigmaSchedule=None):
|
||||
sampling_settings = SampleSettings(batch_offset=batch_offset, noise_type=noise_type, seed_gen=seed_gen, seed_offset=seed_offset, noise_layers=noise_layers,
|
||||
iteration_opts=iteration_opts, seed_override=seed_override, adapt_denoise_steps=adapt_denoise_steps)
|
||||
iteration_opts=iteration_opts, seed_override=seed_override, adapt_denoise_steps=adapt_denoise_steps,
|
||||
custom_cfg=custom_cfg, sigma_schedule=sigma_schedule)
|
||||
return (sampling_settings,)
|
||||
|
||||
|
||||
@@ -51,7 +58,7 @@ class NoiseLayerReplaceNode:
|
||||
"seed_override": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RETURN_TYPES = ("NOISE_LAYERS",)
|
||||
CATEGORY = "Animate Diff 🎭🅐🅓/noise layers"
|
||||
FUNCTION = "create_layers"
|
||||
@@ -86,7 +93,7 @@ class NoiseLayerAddNode:
|
||||
"seed_override": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RETURN_TYPES = ("NOISE_LAYERS",)
|
||||
CATEGORY = "Animate Diff 🎭🅐🅓/noise layers"
|
||||
FUNCTION = "create_layers"
|
||||
@@ -124,7 +131,7 @@ class NoiseLayerAddWeightedNode:
|
||||
"seed_override": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RETURN_TYPES = ("NOISE_LAYERS",)
|
||||
CATEGORY = "Animate Diff 🎭🅐🅓/noise layers"
|
||||
FUNCTION = "create_layers"
|
||||
@@ -156,7 +163,7 @@ class IterationOptionsNode:
|
||||
"iter_seed_offset": ("INT", {"default": 0, "min": BIGMIN, "max": BIGMAX}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RETURN_TYPES = ("ITERATION_OPTS",)
|
||||
CATEGORY = "Animate Diff 🎭🅐🅓/iteration opts"
|
||||
FUNCTION = "create_iter_opts"
|
||||
@@ -185,7 +192,7 @@ class FreeInitOptionsNode:
|
||||
"iter_seed_offset": ("INT", {"default": 1, "min": BIGMIN, "max": BIGMAX}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RETURN_TYPES = ("ITERATION_OPTS",)
|
||||
CATEGORY = "Animate Diff 🎭🅐🅓/iteration opts"
|
||||
FUNCTION = "create_iter_opts"
|
||||
@@ -198,3 +205,51 @@ class FreeInitOptionsNode:
|
||||
filter=filter, d_s=d_s, d_t=d_t, n=n_butterworth, init_type=init_type,
|
||||
iter_batch_offset=iter_batch_offset, iter_seed_offset=iter_seed_offset)
|
||||
return (iter_opts,)
|
||||
|
||||
|
||||
class CustomCFGNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"cfg_multival": ("MULTIVAL",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CUSTOM_CFG",)
|
||||
CATEGORY = "Animate Diff 🎭🅐🅓/sample settings"
|
||||
FUNCTION = "create_custom_cfg"
|
||||
|
||||
def create_custom_cfg(self, cfg_multival: Union[float, Tensor]):
|
||||
keyframe = CustomCFGKeyframe(cfg_multival=cfg_multival)
|
||||
cfg_custom = CustomCFGKeyframeGroup()
|
||||
cfg_custom.add(keyframe)
|
||||
return (cfg_custom,)
|
||||
|
||||
|
||||
class CustomCFGKeyframeNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"cfg_multival": ("MULTIVAL",),
|
||||
"start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}),
|
||||
"guarantee_steps": ("INT", {"default": 1, "min": 0, "max": BIGMAX}),
|
||||
},
|
||||
"optional": {
|
||||
"prev_custom_cfg": ("CUSTOM_CFG",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CUSTOM_CFG",)
|
||||
CATEGORY = "Animate Diff 🎭🅐🅓/sample settings"
|
||||
FUNCTION = "create_custom_cfg"
|
||||
|
||||
def create_custom_cfg(self, cfg_multival: Union[float, Tensor], start_percent: float=0.0, guarantee_steps: int=1,
|
||||
prev_custom_cfg: CustomCFGKeyframeGroup=None):
|
||||
if not prev_custom_cfg:
|
||||
prev_custom_cfg = CustomCFGKeyframeGroup()
|
||||
prev_custom_cfg = prev_custom_cfg.clone()
|
||||
keyframe = CustomCFGKeyframe(cfg_multival=cfg_multival, start_percent=start_percent, guarantee_steps=guarantee_steps)
|
||||
prev_custom_cfg.add(keyframe)
|
||||
return (prev_custom_cfg,)
|
||||
|
||||
+120
-2
@@ -1,13 +1,17 @@
|
||||
from collections.abc import Iterable
|
||||
from typing import Union
|
||||
import torch
|
||||
from torch import Tensor
|
||||
|
||||
import comfy.sample
|
||||
import comfy.samplers
|
||||
from comfy.model_patcher import ModelPatcher
|
||||
from comfy.model_base import BaseModel
|
||||
|
||||
from . import freeinit
|
||||
from .context import ContextOptions, ContextOptionsGroup
|
||||
from .utils_model import SigmaSchedule
|
||||
from .utils_motion import extend_to_batch_size, get_sorted_list_via_attr, prepare_mask_batch
|
||||
from .logger import logger
|
||||
|
||||
|
||||
@@ -48,7 +52,8 @@ class NoiseNormalize:
|
||||
|
||||
class SampleSettings:
|
||||
def __init__(self, batch_offset: int=0, noise_type: str=None, seed_gen: str=None, seed_offset: int=0, noise_layers: 'NoiseLayerGroup'=None,
|
||||
iteration_opts=None, seed_override:int=None, negative_cond_flipflop=False, adapt_denoise_steps: bool=False):
|
||||
iteration_opts=None, seed_override:int=None, negative_cond_flipflop=False, adapt_denoise_steps: bool=False,
|
||||
custom_cfg: 'CustomCFGKeyframeGroup'=None, sigma_schedule: SigmaSchedule=None):
|
||||
self.batch_offset = batch_offset
|
||||
self.noise_type = noise_type if noise_type is not None else NoiseLayerType.DEFAULT
|
||||
self.seed_gen = seed_gen if seed_gen is not None else SeedNoiseGeneration.COMFY
|
||||
@@ -58,6 +63,8 @@ class SampleSettings:
|
||||
self.seed_override = seed_override
|
||||
self.negative_cond_flipflop = negative_cond_flipflop
|
||||
self.adapt_denoise_steps = adapt_denoise_steps
|
||||
self.custom_cfg = custom_cfg.clone() if custom_cfg else custom_cfg
|
||||
self.sigma_schedule = sigma_schedule
|
||||
|
||||
def prepare_noise(self, seed: int, latents: Tensor, noise: Tensor, extra_seed_offset=0, extra_args:dict={}, force_create_noise=True):
|
||||
if self.seed_override is not None:
|
||||
@@ -82,10 +89,18 @@ class SampleSettings:
|
||||
# noise prepared now
|
||||
return noise
|
||||
|
||||
def pre_run(self, model: ModelPatcher):
|
||||
if self.custom_cfg is not None:
|
||||
self.custom_cfg.reset()
|
||||
|
||||
def cleanup(self):
|
||||
if self.custom_cfg is not None:
|
||||
self.custom_cfg.reset()
|
||||
|
||||
def clone(self):
|
||||
return SampleSettings(batch_offset=self.batch_offset, noise_type=self.noise_type, seed_gen=self.seed_gen, seed_offset=self.seed_offset,
|
||||
noise_layers=self.noise_layers.clone(), iteration_opts=self.iteration_opts, seed_override=self.seed_override,
|
||||
negative_cond_flipflop=self.negative_cond_flipflop, adapt_denoise_steps=self.adapt_denoise_steps)
|
||||
negative_cond_flipflop=self.negative_cond_flipflop, adapt_denoise_steps=self.adapt_denoise_steps, custom_cfg=self.custom_cfg, sigma_schedule=self.sigma_schedule)
|
||||
|
||||
|
||||
class NoiseLayer:
|
||||
@@ -435,3 +450,106 @@ class FreeInitOptions(IterationOptions):
|
||||
return cached_latents, noised_latents
|
||||
else:
|
||||
raise ValueError(f"FreeInit init_type '{self.init_type}' is not recognized.")
|
||||
|
||||
|
||||
class CustomCFGKeyframe:
|
||||
def __init__(self, cfg_multival: Union[float, Tensor], start_percent=0.0, guarantee_steps=1):
|
||||
self.cfg_multival = cfg_multival
|
||||
# scheduling
|
||||
self.start_percent = float(start_percent)
|
||||
self.start_t = 999999999.9
|
||||
self.guarantee_steps = guarantee_steps
|
||||
|
||||
def clone(self):
|
||||
c = CustomCFGKeyframe(cfg_multival=self.cfg_multival,
|
||||
start_percent=self.start_percent, guarantee_steps=self.guarantee_steps)
|
||||
c.start_t = self.start_t
|
||||
return c
|
||||
|
||||
|
||||
class CustomCFGKeyframeGroup:
|
||||
def __init__(self):
|
||||
self.keyframes: list[CustomCFGKeyframe] = []
|
||||
self._current_keyframe: CustomCFGKeyframe = None
|
||||
self._current_used_steps: int = 0
|
||||
self._current_index: int = 0
|
||||
|
||||
def reset(self):
|
||||
self._current_keyframe = None
|
||||
self._current_used_steps = 0
|
||||
self._current_index = 0
|
||||
self._set_first_as_current()
|
||||
|
||||
def add(self, keyframe: CustomCFGKeyframe):
|
||||
# add to end of list, then sort
|
||||
self.keyframes.append(keyframe)
|
||||
self.keyframes = get_sorted_list_via_attr(self.keyframes, "start_percent")
|
||||
self._set_first_as_current()
|
||||
|
||||
def _set_first_as_current(self):
|
||||
if len(self.keyframes) > 0:
|
||||
self._current_keyframe = self.keyframes[0]
|
||||
else:
|
||||
self._current_keyframe = None
|
||||
|
||||
def has_index(self, index: int) -> int:
|
||||
return index >=0 and index < len(self.keyframes)
|
||||
|
||||
def is_empty(self) -> bool:
|
||||
return len(self.keyframes) == 0
|
||||
|
||||
def clone(self):
|
||||
cloned = CustomCFGKeyframeGroup()
|
||||
for keyframe in self.keyframes:
|
||||
cloned.keyframes.append(keyframe)
|
||||
cloned._set_first_as_current()
|
||||
return cloned
|
||||
|
||||
def initialize_timesteps(self, model: BaseModel):
|
||||
for keyframe in self.keyframes:
|
||||
keyframe.start_t = model.model_sampling.percent_to_sigma(keyframe.start_percent)
|
||||
|
||||
def prepare_current_keyframe(self, t: Tensor):
|
||||
curr_t: float = t[0]
|
||||
prev_index = self._current_index
|
||||
# if met guaranteed steps, look for next keyframe in case need to switch
|
||||
if self._current_used_steps >= self._current_keyframe.guarantee_steps:
|
||||
# if has next index, loop through and see if need t oswitch
|
||||
if self.has_index(self._current_index+1):
|
||||
for i in range(self._current_index+1, len(self.keyframes)):
|
||||
eval_c = self.keyframes[i]
|
||||
# check if start_t is greater or equal to curr_t
|
||||
# NOTE: t is in terms of sigmas, not percent, so bigger number = earlier step in sampling
|
||||
if eval_c.start_t >= curr_t:
|
||||
self._current_index = i
|
||||
self._current_keyframe = eval_c
|
||||
self._current_used_steps = 0
|
||||
# if guarantee_steps greater than zero, stop searching for other keyframes
|
||||
if self._current_keyframe.guarantee_steps > 0:
|
||||
break
|
||||
# if eval_c is outside the percent range, stop looking further
|
||||
else: break
|
||||
# update steps current context is used
|
||||
self._current_used_steps += 1
|
||||
|
||||
def patch_model(self, model: ModelPatcher) -> ModelPatcher:
|
||||
def evolved_custom_cfg(args):
|
||||
cond: Tensor = args["cond"]
|
||||
uncond: Tensor = args["uncond"]
|
||||
# cond scale is based purely off of CustomCFG - cond_scale input in sampler is ignored!
|
||||
cond_scale = self.cfg_multival
|
||||
if isinstance(cond_scale, Tensor):
|
||||
cond_scale = prepare_mask_batch(cond_scale.to(cond.dtype).to(cond.device), cond.shape)
|
||||
cond_scale = extend_to_batch_size(cond_scale, cond.shape[0])
|
||||
return uncond + (cond - uncond) * cond_scale
|
||||
|
||||
model = model.clone()
|
||||
model.set_model_sampler_cfg_function(evolved_custom_cfg)
|
||||
return model
|
||||
|
||||
# properties shadow those of CustomCFGKeyframe
|
||||
@property
|
||||
def cfg_multival(self):
|
||||
if self._current_keyframe != None:
|
||||
return self._current_keyframe.cfg_multival
|
||||
return None
|
||||
|
||||
+57
-21
@@ -16,7 +16,7 @@ from comfy.controlnet import ControlBase
|
||||
import comfy.ops
|
||||
|
||||
from .context import ContextFuseMethod, ContextSchedules, get_context_weights, get_context_windows
|
||||
from .sample_settings import IterationOptions, SeedNoiseGeneration, prepare_mask_ad
|
||||
from .sample_settings import IterationOptions, SampleSettings, SeedNoiseGeneration, prepare_mask_ad
|
||||
from .utils_model import ModelTypeSD, wrap_function_to_inject_xformers_bug_info
|
||||
from .model_injection import InjectionParams, ModelPatcherAndInjector, MotionModelGroup, MotionModelPatcher
|
||||
from .motion_module_ad import AnimateDiffFormat, AnimateDiffInfo, AnimateDiffVersion, VanillaTemporalModule
|
||||
@@ -30,6 +30,7 @@ class AnimateDiffHelper_GlobalState:
|
||||
def __init__(self):
|
||||
self.motion_models: MotionModelGroup = None
|
||||
self.params: InjectionParams = None
|
||||
self.sample_settings: SampleSettings = None
|
||||
self.reset()
|
||||
|
||||
def initialize(self, model):
|
||||
@@ -40,6 +41,8 @@ class AnimateDiffHelper_GlobalState:
|
||||
self.motion_models.initialize_timesteps(model)
|
||||
if self.params.context_options is not None:
|
||||
self.params.context_options.initialize_timesteps(model)
|
||||
if self.sample_settings.custom_cfg is not None:
|
||||
self.sample_settings.custom_cfg.initialize_timesteps(model)
|
||||
|
||||
def reset(self):
|
||||
self.initialized = False
|
||||
@@ -53,6 +56,9 @@ class AnimateDiffHelper_GlobalState:
|
||||
if self.params is not None:
|
||||
del self.params
|
||||
self.params = None
|
||||
if self.sample_settings is not None:
|
||||
del self.sample_settings
|
||||
self.sample_settings = None
|
||||
|
||||
def update_with_inject_params(self, params: InjectionParams):
|
||||
self.params = params
|
||||
@@ -147,8 +153,9 @@ def get_additional_models_factory(orig_get_additional_models: Callable, motion_m
|
||||
|
||||
def apply_params_to_motion_models(motion_models: MotionModelGroup, params: InjectionParams):
|
||||
params = params.clone()
|
||||
if params.context_options.context_schedule == ContextSchedules.VIEW_AS_CONTEXT:
|
||||
params.context_options._current_context.context_length = params.full_length
|
||||
for context in params.context_options.contexts:
|
||||
if context.context_schedule == ContextSchedules.VIEW_AS_CONTEXT:
|
||||
context.context_length = params.full_length
|
||||
# TODO: check (and message) should be different based on use_on_equal_length setting
|
||||
if params.context_options.context_length:
|
||||
pass
|
||||
@@ -167,7 +174,7 @@ def apply_params_to_motion_models(motion_models: MotionModelGroup, params: Injec
|
||||
# if no context_length, treat video length as intended AD frame window
|
||||
if not params.context_options.context_length:
|
||||
for motion_model in motion_models.models:
|
||||
if params.full_length > motion_model.model.encoding_max_len:
|
||||
if not motion_model.model.is_length_valid_for_encoding_max_len(params.full_length):
|
||||
raise ValueError(f"Without a context window, AnimateDiff model {motion_model.model.mm_info.mm_name} has upper limit of {motion_model.model.encoding_max_len} frames, but received {params.full_length} latents.")
|
||||
motion_models.set_video_length(params.full_length, params.full_length)
|
||||
# otherwise, treat context_length as intended AD frame window
|
||||
@@ -175,7 +182,7 @@ def apply_params_to_motion_models(motion_models: MotionModelGroup, params: Injec
|
||||
for motion_model in motion_models.models:
|
||||
view_options = params.context_options.view_options
|
||||
context_length = view_options.context_length if view_options else params.context_options.context_length
|
||||
if context_length > motion_model.model.encoding_max_len:
|
||||
if not motion_model.model.is_length_valid_for_encoding_max_len(context_length):
|
||||
raise ValueError(f"AnimateDiff model {motion_model.model.mm_info.mm_name} has upper limit of {motion_model.model.encoding_max_len} frames for a context window, but received context length of {params.context_options.context_length}.")
|
||||
motion_models.set_video_length(params.context_options.context_length, params.full_length)
|
||||
# inject model
|
||||
@@ -202,10 +209,10 @@ class FunctionInjectionHolder:
|
||||
if params.unlimited_area_hack:
|
||||
model.model.memory_required = unlimited_memory_required
|
||||
if model.motion_models is not None:
|
||||
# only apply groupnorm hack if not [v3 or (AnimateDiff SD1.5 and v2 and should apply v2 properly)]
|
||||
# only apply groupnorm hack if not [v3 or ([not Hotshot] and SD1.5 and v2 and apply_v2_properly)]
|
||||
info: AnimateDiffInfo = model.motion_models[0].model.mm_info
|
||||
if not (info.mm_version == AnimateDiffVersion.V3 or (info.mm_format == AnimateDiffFormat.ANIMATEDIFF and info.sd_type == ModelTypeSD.SD1_5 and
|
||||
info.mm_version == AnimateDiffVersion.V2 and params.apply_v2_properly)):
|
||||
if not (info.mm_version == AnimateDiffVersion.V3 or
|
||||
(info.mm_format not in [AnimateDiffFormat.HOTSHOTXL] and info.sd_type == ModelTypeSD.SD1_5 and info.mm_version == AnimateDiffVersion.V2 and params.apply_v2_properly)):
|
||||
torch.nn.GroupNorm.forward = groupnorm_mm_factory(params)
|
||||
comfy.ops.manual_cast.GroupNorm.forward_comfy_cast_weights = groupnorm_mm_factory(params, manual_cast=True)
|
||||
# if mps device (Apple Silicon), disable batched conds to avoid black images with groupnorm hack
|
||||
@@ -245,6 +252,8 @@ def motion_sample_factory(orig_comfy_sample: Callable, is_custom: bool=False) ->
|
||||
cached_noise = None
|
||||
function_injections = FunctionInjectionHolder()
|
||||
try:
|
||||
if model.sample_settings.custom_cfg is not None:
|
||||
model = model.sample_settings.custom_cfg.patch_model(model)
|
||||
# clone params from model
|
||||
params = model.motion_injection_params.clone()
|
||||
# get amount of latents passed in, and store in params
|
||||
@@ -279,6 +288,7 @@ def motion_sample_factory(orig_comfy_sample: Callable, is_custom: bool=False) ->
|
||||
ADGS.current_step = ADGS.start_step + step + 1
|
||||
kwargs["callback"] = ad_callback
|
||||
ADGS.motion_models = model.motion_models
|
||||
ADGS.sample_settings = model.sample_settings
|
||||
|
||||
# apply adapt_denoise_steps
|
||||
args = list(args)
|
||||
@@ -331,6 +341,8 @@ def motion_sample_factory(orig_comfy_sample: Callable, is_custom: bool=False) ->
|
||||
|
||||
if model.motion_models is not None:
|
||||
model.motion_models.pre_run(model)
|
||||
if model.sample_settings is not None:
|
||||
model.sample_settings.pre_run(model)
|
||||
latents = wrap_function_to_inject_xformers_bug_info(orig_comfy_sample)(model, noise, *args, **kwargs)
|
||||
return latents
|
||||
finally:
|
||||
@@ -352,8 +364,11 @@ def evolved_sampling_function(model, x, timestep, uncond, cond, cond_scale, mode
|
||||
ADGS.motion_models.prepare_current_keyframe(t=timestep)
|
||||
if ADGS.params.context_options is not None:
|
||||
ADGS.params.context_options.prepare_current_context(t=timestep)
|
||||
if ADGS.sample_settings.custom_cfg is not None:
|
||||
ADGS.sample_settings.custom_cfg.prepare_current_keyframe(t=timestep)
|
||||
|
||||
if math.isclose(cond_scale, 1.0) and model_options.get("disable_cfg1_optimization", False) == False:
|
||||
# never use cfg1 optimization if using custom_cfg (since can have timesteps and such)
|
||||
if ADGS.sample_settings.custom_cfg is None and math.isclose(cond_scale, 1.0) and model_options.get("disable_cfg1_optimization", False) == False:
|
||||
uncond_ = None
|
||||
else:
|
||||
uncond_ = uncond
|
||||
@@ -454,6 +469,7 @@ def sliding_calc_cond_uncond_batch(model, cond, uncond, x_in: Tensor, timestep,
|
||||
cond_final = torch.zeros_like(x_in)
|
||||
uncond_final = torch.zeros_like(x_in)
|
||||
out_count_final = torch.zeros((x_in.shape[0], 1, 1, 1), device=x_in.device)
|
||||
bias_final = [0.0] * x_in.shape[0]
|
||||
|
||||
# perform calc_cond_uncond_batch per context window
|
||||
for ctx_idxs in context_windows:
|
||||
@@ -477,16 +493,36 @@ def sliding_calc_cond_uncond_batch(model, cond, uncond, x_in: Tensor, timestep,
|
||||
|
||||
sub_cond_out, sub_uncond_out = comfy.samplers.calc_cond_uncond_batch(model, sub_cond, sub_uncond, sub_x, sub_timestep, model_options)
|
||||
|
||||
# add conds and counts based on weights of fuse method
|
||||
weights = get_context_weights(len(ctx_idxs), ADGS.params.context_options.fuse_method) * batched_conds
|
||||
weights_tensor = torch.Tensor(weights).to(device=x_in.device).unsqueeze(-1).unsqueeze(-1).unsqueeze(-1)
|
||||
cond_final[full_idxs] += sub_cond_out * weights_tensor
|
||||
uncond_final[full_idxs] += sub_uncond_out * weights_tensor
|
||||
out_count_final[full_idxs] += weights_tensor
|
||||
if ADGS.params.context_options.fuse_method == ContextFuseMethod.RELATIVE:
|
||||
full_length = ADGS.params.full_length
|
||||
for pos, idx in enumerate(ctx_idxs):
|
||||
# bias is the influence of a specific index in relation to the whole context window
|
||||
bias = 1 - abs(idx - (ctx_idxs[0] + ctx_idxs[-1]) / 2) / ((ctx_idxs[-1] - ctx_idxs[0] + 1e-2) / 2)
|
||||
bias = max(1e-2, bias)
|
||||
# take weighted average relative to total bias of current idx
|
||||
# and account for batched_conds
|
||||
for n in range(batched_conds):
|
||||
bias_total = bias_final[(full_length*n)+idx]
|
||||
prev_weight = (bias_total / (bias_total + bias))
|
||||
new_weight = (bias / (bias_total + bias))
|
||||
cond_final[(full_length*n)+idx] = cond_final[(full_length*n)+idx] * prev_weight + sub_cond_out[(full_length*n)+pos] * new_weight
|
||||
uncond_final[(full_length*n)+idx] = uncond_final[(full_length*n)+idx] * prev_weight + sub_uncond_out[(full_length*n)+pos] * new_weight
|
||||
bias_final[(full_length*n)+idx] = bias_total + bias
|
||||
else:
|
||||
# add conds and counts based on weights of fuse method
|
||||
weights = get_context_weights(len(ctx_idxs), ADGS.params.context_options.fuse_method) * batched_conds
|
||||
weights_tensor = torch.Tensor(weights).to(device=x_in.device).unsqueeze(-1).unsqueeze(-1).unsqueeze(-1)
|
||||
cond_final[full_idxs] += sub_cond_out * weights_tensor
|
||||
uncond_final[full_idxs] += sub_uncond_out * weights_tensor
|
||||
out_count_final[full_idxs] += weights_tensor
|
||||
|
||||
|
||||
# normalize cond and uncond via division by context usage counts
|
||||
cond_final /= out_count_final
|
||||
uncond_final /= out_count_final
|
||||
del out_count_final
|
||||
return cond_final, uncond_final
|
||||
if ADGS.params.context_options.fuse_method == ContextFuseMethod.RELATIVE:
|
||||
# already normalized, so return as is
|
||||
del out_count_final
|
||||
return cond_final, uncond_final
|
||||
else:
|
||||
# normalize cond and uncond via division by context usage counts
|
||||
cond_final /= out_count_final
|
||||
uncond_final /= out_count_final
|
||||
del out_count_final
|
||||
return cond_final, uncond_final
|
||||
|
||||
+175
-31
@@ -3,72 +3,169 @@ from pathlib import Path
|
||||
from typing import Callable, Union
|
||||
from collections.abc import Iterable
|
||||
from time import time
|
||||
import copy
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
|
||||
import folder_paths
|
||||
from comfy.model_base import SD21UNCLIP, SDXL, BaseModel, SDXLRefiner, SVD_img2vid, model_sampling
|
||||
from comfy.model_base import SD21UNCLIP, SDXL, BaseModel, SDXLRefiner, SVD_img2vid, model_sampling, ModelType
|
||||
from comfy.model_management import xformers_enabled
|
||||
from comfy.model_patcher import ModelPatcher
|
||||
|
||||
import comfy.model_sampling
|
||||
import comfy_extras.nodes_model_advanced
|
||||
|
||||
class IsChangedHelper:
|
||||
def __init__(self):
|
||||
self.val = 0
|
||||
|
||||
def no_change(self):
|
||||
return self.val
|
||||
|
||||
def change(self):
|
||||
self.val = (self.val + 1) % 100
|
||||
|
||||
BIGMIN = -(2**53-1)
|
||||
BIGMAX = (2**53-1)
|
||||
|
||||
|
||||
class ModelSamplingConfig:
|
||||
def __init__(self, beta_schedule: str):
|
||||
def __init__(self, beta_schedule: str, linear_start: float=None, linear_end: float=None):
|
||||
self.sampling_settings = {"beta_schedule": beta_schedule}
|
||||
if linear_start is not None:
|
||||
self.sampling_settings["linear_start"] = linear_start
|
||||
if linear_end is not None:
|
||||
self.sampling_settings["linear_end"] = linear_end
|
||||
self.beta_schedule = beta_schedule # keeping this for backwards compatibility
|
||||
|
||||
|
||||
class ModelSamplingType:
|
||||
EPS = "eps"
|
||||
V_PREDICTION = "v_prediction"
|
||||
LCM = "lcm"
|
||||
|
||||
_NON_LCM_LIST = [EPS, V_PREDICTION]
|
||||
_FULL_LIST = [EPS, V_PREDICTION, LCM]
|
||||
|
||||
MAP = {
|
||||
EPS: ModelType.EPS,
|
||||
V_PREDICTION: ModelType.V_PREDICTION,
|
||||
LCM: comfy_extras.nodes_model_advanced.LCM,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_alias(cls, alias: str):
|
||||
return cls.MAP[alias]
|
||||
|
||||
|
||||
def factory_model_sampling_discrete_distilled(original_timesteps=50):
|
||||
class ModelSamplingDiscreteDistilledEvolved(comfy_extras.nodes_model_advanced.ModelSamplingDiscreteDistilled):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.original_timesteps = original_timesteps # normal LCM has 50
|
||||
super().__init__(*args, **kwargs)
|
||||
return ModelSamplingDiscreteDistilledEvolved
|
||||
|
||||
|
||||
# based on code in comfy_extras/nodes_model_advanced.py
|
||||
def evolved_model_sampling(model_config: ModelSamplingConfig, model_type: ModelType, alias: str, original_timesteps: int=None):
|
||||
# if LCM, need to handle manually
|
||||
if BetaSchedules.is_lcm(alias) or original_timesteps is not None:
|
||||
sampling_type = comfy_extras.nodes_model_advanced.LCM
|
||||
if original_timesteps is not None:
|
||||
sampling_base = factory_model_sampling_discrete_distilled(original_timesteps=original_timesteps)
|
||||
elif alias == BetaSchedules.LCM_100:
|
||||
sampling_base = factory_model_sampling_discrete_distilled(original_timesteps=100)
|
||||
elif alias == BetaSchedules.LCM_25:
|
||||
sampling_base = factory_model_sampling_discrete_distilled(original_timesteps=25)
|
||||
else:
|
||||
sampling_base = comfy_extras.nodes_model_advanced.ModelSamplingDiscreteDistilled
|
||||
class ModelSamplingAdvancedEvolved(sampling_base, sampling_type):
|
||||
pass
|
||||
# NOTE: if I want to support zsnr, this is where I would add that code
|
||||
return ModelSamplingAdvancedEvolved(model_config)
|
||||
# otherwise, use vanilla model_sampling function
|
||||
return model_sampling(model_config, model_type)
|
||||
|
||||
|
||||
class BetaSchedules:
|
||||
AUTOSELECT = "autoselect"
|
||||
SQRT_LINEAR = "sqrt_linear (AnimateDiff)"
|
||||
LINEAR_ADXL = "linear (AnimateDiff-SDXL)"
|
||||
LINEAR = "linear (HotshotXL/default)"
|
||||
AVG_LINEAR_SQRT_LINEAR = "avg(sqrt_linear,linear)"
|
||||
LCM_AVG_LINEAR_SQRT_LINEAR = "lcm avg(sqrt_linear,linear)"
|
||||
LCM = "lcm"
|
||||
LCM_100 = "lcm[100_ots]"
|
||||
LCM_25 = "lcm[25_ots]"
|
||||
LCM_SQRT_LINEAR = "lcm >> sqrt_linear"
|
||||
USE_EXISTING = "use existing"
|
||||
SQRT = "sqrt"
|
||||
COSINE = "cosine"
|
||||
SQUAREDCOS_CAP_V2 = "squaredcos_cap_v2"
|
||||
RAW_LINEAR = "linear"
|
||||
RAW_SQRT_LINEAR = "sqrt_linear"
|
||||
|
||||
ALIAS_LIST = [AUTOSELECT, SQRT_LINEAR, LINEAR_ADXL, LINEAR,
|
||||
USE_EXISTING, SQRT, COSINE, SQUAREDCOS_CAP_V2]
|
||||
RAW_BETA_SCHEDULE_LIST = [RAW_LINEAR, RAW_SQRT_LINEAR, SQRT, COSINE, SQUAREDCOS_CAP_V2]
|
||||
|
||||
ALIAS_LCM_LIST = [LCM, LCM_100, LCM_25, LCM_SQRT_LINEAR]
|
||||
|
||||
ALIAS_ACTIVE_LIST = [SQRT_LINEAR, LINEAR_ADXL, LINEAR, AVG_LINEAR_SQRT_LINEAR, LCM_AVG_LINEAR_SQRT_LINEAR, LCM, LCM_100, LCM_SQRT_LINEAR, # LCM_25 is purposely omitted
|
||||
SQRT, COSINE, SQUAREDCOS_CAP_V2]
|
||||
|
||||
ALIAS_LIST = [AUTOSELECT, USE_EXISTING] + ALIAS_ACTIVE_LIST
|
||||
|
||||
|
||||
|
||||
ALIAS_MAP = {
|
||||
SQRT_LINEAR: "sqrt_linear",
|
||||
LINEAR_ADXL: "linear", # also linear, but has different linear_end (0.020)
|
||||
LINEAR: "linear",
|
||||
LCM_100: "linear", # distilled, 100 original timesteps
|
||||
LCM_25: "linear", # distilled, 25 original timesteps
|
||||
LCM: "linear", # distilled
|
||||
LCM_SQRT_LINEAR: "sqrt_linear", # distilled, sqrt_linear
|
||||
SQRT: "sqrt",
|
||||
COSINE: "cosine",
|
||||
SQUAREDCOS_CAP_V2: "squaredcos_cap_v2",
|
||||
RAW_LINEAR: "linear",
|
||||
RAW_SQRT_LINEAR: "sqrt_linear"
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def is_lcm(cls, alias: str):
|
||||
return alias in cls.ALIAS_LCM_LIST
|
||||
|
||||
@classmethod
|
||||
def to_name(cls, alias: str):
|
||||
return cls.ALIAS_MAP[alias]
|
||||
|
||||
@classmethod
|
||||
def to_config(cls, alias: str) -> ModelSamplingConfig:
|
||||
return ModelSamplingConfig(cls.to_name(alias))
|
||||
|
||||
@classmethod
|
||||
def to_model_sampling(cls, alias: str, model: ModelPatcher):
|
||||
if alias == cls.USE_EXISTING:
|
||||
return None
|
||||
ms_obj = model_sampling(cls.to_config(alias), model_type=model.model.model_type)
|
||||
linear_start = None
|
||||
linear_end = None
|
||||
if alias == cls.LINEAR_ADXL:
|
||||
# uses linear_end=0.020
|
||||
ms_obj._register_schedule(given_betas=None, beta_schedule=cls.to_name(alias), timesteps=1000, linear_start=0.00085, linear_end=0.020, cosine_s=8e-3)
|
||||
linear_end = 0.020
|
||||
return ModelSamplingConfig(cls.to_name(alias), linear_start=linear_start, linear_end=linear_end)
|
||||
|
||||
@classmethod
|
||||
def _to_model_sampling(cls, alias: str, model_type: ModelType, config_override: ModelSamplingConfig=None, original_timesteps: int=None):
|
||||
if alias == cls.USE_EXISTING:
|
||||
return None
|
||||
elif config_override != None:
|
||||
return evolved_model_sampling(config_override, model_type=model_type, alias=alias, original_timesteps=original_timesteps)
|
||||
elif alias == cls.AVG_LINEAR_SQRT_LINEAR:
|
||||
ms_linear = evolved_model_sampling(cls.to_config(cls.LINEAR), model_type=model_type, alias=cls.LINEAR)
|
||||
ms_sqrt_linear = evolved_model_sampling(cls.to_config(cls.SQRT_LINEAR), model_type=model_type, alias=cls.SQRT_LINEAR)
|
||||
avg_sigmas = (ms_linear.sigmas + ms_sqrt_linear.sigmas) / 2
|
||||
ms_linear.set_sigmas(avg_sigmas)
|
||||
return ms_linear
|
||||
elif alias == cls.LCM_AVG_LINEAR_SQRT_LINEAR:
|
||||
ms_linear = evolved_model_sampling(cls.to_config(cls.LCM), model_type=model_type, alias=cls.LCM)
|
||||
ms_sqrt_linear = evolved_model_sampling(cls.to_config(cls.LCM_SQRT_LINEAR), model_type=model_type, alias=cls.LCM_SQRT_LINEAR)
|
||||
avg_sigmas = (ms_linear.sigmas + ms_sqrt_linear.sigmas) / 2
|
||||
ms_linear.set_sigmas(avg_sigmas)
|
||||
return ms_linear
|
||||
# average out the sigmas
|
||||
ms_obj = evolved_model_sampling(cls.to_config(alias), model_type=model_type, alias=alias, original_timesteps=original_timesteps)
|
||||
return ms_obj
|
||||
|
||||
@classmethod
|
||||
def to_model_sampling(cls, alias: str, model: ModelPatcher):
|
||||
return cls._to_model_sampling(alias=alias, model_type=model.model.model_type)
|
||||
|
||||
@staticmethod
|
||||
def get_alias_list_with_first_element(first_element: str):
|
||||
new_list = BetaSchedules.ALIAS_LIST.copy()
|
||||
@@ -77,16 +174,65 @@ class BetaSchedules:
|
||||
return new_list
|
||||
|
||||
|
||||
class BetaScheduleCache:
|
||||
def __init__(self, model: ModelPatcher):
|
||||
self.model_sampling = model.model.model_sampling
|
||||
class SigmaSchedule:
|
||||
def __init__(self, model_sampling: comfy.model_sampling.ModelSamplingDiscrete, model_type: ModelType):
|
||||
self.model_sampling = model_sampling
|
||||
#self.config = config
|
||||
self.model_type = model_type
|
||||
self.original_timesteps = getattr(self.model_sampling, "original_timesteps", None)
|
||||
|
||||
def is_lcm(self):
|
||||
return self.original_timesteps is not None
|
||||
|
||||
def use_cached_beta_schedule_and_clean(self, model: ModelPatcher):
|
||||
model.model.model_sampling = self.model_sampling
|
||||
self.clean()
|
||||
def total_sigmas(self):
|
||||
return len(self.model_sampling.sigmas)
|
||||
|
||||
def clone(self) -> 'SigmaSchedule':
|
||||
new_model_sampling = copy.deepcopy(self.model_sampling)
|
||||
#new_config = copy.deepcopy(self.config)
|
||||
return SigmaSchedule(model_sampling=new_model_sampling, model_type=self.model_type)
|
||||
|
||||
def clean(self):
|
||||
self.model_sampling = None
|
||||
# def clone(self):
|
||||
# pass
|
||||
|
||||
@staticmethod
|
||||
def apply_zsnr(new_model_sampling: comfy.model_sampling.ModelSamplingDiscrete):
|
||||
new_model_sampling.set_sigmas(comfy_extras.nodes_model_advanced.rescale_zero_terminal_snr_sigmas(new_model_sampling.sigmas))
|
||||
|
||||
# def get_lcmified(self, original_timesteps=50, zsnr=False) -> 'SigmaSchedule':
|
||||
# new_model_sampling = evolved_model_sampling(model_config=self.config, model_type=self.model_type, alias=None, original_timesteps=original_timesteps)
|
||||
# if zsnr:
|
||||
# new_model_sampling.set_sigmas(comfy_extras.nodes_model_advanced.rescale_zero_terminal_snr_sigmas(new_model_sampling.sigmas))
|
||||
# return SigmaSchedule(model_sampling=new_model_sampling, config=self.config, model_type=self.model_type, is_lcm=True)
|
||||
|
||||
|
||||
class InterpolationMethod:
|
||||
LINEAR = "linear"
|
||||
EASE_IN = "ease_in"
|
||||
EASE_OUT = "ease_out"
|
||||
EASE_IN_OUT = "ease_in_out"
|
||||
|
||||
_LIST = [LINEAR, EASE_IN, EASE_OUT, EASE_IN_OUT]
|
||||
|
||||
@classmethod
|
||||
def get_weights(cls, num_from: float, num_to: float, length: int, method: str, reverse=False):
|
||||
diff = num_to - num_from
|
||||
if method == cls.LINEAR:
|
||||
weights = torch.linspace(num_from, num_to, length)
|
||||
elif method == cls.EASE_IN:
|
||||
index = torch.linspace(0, 1, length)
|
||||
weights = diff * np.power(index, 2) + num_from
|
||||
elif method == cls.EASE_OUT:
|
||||
index = torch.linspace(0, 1, length)
|
||||
weights = diff * (1 - np.power(1 - index, 2)) + num_from
|
||||
elif method == cls.EASE_IN_OUT:
|
||||
index = torch.linspace(0, 1, length)
|
||||
weights = diff * ((1 - np.cos(index * np.pi)) / 2) + num_from
|
||||
else:
|
||||
raise ValueError(f"Unrecognized interpolation method '{method}'.")
|
||||
if reverse:
|
||||
weights = weights.flip(dims=(0,))
|
||||
return weights
|
||||
|
||||
|
||||
class Folders:
|
||||
@@ -167,8 +313,6 @@ def calculate_model_hash(model: ModelPatcher):
|
||||
m.update(buf.cpu().numpy().view(np.uint8))
|
||||
return m.hexdigest()
|
||||
|
||||
BIGMIN = -(2**63-1)
|
||||
BIGMAX = (2**63-1)
|
||||
|
||||
class ModelTypeSD:
|
||||
SD1_5 = "SD1.5"
|
||||
|
||||
@@ -285,6 +285,7 @@ app.registerExtension({
|
||||
words[v] = {
|
||||
text: v,
|
||||
info: () => new EmbeddingInfoDialog(emb).show("embeddings", emb),
|
||||
use_replacer: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -303,6 +304,7 @@ app.registerExtension({
|
||||
words[v] = {
|
||||
text: v,
|
||||
info: () => new LoraInfoDialog(lora).show("loras", lora),
|
||||
use_replacer: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -576,7 +576,8 @@ export class TextAreaAutoComplete {
|
||||
onclick: () => {
|
||||
this.el.focus();
|
||||
let value = wordInfo.value ?? wordInfo.text;
|
||||
if (TextAreaAutoComplete.replacer) {
|
||||
const use_replacer = wordInfo.use_replacer ?? true;
|
||||
if (TextAreaAutoComplete.replacer && use_replacer) {
|
||||
value = TextAreaAutoComplete.replacer(value);
|
||||
}
|
||||
this.helper.insertAtCursor(value + this.separator, -before.length, wordInfo.caretOffset);
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
|
||||
// Adds mapping of touch events to mouse events for mobile. This isnt great but it is somewhat usable
|
||||
|
||||
app.registerExtension({
|
||||
name: "pysssss.TouchEvents",
|
||||
setup() {
|
||||
let touchStart = null;
|
||||
let touchType = 0;
|
||||
|
||||
function fireEvent(originalEvent, type) {
|
||||
const fakeEvent = document.createEvent("MouseEvent");
|
||||
const touch = originalEvent.changedTouches[0];
|
||||
fakeEvent.initMouseEvent(
|
||||
type,
|
||||
true,
|
||||
true,
|
||||
window,
|
||||
1,
|
||||
touch.screenX,
|
||||
touch.screenY,
|
||||
touch.clientX,
|
||||
touch.clientY,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
null
|
||||
);
|
||||
|
||||
touch.target.dispatchEvent(fakeEvent);
|
||||
if (fakeEvent.defaultPrevented) {
|
||||
originalEvent.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener(
|
||||
"touchstart",
|
||||
(e) => {
|
||||
// Support tap as click if it completes within a delay
|
||||
if (touchStart) {
|
||||
clearTimeout(touchStart);
|
||||
}
|
||||
touchStart = setTimeout(() => {
|
||||
touchStart = null;
|
||||
}, 100);
|
||||
|
||||
// Left or right button down
|
||||
touchType = e.touches.length === 1 ? 0 : 2;
|
||||
|
||||
fireEvent(e, "mousedown");
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
document.addEventListener("touchmove", (e) => fireEvent(e, "mousemove"), true);
|
||||
|
||||
document.addEventListener(
|
||||
"touchend",
|
||||
(e) => {
|
||||
const isClick = touchStart;
|
||||
if (isClick) {
|
||||
// We are within the touch start delay so fire this as a click
|
||||
clearTimeout(touchStart);
|
||||
fireEvent(e, "click");
|
||||
}
|
||||
fireEvent(e, "mouseup");
|
||||
touchType = 0;
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -25,8 +25,6 @@ s_param = '-s' if "python_embeded" in sys.executable else ''
|
||||
def get_cuda_home_path():
|
||||
if "CUDA_HOME" in os.environ:
|
||||
return os.environ["CUDA_HOME"]
|
||||
if platform.system() == "Windows":
|
||||
return str(Path(__file__).parent / "nvrtc_dlls") #https://github.com/cupy/cupy/issues/7776
|
||||
import torch
|
||||
torch_lib_path = Path(torch.__file__).parent / "lib"
|
||||
torch_lib_path = str(torch_lib_path.resolve())
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+3
-4
@@ -41,8 +41,7 @@ class AMT_VFI:
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
"cache_in_fp16": ("BOOLEAN", {"default": True})
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +56,7 @@ class AMT_VFI:
|
||||
clear_cache_after_n_frames: typing.SupportsInt = 1,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
cache_in_fp16: bool = True
|
||||
**kwargs
|
||||
):
|
||||
model_path = load_file_from_direct_url(MODEL_TYPE, f"https://huggingface.co/lalala125/AMT/resolve/main/{ckpt_name}")
|
||||
ckpt_config = CKPT_CONFIGS[ckpt_name]
|
||||
@@ -81,7 +80,7 @@ class AMT_VFI:
|
||||
|
||||
args = [interpolation_model]
|
||||
out = generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float16 if cache_in_fp16 else torch.float32)
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
out = padder.unpad(out)
|
||||
out = postprocess_frames(out)
|
||||
return (out,)
|
||||
|
||||
+3
-4
@@ -20,8 +20,7 @@ class CAIN_VFI:
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
"cache_in_fp16": ("BOOLEAN", {"default": True})
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +35,7 @@ class CAIN_VFI:
|
||||
clear_cache_after_n_frames: typing.SupportsInt = 1,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
cache_in_fp16: bool = True
|
||||
**kwargs
|
||||
):
|
||||
from .cain_arch import CAIN
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
@@ -60,6 +59,6 @@ class CAIN_VFI:
|
||||
args = [interpolation_model]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, use_timestep=False, dtype=torch.float16 if cache_in_fp16 else torch.float32)
|
||||
interpolation_states=optional_interpolation_states, use_timestep=False, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
+3
-4
@@ -50,8 +50,7 @@ class EISAI_VFI:
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000}),
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
"cache_in_fp16": ("BOOLEAN", {"default": True})
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +65,7 @@ class EISAI_VFI:
|
||||
clear_cache_after_n_frames = 10,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
cache_in_fp16: bool = True
|
||||
**kwargs
|
||||
):
|
||||
interpolation_model = EISAI(MODEL_FILE_NAMES)
|
||||
interpolation_model.eval().to(get_torch_device())
|
||||
@@ -80,6 +79,6 @@ class EISAI_VFI:
|
||||
args = [interpolation_model, scale]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float16 if cache_in_fp16 else torch.float32)
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
|
||||
+3
-4
@@ -52,8 +52,7 @@ class FILM_VFI:
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000}),
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
"cache_in_fp16": ("BOOLEAN", {"default": True})
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,14 +67,14 @@ class FILM_VFI:
|
||||
clear_cache_after_n_frames = 10,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
cache_in_fp16: bool = True
|
||||
**kwargs
|
||||
):
|
||||
interpolation_states = optional_interpolation_states
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
model = torch.jit.load(model_path, map_location='cpu')
|
||||
model.eval()
|
||||
model = model.to(DEVICE)
|
||||
dtype = torch.float16 if cache_in_fp16 else torch.float32
|
||||
dtype = torch.float32
|
||||
|
||||
frames = preprocess_frames(frames)
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache = 0
|
||||
|
||||
+3
-4
@@ -37,8 +37,7 @@ class FLAVR_VFI:
|
||||
"duplicate_first_last_frames": ("BOOLEAN", {"default": False})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
"cache_in_fp16": ("BOOLEAN", {"default": True})
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +54,7 @@ class FLAVR_VFI:
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
duplicate_first_last_frames: bool = False,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
cache_in_fp16: bool = True
|
||||
**kwargs
|
||||
):
|
||||
if multiplier != 2:
|
||||
warnings.warn("Currently, FLAVR only supports 2x interpolation. The process will continue but please set multiplier=2 afterward")
|
||||
@@ -105,7 +104,7 @@ class FLAVR_VFI:
|
||||
print("Done cache clearing")
|
||||
gc.collect()
|
||||
|
||||
dtype = torch.float16 if cache_in_fp16 else torch.float32
|
||||
dtype = torch.float32
|
||||
output_frames = [frame.cpu().to(dtype=dtype) for frame in output_frames] #Ensure all frames are in cpu
|
||||
out = torch.cat(output_frames, dim=0)
|
||||
out = padder.unpad(out)
|
||||
|
||||
+3
-4
@@ -87,8 +87,7 @@ class GMFSS_Fortuna_VFI:
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000}),
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
"cache_in_fp16": ("BOOLEAN", {"default": True})
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +102,7 @@ class GMFSS_Fortuna_VFI:
|
||||
clear_cache_after_n_frames = 10,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
cache_in_fp16: bool = True
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Perform video frame interpolation using a given checkpoint model.
|
||||
@@ -139,6 +138,6 @@ class GMFSS_Fortuna_VFI:
|
||||
args = [interpolation_model, scale]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float16 if cache_in_fp16 else torch.float32)
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
|
||||
+3
-4
@@ -20,8 +20,7 @@ class IFRNet_VFI:
|
||||
"scale_factor": ([0.25, 0.5, 1.0, 2.0, 4.0], {"default": 1.0}),
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
"cache_in_fp16": ("BOOLEAN", {"default": True})
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +36,7 @@ class IFRNet_VFI:
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
scale_factor: typing.SupportsFloat = 1.0,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
cache_in_fp16: bool = True
|
||||
**kwargs
|
||||
):
|
||||
from .IFRNet_S_arch import IRFNet_S
|
||||
from .IFRNet_L_arch import IRFNet_L
|
||||
@@ -53,6 +52,6 @@ class IFRNet_VFI:
|
||||
args = [interpolation_model, scale_factor]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float16 if cache_in_fp16 else torch.float32)
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
|
||||
+3
-4
@@ -21,8 +21,7 @@ class IFUnet_VFI:
|
||||
"ensemble": ("BOOLEAN", {"default":True})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
"cache_in_fp16": ("BOOLEAN", {"default": True})
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +38,7 @@ class IFUnet_VFI:
|
||||
scale_factor: typing.SupportsFloat = 1.0,
|
||||
ensemble: bool = True,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
cache_in_fp16: bool = True
|
||||
**kwargs
|
||||
):
|
||||
from .IFUNet_arch import IFUNetModel
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
@@ -54,7 +53,7 @@ class IFUnet_VFI:
|
||||
args = [interpolation_model, scale_factor, ensemble]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float16 if cache_in_fp16 else torch.float32)
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
|
||||
|
||||
+3
-4
@@ -22,8 +22,7 @@ class M2M_VFI:
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000}),
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
"cache_in_fp16": ("BOOLEAN", {"default": True})
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +37,7 @@ class M2M_VFI:
|
||||
clear_cache_after_n_frames: typing.SupportsInt = 1,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
cache_in_fp16: bool = True
|
||||
**kwargs
|
||||
):
|
||||
from .M2M_arch import M2M_PWC
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
@@ -56,6 +55,6 @@ class M2M_VFI:
|
||||
args = [interpolation_model]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float16 if cache_in_fp16 else torch.float32)
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
|
||||
-2
@@ -217,8 +217,6 @@ def cuda_kernel(strFunction: str, strKernel: str, objVariables: typing.Dict, **r
|
||||
def get_cuda_home_path():
|
||||
if "CUDA_HOME" in os.environ:
|
||||
return os.environ["CUDA_HOME"]
|
||||
if platform.system() == "Windows":
|
||||
return str(Path(__file__).parent.parent.parent.parent / "nvrtc_dlls") #https://github.com/cupy/cupy/issues/7776
|
||||
import torch
|
||||
torch_lib_path = Path(torch.__file__).parent / "lib"
|
||||
torch_lib_path = str(torch_lib_path.resolve())
|
||||
|
||||
+6
-7
@@ -6,6 +6,7 @@ import typing
|
||||
from comfy.model_management import get_torch_device
|
||||
import re
|
||||
from functools import cmp_to_key
|
||||
from packaging import version
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
CKPT_NAME_VER_DICT = {
|
||||
@@ -19,14 +20,13 @@ CKPT_NAME_VER_DICT = {
|
||||
"rife47.pth": "4.7",
|
||||
"rife48.pth": "4.7",
|
||||
"rife49.pth": "4.7",
|
||||
"sudo_rife4_269.662_testV1_scale1.pth": "4.0"
|
||||
#Arch 4.10 doesn't work due to state dict mismatch
|
||||
#TODO: Investigating and fix it
|
||||
#"rife410.pth": "4.10",
|
||||
#"rife411.pth": "4.10",
|
||||
#"rife412.pth": "4.10"
|
||||
}
|
||||
ver_re = re.compile(r'\d+')
|
||||
ver_cmp_key = cmp_to_key(lambda a,b: int(ver_re.search(a)[0]) > int(ver_re.search(b)[0]))
|
||||
|
||||
class RIFE_VFI:
|
||||
@classmethod
|
||||
@@ -34,7 +34,7 @@ class RIFE_VFI:
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (
|
||||
sorted(list(CKPT_NAME_VER_DICT.keys()), key=ver_cmp_key),
|
||||
sorted(list(CKPT_NAME_VER_DICT.keys()), key=lambda ckpt_name: version.parse(CKPT_NAME_VER_DICT[ckpt_name])),
|
||||
{"default": "rife47.pth"}
|
||||
),
|
||||
"frames": ("IMAGE", ),
|
||||
@@ -45,8 +45,7 @@ class RIFE_VFI:
|
||||
"scale_factor": ([0.25, 0.5, 1.0, 2.0, 4.0], {"default": 1.0})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
"cache_in_fp16": ("BOOLEAN", {"default": True})
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +63,7 @@ class RIFE_VFI:
|
||||
ensemble = False,
|
||||
scale_factor = 1.0,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
cache_in_fp16: bool = True
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Perform video frame interpolation using a given checkpoint model.
|
||||
@@ -103,6 +102,6 @@ class RIFE_VFI:
|
||||
args = [interpolation_model, scale_list, fast_mode, ensemble]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float16 if cache_in_fp16 else torch.float32)
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
|
||||
+3
-4
@@ -21,8 +21,7 @@ class SepconvVFI:
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
"cache_in_fp16": ("BOOLEAN", {"default": True})
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +36,7 @@ class SepconvVFI:
|
||||
clear_cache_after_n_frames = 10,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
cache_in_fp16: bool = True
|
||||
**kwargs
|
||||
):
|
||||
from .sepconv_enhanced import Network
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
@@ -52,6 +51,6 @@ class SepconvVFI:
|
||||
args = [interpolation_model]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, use_timestep=False, dtype=torch.float16 if cache_in_fp16 else torch.float32)
|
||||
interpolation_states=optional_interpolation_states, use_timestep=False, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
|
||||
+3
-4
@@ -22,8 +22,7 @@ class STMFNet_VFI:
|
||||
"duplicate_first_last_frames": ("BOOLEAN", {"default": False})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
"cache_in_fp16": ("BOOLEAN", {"default": True})
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +39,7 @@ class STMFNet_VFI:
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
duplicate_first_last_frames: bool = False,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
cache_in_fp16: bool = True
|
||||
**kwargs
|
||||
):
|
||||
from .stmfnet_arch import STMFNet_Model
|
||||
if multiplier != 2:
|
||||
@@ -91,7 +90,7 @@ class STMFNet_VFI:
|
||||
print("Comfy-VFI: Done cache clearing")
|
||||
gc.collect()
|
||||
|
||||
dtype = torch.float16 if cache_in_fp16 else torch.float32
|
||||
dtype = torch.float32
|
||||
output_frames = [frame.cpu().to(dtype=dtype) for frame in output_frames] #Ensure all frames are in cpu
|
||||
out = torch.cat(output_frames, dim=0)
|
||||
# clear cache for courtesy
|
||||
|
||||
@@ -144,15 +144,18 @@ def generic_frame_loop(
|
||||
return [*first_half, *second_half]
|
||||
|
||||
assert_batch_size(frames) # Too lazy to include model name lol
|
||||
output_frames = [] # List to store processed frames in the correct order
|
||||
output_frames = torch.zeros(multiplier*frames.shape[0], *frames.shape[1:], dtype=dtype, device="cpu")
|
||||
out_len = 0
|
||||
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache = 0
|
||||
|
||||
for frame_itr in range(len(frames) - 1): # Skip the final frame since there are no frames after it
|
||||
#Ensure that input frames are in fp32 - the same dtype as model
|
||||
frame0 = frames[frame_itr:frame_itr+1].float()
|
||||
frame1 = frames[frame_itr+1:frame_itr+2].float()
|
||||
output_frames.append(frame0.to(dtype=dtype)) # Start with first frame
|
||||
frame0 = frames[frame_itr:frame_itr+1]
|
||||
output_frames[out_len] = frame0 # Start with first frame
|
||||
out_len += 1
|
||||
# Ensure that input frames are in fp32 - the same dtype as model
|
||||
frame0 = frame0.to(dtype=torch.float32)
|
||||
frame1 = frames[frame_itr+1:frame_itr+2].to(dtype=torch.float32)
|
||||
|
||||
if interpolation_states is not None and interpolation_states.is_frame_skipped(frame_itr):
|
||||
continue
|
||||
@@ -175,8 +178,10 @@ def generic_frame_loop(
|
||||
middle_frames = non_timestep_inference(frame0.to(DEVICE), frame1.to(DEVICE), multiplier - 1)
|
||||
middle_frame_batches.extend(torch.cat(middle_frames, dim=0).detach().cpu().to(dtype=dtype))
|
||||
|
||||
# Extend output array by batch
|
||||
output_frames.extend(middle_frame_batches)
|
||||
# Copy middle frames to output
|
||||
for middle_frame in middle_frame_batches:
|
||||
output_frames[out_len] = middle_frame
|
||||
out_len += 1
|
||||
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache += 1
|
||||
# Try to avoid a memory overflow by clearing cuda cache regularly
|
||||
@@ -189,14 +194,14 @@ def generic_frame_loop(
|
||||
gc.collect()
|
||||
|
||||
print(f"Comfy-VFI done! {len(output_frames)} frames generated at resolution: {output_frames[0].shape}")
|
||||
output_frames.append(frames[-1:].to(dtype=dtype)) # Append final frame
|
||||
output_frames = [frame.cpu() for frame in output_frames] #Ensure all frames are in cpu
|
||||
out = torch.cat(output_frames, dim=0)
|
||||
# Append final frame
|
||||
output_frames[out_len] = frames[-1:]
|
||||
out_len += 1
|
||||
# clear cache for courtesy
|
||||
print("Comfy-VFI: Final clearing cache...", end = ' ')
|
||||
soft_empty_cache()
|
||||
print("Done cache clearing")
|
||||
return out
|
||||
return output_frames[:out_len]
|
||||
|
||||
""" def generic_4frame_loop(
|
||||
frames,
|
||||
|
||||
@@ -7,6 +7,8 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
|
||||
|
||||
## NOTICE
|
||||
* V4.77: Compatibility patch applied. Requires ComfyUI version (Oct. 8th) or later.
|
||||
* V4.73.3: ControlNetApply (SEGS) supports AnimateDiff
|
||||
* V4.20.1: Due to the feature update in `RegionalSampler`, the parameter order has changed, causing malfunctions in previously created `RegionalSamplers`. Please adjust the parameters accordingly.
|
||||
* V4.12: `MASKS` is changed to `MASK`.
|
||||
* V4.7.2 isn't compatible with old version of `ControlNet Auxiliary Preprocessor`. If you will use `MediaPipe FaceMesh to SEGS` update to latest version(Sep. 17th).
|
||||
@@ -41,8 +43,9 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
|
||||
* ControlNet
|
||||
* ControlNetApply (SEGS) - To apply ControlNet in SEGS, you need to use the Preprocessor Provider node from the Inspire Pack to utilize this node.
|
||||
* `SEGSPreprocessor` and `Image` can be selectively applied. If an `Image` is given, `SEGSPreprocessor` will be ignored.
|
||||
* If set to `Image`, you can preview the cropped cnet image through `SEGSPreview (CNET Image)`. Images generated by `SEGSPreprocessor` should be verified through the `cnet_pil` output of each Detailer.
|
||||
* `segs_preprocessor` and `control_image` can be selectively applied. If an `control_image` is given, `segs_preprocessor` will be ignored.
|
||||
* If set to `control_image`, you can preview the cropped cnet image through `SEGSPreview (CNET Image)`. Images generated by `segs_preprocessor` should be verified through the `cnet_images` output of each Detailer.
|
||||
* The `segs_preprocessor` operates by applying preprocessing on-the-fly based on the cropped image during the detailing process, while `control_image` will be cropped and used as input to `ControlNetApply (SEGS)`.
|
||||
* ControlNetClear (SEGS) - Clear applied ControlNet in SEGS
|
||||
|
||||
* Bitwise(SEGS & SEGS) - Performs a 'bitwise and' operation between two SEGS.
|
||||
@@ -89,6 +92,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* SEGS Filter (label) - This node filters SEGS based on the label of the detected areas.
|
||||
* SEGS Filter (ordered) - This node sorts SEGS based on size and position and retrieves SEGs within a certain range.
|
||||
* SEGS Filter (range) - This node retrieves only SEGs from SEGS that have a size and position within a certain range.
|
||||
* SEGS Assign (label) - Assign labels sequentially to SEGS. This node is useful when used with `[LAB]` of FaceDetailer.
|
||||
* SEGSConcat - Concatenate segs1 and segs2. If source shape of segs1 and segs2 are different from segs2 will be ignored.
|
||||
* Picker (SEGS) - Among the input SEGS, you can select a specific SEG through a dialog. If no SEG is selected, it outputs an empty SEGS. Increasing the batch_size of SEGSDetailer can be used for the purpose of selecting from the candidates.
|
||||
* Set Default Image For SEGS - Set a default image for SEGS. SEGS with images set this way do not need to have a fallback image set. When override is set to false, the original image is preserved.
|
||||
@@ -122,8 +126,9 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* You need to install the [BlenderNeko/ComfyUI_TiledKSampler](https://github.com/BlenderNeko/ComfyUI_TiledKSampler) node extension.
|
||||
|
||||
* PK_HOOK
|
||||
* DenoiseScheduleHookProvider - IterativeUpscale provides a hook that gradually changes the denoise to target_denoise as the step progresses.
|
||||
* CfgScheduleHookProvider - IterativeUpscale provides a hook that gradually changes the cfg to target_cfg as the step progresses.
|
||||
* DenoiseScheduleHookProvider - IterativeUpscale provides a hook that gradually changes the denoise to target_denoise as the iterative-step progresses.
|
||||
* CfgScheduleHookProvider - IterativeUpscale provides a hook that gradually changes the cfg to target_cfg as the iterative-step progresses.
|
||||
* StepsScheduleHookProvider - IterativeUpscale provides a hook that gradually changes the sampling-steps to target_steps as the iterative-step progresses.
|
||||
* NoiseInjectionHookProvider - During each iteration of IterativeUpscale, noise is injected into the latent space while varying the strength according to a schedule.
|
||||
* You need to install the [BlenderNeko/ComfyUI_Noise](https://github.com/BlenderNeko/ComfyUI_Noise) node extension.
|
||||
* The seed serves as the initial value required for generating noise, and it increments by 1 with each iteration as the process unfolds.
|
||||
@@ -155,7 +160,8 @@ This takes latent as input and outputs latent as the result.
|
||||
* You need to install the [BlenderNeko/ComfyUI_TiledKSampler](https://github.com/BlenderNeko/ComfyUI_TiledKSampler) node extension.
|
||||
|
||||
* TwoAdvancedSamplersForMask - TwoSamplersForMask is similar to TwoAdvancedSamplersForMask, but they differ in their operation. TwoSamplersForMask performs sampling in the mask area only after all the samples in the base area are finished. On the other hand, TwoAdvancedSamplersForMask performs sampling in both the base area and the mask area sequentially at each step.
|
||||
* KSamplerAdvancedProvider - This is a wrapper that enables KSampler to be used in TwoAdvancedSamplersForMask.
|
||||
* KSamplerAdvancedProvider - This is a wrapper that enables KSampler to be used in TwoAdvancedSamplersForMask, RegionalSampler.
|
||||
* sigma_factor: By multiplying the denoise schedule by the sigma_factor, you can adjust the amount of denoising based on the configured denoise.
|
||||
|
||||
* TwoSamplersForMaskUpscalerProvider - This is an Upscaler that extends TwoSamplersForMask to be used in Iterative Upscale.
|
||||
* TwoSamplersForMaskUpscalerProviderPipe - pipe version of TwoSamplersForMaskUpscalerProvider.
|
||||
|
||||
@@ -176,6 +176,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
|
||||
"PixelKSampleHookCombine": PixelKSampleHookCombine,
|
||||
"DenoiseScheduleHookProvider": DenoiseScheduleHookProvider,
|
||||
"StepsScheduleHookProvider": StepsScheduleHookProvider,
|
||||
"CfgScheduleHookProvider": CfgScheduleHookProvider,
|
||||
"NoiseInjectionHookProvider": NoiseInjectionHookProvider,
|
||||
"UnsamplerHookProvider": UnsamplerHookProvider,
|
||||
@@ -285,6 +286,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactCombineConditionings": CombineConditionings,
|
||||
"ImpactConcatConditionings": ConcatConditionings,
|
||||
|
||||
"ImpactSEGSLabelAssign": SEGSLabelAssign,
|
||||
"ImpactSEGSLabelFilter": SEGSLabelFilter,
|
||||
"ImpactSEGSRangeFilter": SEGSRangeFilter,
|
||||
"ImpactSEGSOrderedFilter": SEGSOrderedFilter,
|
||||
@@ -384,6 +386,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
|
||||
"ImpactKSamplerBasicPipe": "KSampler (pipe)",
|
||||
"ImpactKSamplerAdvancedBasicPipe": "KSampler (Advanced/pipe)",
|
||||
"ImpactSEGSLabelAssign": "SEGS Assign (label)",
|
||||
"ImpactSEGSLabelFilter": "SEGS Filter (label)",
|
||||
"ImpactSEGSRangeFilter": "SEGS Filter (range)",
|
||||
"ImpactSEGSOrderedFilter": "SEGS Filter (ordered)",
|
||||
|
||||
+2
-2
@@ -65,7 +65,7 @@ class SEGSDetailerForAnimateDiff:
|
||||
else:
|
||||
cropped_image_frames = torch.concat((cropped_image_frames, cropped_image), dim=0)
|
||||
|
||||
cropped_image_frames = cropped_image_frames.numpy()
|
||||
cropped_image_frames = cropped_image_frames.cpu().numpy()
|
||||
enhanced_image_tensor, cnet_images = core.enhance_detail_for_animatediff(cropped_image_frames, model, clip, vae, guide_size, guide_size_for, max_size,
|
||||
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, denoise, seg.cropped_mask,
|
||||
@@ -79,7 +79,7 @@ class SEGSDetailerForAnimateDiff:
|
||||
if enhanced_image_tensor is None:
|
||||
new_cropped_image = cropped_image_frames
|
||||
else:
|
||||
new_cropped_image = enhanced_image_tensor.numpy()
|
||||
new_cropped_image = enhanced_image_tensor.cpu().numpy()
|
||||
|
||||
new_seg = SEG(new_cropped_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, None)
|
||||
new_segs.append(new_seg)
|
||||
|
||||
@@ -2,7 +2,7 @@ import configparser
|
||||
import os
|
||||
|
||||
|
||||
version_code = [4, 73, 3]
|
||||
version_code = [4, 78]
|
||||
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
dependency_version = 20
|
||||
|
||||
@@ -370,7 +370,7 @@ def enhance_detail_for_animatediff(image_frames, model, clip, vae, guide_size, g
|
||||
|
||||
cnet_images = None
|
||||
if control_net_wrapper is not None:
|
||||
positive, negative, cnet_images = control_net_wrapper.apply(positive, negative, torch.from_numpy(image_frames), noise_mask)
|
||||
positive, negative, cnet_images = control_net_wrapper.apply(positive, negative, torch.from_numpy(image_frames), noise_mask, use_acn=True)
|
||||
|
||||
if len(upscaled_mask) != len(image_frames) and len(upscaled_mask) > 1:
|
||||
print(f"[Impact Pack] WARN: DetailerForAnimateDiff - The number of the mask frames({len(upscaled_mask)}) and the image frames({len(image_frames)}) are different. Combine the mask frames and apply.")
|
||||
@@ -1505,26 +1505,36 @@ class ControlNetWrapper:
|
||||
else:
|
||||
self.control_image = None
|
||||
|
||||
def apply(self, positive, negative, image, mask=None):
|
||||
cnet_tensors = []
|
||||
prev_cnet_tensors = []
|
||||
def apply(self, positive, negative, image, mask=None, use_acn=False):
|
||||
cnet_image_list = []
|
||||
prev_cnet_images = []
|
||||
|
||||
if self.prev_control_net is not None:
|
||||
positive, negative, prev_cnet_tensors = self.prev_control_net.apply(positive, negative, image, mask)
|
||||
positive, negative, prev_cnet_images = self.prev_control_net.apply(positive, negative, image, mask, use_acn=use_acn)
|
||||
|
||||
if self.control_image is not None:
|
||||
cnet_tensor = self.control_image
|
||||
cnet_image = self.control_image
|
||||
elif self.preprocessor is not None:
|
||||
cnet_tensor = self.preprocessor.apply(image, mask)
|
||||
cnet_image = self.preprocessor.apply(image, mask)
|
||||
else:
|
||||
cnet_tensor = image
|
||||
cnet_image = image
|
||||
|
||||
cnet_tensors.extend(prev_cnet_tensors)
|
||||
cnet_tensors.append(cnet_tensor)
|
||||
cnet_image_list.extend(prev_cnet_images)
|
||||
cnet_image_list.append(cnet_image)
|
||||
|
||||
positive = nodes.ControlNetApply().apply_controlnet(positive, self.control_net, cnet_tensor, self.strength)[0]
|
||||
if use_acn:
|
||||
if "ACN_AdvancedControlNetApply" in nodes.NODE_CLASS_MAPPINGS:
|
||||
acn = nodes.NODE_CLASS_MAPPINGS['ACN_AdvancedControlNetApply']()
|
||||
positive, negative, _ = acn.apply_controlnet(positive=positive, negative=negative, control_net=self.control_net, image=cnet_image,
|
||||
strength=self.strength, start_percent=0.0, end_percent=1.0)
|
||||
else:
|
||||
utils.try_install_custom_node('https://github.com/BlenderNeko/ComfyUI_TiledKSampler',
|
||||
"To use 'ControlNetWrapper' for AnimateDiff, 'ComfyUI-Advanced-ControlNet' extension is required.")
|
||||
raise Exception("'ACN_AdvancedControlNetApply' node isn't installed.")
|
||||
else:
|
||||
positive = nodes.ControlNetApply().apply_controlnet(positive, self.control_net, cnet_image, self.strength)[0]
|
||||
|
||||
return positive, negative, cnet_tensors
|
||||
return positive, negative, cnet_image_list
|
||||
|
||||
|
||||
class ControlNetAdvancedWrapper:
|
||||
@@ -1543,26 +1553,36 @@ class ControlNetAdvancedWrapper:
|
||||
else:
|
||||
self.control_image = None
|
||||
|
||||
def apply(self, positive, negative, image, mask=None):
|
||||
cnet_tensors = []
|
||||
prev_cnet_tensors = []
|
||||
def apply(self, positive, negative, image, mask=None, use_acn=False):
|
||||
cnet_image_list = []
|
||||
prev_cnet_images = []
|
||||
|
||||
if self.prev_control_net is not None:
|
||||
positive, negative, prev_cnet_tensors = self.prev_control_net.apply(positive, negative, image, mask)
|
||||
positive, negative, prev_cnet_images = self.prev_control_net.apply(positive, negative, image, mask)
|
||||
|
||||
if self.control_image is not None:
|
||||
cnet_tensor = self.control_image
|
||||
cnet_image = self.control_image
|
||||
elif self.preprocessor is not None:
|
||||
cnet_tensor = self.preprocessor.apply(image, mask)
|
||||
cnet_image = self.preprocessor.apply(image, mask)
|
||||
else:
|
||||
cnet_tensor = image
|
||||
cnet_image = image
|
||||
|
||||
cnet_tensors.extend(prev_cnet_tensors)
|
||||
cnet_tensors.append(cnet_tensor)
|
||||
cnet_image_list.extend(prev_cnet_images)
|
||||
cnet_image_list.append(cnet_image)
|
||||
|
||||
conditioning = nodes.ControlNetApplyAdvanced().apply_controlnet(positive, negative, self.control_net, cnet_tensor, self.strength, self.start_percent, self.end_percent)
|
||||
if use_acn:
|
||||
if "ACN_AdvancedControlNetApply" in nodes.NODE_CLASS_MAPPINGS:
|
||||
acn = nodes.NODE_CLASS_MAPPINGS['ACN_AdvancedControlNetApply']()
|
||||
positive, negative, _ = acn.apply_controlnet(positive=positive, negative=negative, control_net=self.control_net, image=cnet_image,
|
||||
strength=self.strength, start_percent=self.start_percent, end_percent=self.end_percent)
|
||||
else:
|
||||
utils.try_install_custom_node('https://github.com/BlenderNeko/ComfyUI_TiledKSampler',
|
||||
"To use 'ControlNetAdvancedWrapper' for AnimateDiff, 'ComfyUI-Advanced-ControlNet' extension is required.")
|
||||
raise Exception("'ACN_AdvancedControlNetApply' node isn't installed.")
|
||||
else:
|
||||
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive, negative, self.control_net, cnet_image, self.strength, self.start_percent, self.end_percent)
|
||||
|
||||
return conditioning[0], conditioning[1], cnet_tensors
|
||||
return positive, negative, cnet_image_list
|
||||
|
||||
|
||||
# REQUIREMENTS: BlenderNeko/ComfyUI_TiledKSampler
|
||||
|
||||
@@ -109,11 +109,14 @@ class SimpleCfgScheduleHook(PixelKSampleHook):
|
||||
super().__init__()
|
||||
self.target_cfg = target_cfg
|
||||
|
||||
def pre_ksample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent,
|
||||
denoise):
|
||||
progress = self.cur_step / self.total_step
|
||||
gap = self.target_cfg - cfg
|
||||
current_cfg = cfg + gap * progress
|
||||
def pre_ksample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent, denoise):
|
||||
if self.total_step > 1:
|
||||
progress = self.cur_step / (self.total_step - 1)
|
||||
gap = self.target_cfg - cfg
|
||||
current_cfg = int(cfg + gap * progress)
|
||||
else:
|
||||
current_cfg = self.target_cfg
|
||||
|
||||
return model, seed, steps, current_cfg, sampler_name, scheduler, positive, negative, upscaled_latent, denoise
|
||||
|
||||
|
||||
@@ -122,14 +125,33 @@ class SimpleDenoiseScheduleHook(PixelKSampleHook):
|
||||
super().__init__()
|
||||
self.target_denoise = target_denoise
|
||||
|
||||
def pre_ksample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent,
|
||||
denoise):
|
||||
progress = self.cur_step / self.total_step
|
||||
gap = self.target_denoise - denoise
|
||||
current_denoise = denoise + gap * progress
|
||||
def pre_ksample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent, denoise):
|
||||
if self.total_step > 1:
|
||||
progress = self.cur_step / (self.total_step - 1)
|
||||
gap = self.target_denoise - denoise
|
||||
current_denoise = denoise + gap * progress
|
||||
else:
|
||||
current_denoise = self.target_denoise
|
||||
|
||||
return model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent, current_denoise
|
||||
|
||||
|
||||
class SimpleStepsScheduleHook(PixelKSampleHook):
|
||||
def __init__(self, target_steps):
|
||||
super().__init__()
|
||||
self.target_steps = target_steps
|
||||
|
||||
def pre_ksample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent, denoise):
|
||||
if self.total_step > 1:
|
||||
progress = self.cur_step / (self.total_step - 1)
|
||||
gap = self.target_steps - steps
|
||||
current_steps = int(steps + gap * progress)
|
||||
else:
|
||||
current_steps = self.target_steps
|
||||
|
||||
return model, seed, current_steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent, denoise
|
||||
|
||||
|
||||
class DetailerHook(PixelKSampleHook):
|
||||
def cycle_latent(self, latent):
|
||||
return latent
|
||||
@@ -147,9 +169,14 @@ class SimpleDetailerDenoiseSchedulerHook(DetailerHook):
|
||||
self.target_denoise = target_denoise
|
||||
|
||||
def pre_ksample(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise):
|
||||
progress = self.cur_step / self.total_step
|
||||
gap = self.target_denoise - denoise
|
||||
current_denoise = denoise + gap * progress
|
||||
if self.total_step > 1:
|
||||
progress = self.cur_step / (self.total_step - 1)
|
||||
gap = self.target_denoise - denoise
|
||||
current_denoise = denoise + gap * progress
|
||||
else:
|
||||
# ignore hook if total cycle <= 1
|
||||
current_denoise = denoise
|
||||
|
||||
return model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, current_denoise
|
||||
|
||||
|
||||
|
||||
+32
-6
@@ -237,8 +237,10 @@ class DetailerForEach:
|
||||
else:
|
||||
cropped_mask = None
|
||||
|
||||
if wildcard_chooser is not None:
|
||||
if wildcard_chooser is not None and wmode != "LAB":
|
||||
seg_seed, wildcard_item = wildcard_chooser.get(seg)
|
||||
elif wildcard_chooser is not None and wmode == "LAB":
|
||||
seg_seed, wildcard_item = None, wildcard_chooser.get(seg)
|
||||
else:
|
||||
seg_seed, wildcard_item = None, None
|
||||
|
||||
@@ -272,7 +274,7 @@ class DetailerForEach:
|
||||
# Convert enhanced_pil_alpha to RGBA mode
|
||||
enhanced_image_alpha = tensor_convert_rgba(enhanced_image)
|
||||
new_seg_image = enhanced_image.numpy() # alpha should not be applied to seg_image
|
||||
|
||||
|
||||
# Apply the mask
|
||||
mask = tensor_resize(mask, *tensor_get_size(enhanced_image))
|
||||
tensor_putalpha(enhanced_image_alpha, mask)
|
||||
@@ -780,6 +782,30 @@ class DenoiseScheduleHookProvider:
|
||||
return (hook, )
|
||||
|
||||
|
||||
class StepsScheduleHookProvider:
|
||||
schedules = ["simple"]
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"schedule_for_iteration": (s.schedules,),
|
||||
"target_steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("PK_HOOK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Upscale"
|
||||
|
||||
def doit(self, schedule_for_iteration, target_steps):
|
||||
hook = None
|
||||
if schedule_for_iteration == "simple":
|
||||
hook = hooks.SimpleStepsScheduleHook(target_steps)
|
||||
|
||||
return (hook, )
|
||||
|
||||
|
||||
class DetailerHookCombine:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -1104,7 +1130,7 @@ class IterativeLatentUpscale:
|
||||
new_h = h*upscale_factor
|
||||
core.update_node_status(unique_id, f"Final step | x{upscale_factor:.2f}", 1.0)
|
||||
print(f"IterativeLatentUpscale[Final]: {new_w:.1f}x{new_h:.1f} (scale:{upscale_factor:.2f}) ")
|
||||
step_info = steps, steps
|
||||
step_info = steps-1, steps
|
||||
current_latent = upscaler.upscale_shape(step_info, current_latent, new_w, new_h, temp_prefix)
|
||||
|
||||
core.update_node_status(unique_id, "", None)
|
||||
@@ -1451,7 +1477,7 @@ class SegsBitwiseAndMask:
|
||||
|
||||
def doit(self, segs, mask):
|
||||
return (core.segs_bitwise_and_mask(segs, mask), )
|
||||
|
||||
|
||||
|
||||
class SegsBitwiseAndMaskForEach:
|
||||
@classmethod
|
||||
@@ -1626,7 +1652,7 @@ class SubtractMask:
|
||||
"mask2": ("MASK", ),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
@@ -1757,7 +1783,7 @@ class ImageReceiver:
|
||||
return hash(image_data)
|
||||
else:
|
||||
return hash(image)
|
||||
|
||||
|
||||
|
||||
from server import PromptServer
|
||||
|
||||
|
||||
+13
-12
@@ -119,18 +119,18 @@ def separated_sample(model, add_noise, seed, steps, cfg, sampler_name, scheduler
|
||||
|
||||
|
||||
def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise,
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None):
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None, sigma_factor=1.0):
|
||||
|
||||
if refiner_ratio is None or refiner_model is None or refiner_clip is None or refiner_positive is None or refiner_negative is None:
|
||||
refined_latent = nodes.KSampler().sample(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise)[0]
|
||||
refined_latent = nodes.KSampler().sample(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise * sigma_factor)[0]
|
||||
else:
|
||||
advanced_steps = math.floor(steps / denoise)
|
||||
start_at_step = advanced_steps - steps
|
||||
end_at_step = start_at_step + math.floor(steps * (1.0 - refiner_ratio))
|
||||
|
||||
print(f"pre: {start_at_step} .. {end_at_step} / {advanced_steps}")
|
||||
# print(f"pre: {start_at_step} .. {end_at_step} / {advanced_steps}")
|
||||
temp_latent = separated_sample(model, True, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, latent_image, start_at_step, end_at_step, True)
|
||||
positive, negative, latent_image, start_at_step, end_at_step, True, sigma_ratio=sigma_factor)
|
||||
|
||||
if 'noise_mask' in latent_image:
|
||||
# noise_latent = \
|
||||
@@ -141,9 +141,9 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
latent_compositor = nodes.NODE_CLASS_MAPPINGS['LatentCompositeMasked']()
|
||||
temp_latent = latent_compositor.composite(latent_image, temp_latent, 0, 0, False, latent_image['noise_mask'])[0]
|
||||
|
||||
print(f"post: {end_at_step} .. {advanced_steps + 1} / {advanced_steps}")
|
||||
# print(f"post: {end_at_step} .. {advanced_steps + 1} / {advanced_steps}")
|
||||
refined_latent = separated_sample(refiner_model, False, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
refiner_positive, refiner_negative, temp_latent, end_at_step, advanced_steps + 1, False)
|
||||
refiner_positive, refiner_negative, temp_latent, end_at_step, advanced_steps + 1, False, sigma_ratio=sigma_factor)
|
||||
|
||||
return refined_latent
|
||||
|
||||
@@ -151,18 +151,19 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
class KSamplerAdvancedWrapper:
|
||||
params = None
|
||||
|
||||
def __init__(self, model, cfg, sampler_name, scheduler, positive, negative, sampler_opt=None):
|
||||
self.params = model, cfg, sampler_name, scheduler, positive, negative
|
||||
def __init__(self, model, cfg, sampler_name, scheduler, positive, negative, sampler_opt=None, sigma_factor=1.0):
|
||||
self.params = model, cfg, sampler_name, scheduler, positive, negative, sigma_factor
|
||||
self.sampler_opt = sampler_opt
|
||||
|
||||
def clone_with_conditionings(self, positive, negative):
|
||||
model, cfg, sampler_name, scheduler, _, _ = self.params
|
||||
model, cfg, sampler_name, scheduler, _, _, _ = self.params
|
||||
return KSamplerAdvancedWrapper(model, cfg, sampler_name, scheduler, positive, negative, self.sampler_opt)
|
||||
|
||||
def sample_advanced(self, add_noise, seed, steps, latent_image, start_at_step, end_at_step, return_with_leftover_noise, hook=None,
|
||||
recovery_mode="ratio additional", recovery_sampler="AUTO", recovery_sigma_ratio=1.0):
|
||||
|
||||
model, cfg, sampler_name, scheduler, positive, negative = self.params
|
||||
model, cfg, sampler_name, scheduler, positive, negative, sigma_factor = self.params
|
||||
# steps, start_at_step, end_at_step = self.compensate_denoise(steps, start_at_step, end_at_step)
|
||||
|
||||
if hook is not None:
|
||||
model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent = hook.pre_ksample_advanced(model, add_noise, seed, steps, cfg, sampler_name, scheduler,
|
||||
@@ -183,7 +184,7 @@ class KSamplerAdvancedWrapper:
|
||||
if sigma_ratio > 0:
|
||||
latent_image = separated_sample(model, add_noise, seed, steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, latent_image, start_at_step, end_at_step,
|
||||
return_with_leftover_noise, sigma_ratio=sigma_ratio, sampler_opt=self.sampler_opt)
|
||||
return_with_leftover_noise, sigma_ratio=sigma_ratio * sigma_factor, sampler_opt=self.sampler_opt)
|
||||
except ValueError as e:
|
||||
if str(e) == 'sigma_min and sigma_max must not be 0':
|
||||
print(f"\nWARN: sampling skipped - sigma_min and sigma_max are 0")
|
||||
@@ -207,7 +208,7 @@ class KSamplerAdvancedWrapper:
|
||||
try:
|
||||
latent_image = separated_sample(model, add_noise, seed, steps, cfg, recovery_sampler, scheduler,
|
||||
positive, negative, latent_image, start_at_step-compensate, end_at_step,
|
||||
return_with_leftover_noise, sigma_ratio=recovery_sigma_ratio, sampler_opt=self.sampler_opt)
|
||||
return_with_leftover_noise, sigma_ratio=recovery_sigma_ratio * sigma_factor, sampler_opt=self.sampler_opt)
|
||||
except ValueError as e:
|
||||
if str(e) == 'sigma_min and sigma_max must not be 0':
|
||||
print(f"\nWARN: sampling skipped - sigma_min and sigma_max are 0")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import folder_paths
|
||||
from impact.core import *
|
||||
import os
|
||||
|
||||
import mmcv
|
||||
from mmdet.apis import (inference_detector, init_detector)
|
||||
|
||||
@@ -415,6 +415,44 @@ class SEGSLabelFilter:
|
||||
return SEGSLabelFilter.filter(segs, labels)
|
||||
|
||||
|
||||
class SEGSLabelAssign:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segs": ("SEGS", ),
|
||||
"labels": ("STRING", {"multiline": True, "placeholder": "List the label to be assigned in order of segs, separated by commas"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS",)
|
||||
RETURN_NAMES = ("SEGS",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@staticmethod
|
||||
def assign(segs, labels):
|
||||
labels = [label.strip() for label in labels]
|
||||
|
||||
if len(labels) != len(segs[1]):
|
||||
print(f'Warning (SEGSLabelAssign): length of labels ({len(labels)}) != length of segs ({len(segs[1])})')
|
||||
|
||||
labeled_segs = []
|
||||
|
||||
idx = 0
|
||||
for x in segs[1]:
|
||||
if len(labels) > idx:
|
||||
x = x._replace(label=labels[idx])
|
||||
labeled_segs.append(x)
|
||||
idx += 1
|
||||
|
||||
return ((segs[0], labeled_segs), )
|
||||
|
||||
def doit(self, segs, labels):
|
||||
labels = labels.split(',')
|
||||
return SEGSLabelAssign.assign(segs, labels)
|
||||
|
||||
|
||||
class SEGSOrderedFilter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
+4
-3
@@ -67,6 +67,7 @@ class KSamplerAdvancedProvider:
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
|
||||
"sigma_factor": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
"basic_pipe": ("BASIC_PIPE", )
|
||||
},
|
||||
"optional": {
|
||||
@@ -79,9 +80,9 @@ class KSamplerAdvancedProvider:
|
||||
|
||||
CATEGORY = "ImpactPack/Sampler"
|
||||
|
||||
def doit(self, cfg, sampler_name, scheduler, basic_pipe, sampler_opt=None):
|
||||
def doit(self, cfg, sampler_name, scheduler, basic_pipe, sigma_factor=1.0, sampler_opt=None):
|
||||
model, _, _, positive, negative = basic_pipe
|
||||
sampler = KSamplerAdvancedWrapper(model, cfg, sampler_name, scheduler, positive, negative, sampler_opt=sampler_opt)
|
||||
sampler = KSamplerAdvancedWrapper(model, cfg, sampler_name, scheduler, positive, negative, sampler_opt=sampler_opt, sigma_factor=sigma_factor)
|
||||
return (sampler, )
|
||||
|
||||
|
||||
@@ -251,7 +252,7 @@ class ConcatConditionings:
|
||||
RETURN_TYPES = ("CONDITIONING", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/__for_testing"
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, **kwargs):
|
||||
conditioning_to = list(kwargs.values())[0]
|
||||
|
||||
@@ -495,9 +495,16 @@ def to_latent_image(pixels, vae):
|
||||
y = pixels.shape[2]
|
||||
if pixels.shape[1] != x or pixels.shape[2] != y:
|
||||
pixels = pixels[:, :x, :y, :]
|
||||
pixels = nodes.VAEEncode.vae_encode_crop_pixels(pixels)
|
||||
t = vae.encode(pixels[:, :, :, :3])
|
||||
return {"samples": t}
|
||||
|
||||
vae_encode = nodes.VAEEncode()
|
||||
if hasattr(nodes.VAEEncode, "vae_encode_crop_pixels"):
|
||||
# backward compatibility
|
||||
print(f"[Impact Pack] ComfyUI is outdated.")
|
||||
pixels = nodes.VAEEncode.vae_encode_crop_pixels(pixels)
|
||||
t = vae.encode(pixels[:, :, :, :3])
|
||||
return {"samples": t}
|
||||
|
||||
return vae_encode.encode(vae, pixels)[0]
|
||||
|
||||
|
||||
def empty_pil_tensor(w=64, h=64):
|
||||
|
||||
@@ -393,6 +393,47 @@ class CreateFadeMaskAdvanced:
|
||||
if invert:
|
||||
return (1.0 - torch.cat(out, dim=0),)
|
||||
return (torch.cat(out, dim=0),)
|
||||
|
||||
class ScaleBatchPromptSchedule:
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
FUNCTION = "scaleschedule"
|
||||
CATEGORY = "KJNodes"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"input_str": ("STRING", {"forceInput": True,"default": "0:(0.0),\n7:(1.0),\n15:(0.0)\n"}),
|
||||
"old_frame_count": ("INT", {"forceInput": True,"default": 1,"min": 1, "max": 4096, "step": 1}),
|
||||
"new_frame_count": ("INT", {"forceInput": True,"default": 1,"min": 1, "max": 4096, "step": 1}),
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
def scaleschedule(self, old_frame_count, input_str, new_frame_count):
|
||||
print("input_str:", input_str)
|
||||
pattern = r'"(\d+)"\s*:\s*"(.*?)"(?:,|\Z)'
|
||||
frame_strings = dict(re.findall(pattern, input_str))
|
||||
|
||||
# Calculate the scaling factor
|
||||
scaling_factor = (new_frame_count - 1) / (old_frame_count - 1)
|
||||
|
||||
# Initialize a dictionary to store the new frame numbers and strings
|
||||
new_frame_strings = {}
|
||||
|
||||
# Iterate over the frame numbers and strings
|
||||
for old_frame, string in frame_strings.items():
|
||||
# Calculate the new frame number
|
||||
new_frame = int(round(int(old_frame) * scaling_factor))
|
||||
|
||||
# Store the new frame number and corresponding string
|
||||
new_frame_strings[new_frame] = string
|
||||
|
||||
# Format the output string
|
||||
output_str = ', '.join([f'"{k}":"{v}"' for k, v in sorted(new_frame_strings.items())])
|
||||
print(output_str)
|
||||
return (output_str,)
|
||||
|
||||
class CrossFadeImages:
|
||||
|
||||
@@ -1128,24 +1169,38 @@ class VRAM_Debug:
|
||||
freemem_after = comfy.model_management.get_free_memory()
|
||||
print(freemem_after)
|
||||
return (model, freemem_before, freemem_after)
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
|
||||
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
any = AnyType("*")
|
||||
|
||||
class SomethingToString:
|
||||
@classmethod
|
||||
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"input": ("*", {"forceinput": True, "default": ""}),
|
||||
"input": (any, {}),
|
||||
},
|
||||
"optional": {
|
||||
"prefix": ("STRING", {"default": ""}),
|
||||
"suffix": ("STRING", {"default": ""}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("STRING",)
|
||||
FUNCTION = "stringify"
|
||||
CATEGORY = "KJNodes"
|
||||
|
||||
def stringify(self, input):
|
||||
def stringify(self, input, prefix="", suffix=""):
|
||||
if isinstance(input, (int, float, bool)):
|
||||
stringified = str(input)
|
||||
print(stringified)
|
||||
if prefix: # Check if prefix is not empty
|
||||
stringified = prefix + stringified # Add the prefix
|
||||
if suffix: # Check if suffix is not empty
|
||||
stringified = stringified + suffix # Add the suffix
|
||||
else:
|
||||
return
|
||||
return (stringified,)
|
||||
@@ -2140,7 +2195,7 @@ class BatchCLIPSeg:
|
||||
model.to(device) # Ensure the model is on the correct device
|
||||
images = images.to(device)
|
||||
processor = CLIPSegProcessor.from_pretrained("CIDAS/clipseg-rd64-refined")
|
||||
|
||||
pbar = comfy.utils.ProgressBar(images.shape[0])
|
||||
for image in images:
|
||||
image = (image* 255).type(torch.uint8)
|
||||
prompt = text
|
||||
@@ -2165,7 +2220,7 @@ class BatchCLIPSeg:
|
||||
|
||||
# Remove the extra dimensions
|
||||
resized_tensor = resized_tensor[0, 0, :, :]
|
||||
|
||||
pbar.update(1)
|
||||
out.append(resized_tensor)
|
||||
|
||||
results = torch.stack(out).cpu()
|
||||
@@ -2319,12 +2374,7 @@ class OffsetMask:
|
||||
|
||||
return mask,
|
||||
|
||||
class AnyType(str):
|
||||
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
|
||||
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
any = AnyType("*")
|
||||
class WidgetToString:
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
@@ -3266,13 +3316,14 @@ class OffsetMaskByNormalizedAmplitude:
|
||||
|
||||
return offsetmask,
|
||||
|
||||
|
||||
class ImageTransformByNormalizedAmplitude:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"normalized_amp": ("NORMALIZED_AMPLITUDE",),
|
||||
"zoom_scale": ("FLOAT", { "default": 0.0, "min": -1.0, "max": 1.0, "step": 0.001, "display": "number" }),
|
||||
"x_offset": ("INT", { "default": 0, "min": (1 -MAX_RESOLUTION), "max": MAX_RESOLUTION, "step": 1, "display": "number" }),
|
||||
"y_offset": ("INT", { "default": 0, "min": (1 -MAX_RESOLUTION), "max": MAX_RESOLUTION, "step": 1, "display": "number" }),
|
||||
"cumulative": ("BOOLEAN", { "default": False }),
|
||||
"image": ("IMAGE",),
|
||||
}}
|
||||
@@ -3281,7 +3332,7 @@ class ImageTransformByNormalizedAmplitude:
|
||||
FUNCTION = "amptransform"
|
||||
CATEGORY = "KJNodes"
|
||||
|
||||
def amptransform(self, image, normalized_amp, zoom_scale, cumulative):
|
||||
def amptransform(self, image, normalized_amp, zoom_scale, cumulative, x_offset, y_offset):
|
||||
# Ensure normalized_amp is an array and within the range [0, 1]
|
||||
normalized_amp = np.clip(normalized_amp, 0.0, 1.0)
|
||||
transformed_images = []
|
||||
@@ -3325,6 +3376,17 @@ class ImageTransformByNormalizedAmplitude:
|
||||
# Convert the tensor back to BxHxWxC format
|
||||
tensor_img = tensor_img.permute(1, 2, 0)
|
||||
|
||||
# Offset the image based on the amplitude
|
||||
offset_amp = amp * 10 # Calculate the offset magnitude based on the amplitude
|
||||
shift_x = min(x_offset * offset_amp, img.shape[1] - 1) # Calculate the shift in x direction
|
||||
shift_y = min(y_offset * offset_amp, img.shape[0] - 1) # Calculate the shift in y direction
|
||||
|
||||
# Apply the offset to the image tensor
|
||||
if shift_x != 0:
|
||||
tensor_img = torch.roll(tensor_img, shifts=int(shift_x), dims=1)
|
||||
if shift_y != 0:
|
||||
tensor_img = torch.roll(tensor_img, shifts=int(shift_y), dims=0)
|
||||
|
||||
# Add to the list
|
||||
transformed_images.append(tensor_img)
|
||||
|
||||
@@ -3460,7 +3522,7 @@ class GLIGENTextBoxApplyBatch:
|
||||
interpolated_coords = interpolate_coordinates_with_curves(coordinates_dict, batch_size)
|
||||
if interpolation == 'straight':
|
||||
interpolated_coords = interpolate_coordinates(coordinates_dict, batch_size)
|
||||
|
||||
|
||||
plot_image_tensor = plot_to_tensor(coordinates_dict, interpolated_coords, 512, 512, height)
|
||||
for t in conditioning_to:
|
||||
n = [t[0], t[1].copy()]
|
||||
@@ -3471,6 +3533,7 @@ class GLIGENTextBoxApplyBatch:
|
||||
x_position, y_position = interpolated_coords[i]
|
||||
position_param = (cond_pooled, height // 8, width // 8, y_position // 8, x_position // 8)
|
||||
position_params_batch[i].append(position_param) # Append position_param to the correct sublist
|
||||
print("x ",x_position, "y ", y_position)
|
||||
prev = []
|
||||
if "gligen" in n[1]:
|
||||
prev = n[1]['gligen'][2]
|
||||
@@ -3484,6 +3547,101 @@ class GLIGENTextBoxApplyBatch:
|
||||
|
||||
return (c, plot_image_tensor,)
|
||||
|
||||
class ImageUpscaleWithModelBatched:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "upscale_model": ("UPSCALE_MODEL",),
|
||||
"images": ("IMAGE",),
|
||||
"per_batch": ("INT", {"default": 16, "min": 1, "max": 4096, "step": 1}),
|
||||
}}
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "upscale"
|
||||
|
||||
CATEGORY = "KJNodes"
|
||||
|
||||
def upscale(self, upscale_model, images, per_batch):
|
||||
|
||||
device = comfy.model_management.get_torch_device()
|
||||
upscale_model.to(device)
|
||||
in_img = images.movedim(-1,-3).to(device)
|
||||
|
||||
steps = in_img.shape[0]
|
||||
pbar = comfy.utils.ProgressBar(steps)
|
||||
t = []
|
||||
|
||||
for start_idx in range(0, in_img.shape[0], per_batch):
|
||||
sub_images = upscale_model(in_img[start_idx:start_idx+per_batch])
|
||||
t.append(sub_images.cpu())
|
||||
# Calculate the number of images processed in this batch
|
||||
batch_count = sub_images.shape[0]
|
||||
# Update the progress bar by the number of images processed in this batch
|
||||
pbar.update(batch_count)
|
||||
upscale_model.cpu()
|
||||
|
||||
t = torch.cat(t, dim=0).permute(0, 2, 3, 1).cpu()
|
||||
|
||||
return (t,)
|
||||
|
||||
|
||||
import torchvision
|
||||
from torch import nn
|
||||
from safetensors import safe_open
|
||||
|
||||
class EfficientNetEncoder(nn.Module):
|
||||
def __init__(self, c_latent=16):
|
||||
super().__init__()
|
||||
self.backbone = torchvision.models.efficientnet_v2_s(weights='DEFAULT').features.eval()
|
||||
self.mapper = nn.Sequential(
|
||||
nn.Conv2d(1280, c_latent, kernel_size=1, bias=False),
|
||||
nn.BatchNorm2d(c_latent, affine=False), # then normalize them to have mean 0 and std 1
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.mapper(self.backbone(x))
|
||||
|
||||
class EffnetEncode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE",),
|
||||
}}
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
FUNCTION = "encode"
|
||||
|
||||
CATEGORY = "KJNodes"
|
||||
|
||||
def encode(self, image):
|
||||
device = comfy.model_management.get_torch_device()
|
||||
|
||||
image = image.permute(0, 3, 1, 2).to(device)
|
||||
effnet_preprocess = torchvision.transforms.Compose([
|
||||
torchvision.transforms.Normalize(
|
||||
mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)
|
||||
)
|
||||
])
|
||||
image = effnet_preprocess(image)
|
||||
effnet = EfficientNetEncoder()
|
||||
|
||||
effnet_checkpoint = os.path.join(folder_paths.models_dir,"vae", "Stable-cascade","effnet_encoder.safetensors")
|
||||
if not os.path.exists(effnet_checkpoint):
|
||||
try:
|
||||
from huggingface_hub import snapshot_download
|
||||
download_to = os.path.join(folder_paths.models_dir,'vae', "Stable-cascade")
|
||||
snapshot_download(repo_id="stabilityai/stable-cascade", allow_patterns=["effnet_encoder.safetensors"],
|
||||
local_dir=download_to, local_dir_use_symlinks=False)
|
||||
except:
|
||||
raise Exception("Model not found and download failed. (https://huggingface.co/stabilityai/stable-cascade, effnet_encoder.safetensors)")
|
||||
|
||||
effnet_state_dict = {}
|
||||
with safe_open(effnet_checkpoint, framework="pt", device="cpu") as f:
|
||||
for key in f.keys():
|
||||
effnet_state_dict[key] = f.get_tensor(key)
|
||||
|
||||
effnet.load_state_dict(effnet_state_dict if 'state_dict' not in effnet_checkpoint else effnet_checkpoint['state_dict'])
|
||||
effnet.eval().requires_grad_(False).to(device)
|
||||
t = effnet(image).cpu()
|
||||
return ({"samples":t}, )
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"INTConstant": INTConstant,
|
||||
@@ -3548,7 +3706,10 @@ NODE_CLASS_MAPPINGS = {
|
||||
"GetLatentsFromBatchIndexed": GetLatentsFromBatchIndexed,
|
||||
"StringConstant": StringConstant,
|
||||
"GLIGENTextBoxApplyBatch": GLIGENTextBoxApplyBatch,
|
||||
"CondPassThrough": CondPassThrough
|
||||
"CondPassThrough": CondPassThrough,
|
||||
"ImageUpscaleWithModelBatched": ImageUpscaleWithModelBatched,
|
||||
"ScaleBatchPromptSchedule": ScaleBatchPromptSchedule,
|
||||
"EffnetEncode": EffnetEncode
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"INTConstant": "INT Constant",
|
||||
@@ -3612,5 +3773,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"GetLatentsFromBatchIndexed": "GetLatentsFromBatchIndexed",
|
||||
"StringConstant": "StringConstant",
|
||||
"GLIGENTextBoxApplyBatch": "GLIGENTextBoxApplyBatch",
|
||||
"CondPassThrough": "CondPassThrough"
|
||||
"CondPassThrough": "CondPassThrough",
|
||||
"ImageUpscaleWithModelBatched": "ImageUpscaleWithModelBatched",
|
||||
"ScaleBatchPromptSchedule": "ScaleBatchPromptSchedule",
|
||||
"EffnetEncode": "EffnetEncode"
|
||||
}
|
||||
@@ -29,7 +29,7 @@ except:
|
||||
print(f"[WARN] ComfyUI-Manager: Your ComfyUI version is outdated. Please update to the latest version.")
|
||||
|
||||
|
||||
version = [2, 7]
|
||||
version = [2, 7, 2]
|
||||
version_str = f"V{version[0]}.{version[1]}" + (f'.{version[2]}' if len(version) > 2 else '')
|
||||
print(f"### Loading: ComfyUI-Manager ({version_str})")
|
||||
|
||||
@@ -307,16 +307,19 @@ def print_comfyui_version():
|
||||
global comfy_ui_commit_datetime
|
||||
global comfy_ui_hash
|
||||
|
||||
is_detached = False
|
||||
try:
|
||||
repo = git.Repo(os.path.dirname(folder_paths.__file__))
|
||||
|
||||
comfy_ui_revision = len(list(repo.iter_commits('HEAD')))
|
||||
current_branch = repo.active_branch.name
|
||||
comfy_ui_hash = repo.head.commit.hexsha
|
||||
|
||||
comfy_ui_hash = repo.head.commit.hexsha
|
||||
cm_global.variables['comfyui.revision'] = comfy_ui_revision
|
||||
|
||||
comfy_ui_commit_datetime = repo.head.commit.committed_datetime
|
||||
cm_global.variables['comfyui.commit_datetime'] = comfy_ui_commit_datetime
|
||||
|
||||
is_detached = repo.head.is_detached
|
||||
current_branch = repo.active_branch.name
|
||||
|
||||
try:
|
||||
if comfy_ui_commit_datetime.date() < comfy_ui_required_commit_datetime.date():
|
||||
@@ -343,7 +346,10 @@ def print_comfyui_version():
|
||||
else:
|
||||
print(f"### ComfyUI Revision: {comfy_ui_revision} on '{current_branch}' [{comfy_ui_hash[:8]}] | Released on '{comfy_ui_commit_datetime.date()}'")
|
||||
except:
|
||||
print("### ComfyUI Revision: UNKNOWN (The currently installed ComfyUI is not a Git repository)")
|
||||
if is_detached:
|
||||
print(f"### ComfyUI Revision: {comfy_ui_revision} [{comfy_ui_hash[:8]}] *DETACHED | Released on '{comfy_ui_commit_datetime.date()}'")
|
||||
else:
|
||||
print("### ComfyUI Revision: UNKNOWN (The currently installed ComfyUI is not a Git repository)")
|
||||
|
||||
|
||||
print_comfyui_version()
|
||||
|
||||
@@ -199,6 +199,11 @@
|
||||
"id":"https://github.com/abyz22/image_control",
|
||||
"tags":"BMAB",
|
||||
"description": "This extension provides some alternative functionalities of the [a/sd-webui-bmab](https://github.com/portu-sim/sd-webui-bmab) extension."
|
||||
},
|
||||
{
|
||||
"id":"https://github.com/blepping/ComfyUI-sonar",
|
||||
"tags":"sonar",
|
||||
"description": "This extension provides some alternative functionalities of the [a/stable-diffusion-webui-sonar](https://github.com/Kahsolt/stable-diffusion-webui-sonar) extension."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -446,7 +446,7 @@
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom node to convert the lantents between SDXL and SD v1.5 directly without the VAE decoding/encoding step."
|
||||
},
|
||||
},
|
||||
{
|
||||
"author": "city96",
|
||||
"title": "SD-Advanced-Noise",
|
||||
@@ -456,7 +456,7 @@
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes: LatentGaussianNoise, MathEncode. An experimental custom node that generates latent noise directly by utilizing the linear characteristics of the latent space."
|
||||
},
|
||||
},
|
||||
{
|
||||
"author": "city96",
|
||||
"title": "SD-Latent-Upscaler",
|
||||
@@ -467,7 +467,7 @@
|
||||
"pip": ["huggingface-hub"],
|
||||
"install_type": "git-clone",
|
||||
"description": "Upscaling stable diffusion latents using a small neural network."
|
||||
},
|
||||
},
|
||||
{
|
||||
"author": "city96",
|
||||
"title": "ComfyUI_DiT [WIP]",
|
||||
@@ -478,7 +478,7 @@
|
||||
"pip": ["huggingface-hub"],
|
||||
"install_type": "git-clone",
|
||||
"description": "Testbed for [a/DiT(Scalable Diffusion Models with Transformers)](https://github.com/facebookresearch/DiT). [w/None of this code is stable, expect breaking changes if for some reason you want to use this.]"
|
||||
},
|
||||
},
|
||||
{
|
||||
"author": "city96",
|
||||
"title": "ComfyUI_ColorMod",
|
||||
@@ -488,7 +488,7 @@
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This extension currently has two sets of nodes - one set for editing the contrast/color of images and another set for saving images as 16 bit PNG files."
|
||||
},
|
||||
},
|
||||
{
|
||||
"author": "city96",
|
||||
"title": "Extra Models for ComfyUI",
|
||||
@@ -904,6 +904,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI reference implementation for IPAdapter models. The code is mostly taken from the original IPAdapter repository and laksjdjf's implementation, all credit goes to them. I just made the extension closer to ComfyUI philosophy."
|
||||
},
|
||||
{
|
||||
"author": "cubiq",
|
||||
"title": "ComfyUI InstantID (Native Support)",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_InstantID",
|
||||
"files": [
|
||||
"https://github.com/cubiq/ComfyUI_InstantID"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Native [a/InstantID](https://github.com/InstantID/InstantID) support for ComfyUI.\nThis extension differs from the many already available as it doesn't use diffusers but instead implements InstantID natively and it fully integrates with ComfyUI.\nPlease note this still could be considered beta stage, looking forward to your feedback."
|
||||
},
|
||||
{
|
||||
"author": "shockz0rz",
|
||||
"title": "InterpolateEverything",
|
||||
@@ -1497,6 +1507,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "A few nodes to mix sigmas and a custom scheduler that uses phi, then one using eval() to be able to schedule with custom formulas."
|
||||
},
|
||||
{
|
||||
"author": "Extraltodeus",
|
||||
"title": "ComfyUI-AutomaticCFG",
|
||||
"reference": "https://github.com/Extraltodeus/ComfyUI-AutomaticCFG",
|
||||
"files": [
|
||||
"https://github.com/Extraltodeus/ComfyUI-AutomaticCFG"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "My own version 'from scratch' of a self-rescaling CFG. It isn't much but it's honest work.\nTLDR: set your CFG at 8 to try it. No burned images and artifacts anymore. CFG is also a bit more sensitive because it's a proportion around 8. Low scale like 4 also gives really nice results since your CFG is not the CFG anymore. Also in general even with relatively low settings it seems to improve the quality."
|
||||
},
|
||||
{
|
||||
"author": "JPS",
|
||||
"title": "JPS Custom Nodes for ComfyUI",
|
||||
@@ -2297,6 +2317,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Node to use [a/DDColor](https://github.com/piddnad/DDColor) in ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "Kijai",
|
||||
"title": "Animatediff MotionLoRA Trainer",
|
||||
"reference": "https://github.com/kijai/ComfyUI-ADMotionDirector",
|
||||
"files": [
|
||||
"https://github.com/kijai/ComfyUI-ADMotionDirector"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This is a trainer for AnimateDiff MotionLoRAs, based on the implementation of MotionDirector by ExponentialML."
|
||||
},
|
||||
{
|
||||
"author": "hhhzzyang",
|
||||
"title": "Comfyui-Lama",
|
||||
@@ -3010,6 +3040,36 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Easily use Stable Video Diffusion inside ComfyUI!"
|
||||
},
|
||||
{
|
||||
"author": "thecooltechguy",
|
||||
"title": "ComfyUI-ComfyRun",
|
||||
"reference": "https://github.com/thecooltechguy/ComfyUI-ComfyRun",
|
||||
"files": [
|
||||
"https://github.com/thecooltechguy/ComfyUI-ComfyRun"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "The easiest way to run & share any ComfyUI workflow [a/https://comfyrun.com](https://comfyrun.com)"
|
||||
},
|
||||
{
|
||||
"author": "thecooltechguy",
|
||||
"title": "ComfyUI-MagicAnimate",
|
||||
"reference": "https://github.com/thecooltechguy/ComfyUI-MagicAnimate",
|
||||
"files": [
|
||||
"https://github.com/thecooltechguy/ComfyUI-MagicAnimate"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Easily use Magic Animate within ComfyUI!\n[w/WARN: This extension requires 15GB disk space.]"
|
||||
},
|
||||
{
|
||||
"author": "thecooltechguy",
|
||||
"title": "ComfyUI-ComfyWorkflows",
|
||||
"reference": "https://github.com/thecooltechguy/ComfyUI-ComfyWorkflows",
|
||||
"files": [
|
||||
"https://github.com/thecooltechguy/ComfyUI-ComfyWorkflows"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "The best way to run, share, & discover thousands of ComfyUI workflows."
|
||||
},
|
||||
{
|
||||
"author": "Danand",
|
||||
"title": "ComfyUI-ComfyCouple",
|
||||
@@ -3130,6 +3190,36 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "QWen-VL-Plus & QWen-VL-Max in ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "ZHO-ZHO-ZHO",
|
||||
"title": "ComfyUI-SVD-ZHO (WIP)",
|
||||
"reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-SVD-ZHO",
|
||||
"files": [
|
||||
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-SVD-ZHO"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "My Workflows + Auxiliary nodes for Stable Video Diffusion (SVD)"
|
||||
},
|
||||
{
|
||||
"author": "ZHO-ZHO-ZHO",
|
||||
"title": "ComfyUI SegMoE",
|
||||
"reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-SegMoE",
|
||||
"files": [
|
||||
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-SegMoE"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Unofficial implementation of [a/SegMoE: Segmind Mixture of Diffusion Experts](https://github.com/segmind/segmoe) for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "ZHO-ZHO-ZHO",
|
||||
"title": "ComfyUI YoloWorld-EfficientSAM",
|
||||
"reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-YoloWorld-EfficientSAM",
|
||||
"files": [
|
||||
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-YoloWorld-EfficientSAM"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Unofficial implementation of [a/YOLO-World + EfficientSAM](https://huggingface.co/spaces/SkalskiP/YOLO-World) & [a/YOLO-World](https://github.com/AILab-CVC/YOLO-World) for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "kenjiqq",
|
||||
"title": "qq-nodes-comfyui",
|
||||
@@ -3872,36 +3962,6 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "A ComfyUI extension for chatting with your images. Runs on your own system, no external services used, no filter. Uses the [a/LLaVA multimodal LLM](https://llava-vl.github.io/) so you can give instructions or ask questions in natural language. It's maybe as smart as GPT3.5, and it can see."
|
||||
},
|
||||
{
|
||||
"author": "thecooltechguy",
|
||||
"title": "ComfyUI-ComfyRun",
|
||||
"reference": "https://github.com/thecooltechguy/ComfyUI-ComfyRun",
|
||||
"files": [
|
||||
"https://github.com/thecooltechguy/ComfyUI-ComfyRun"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "The easiest way to run & share any ComfyUI workflow [a/https://comfyrun.com](https://comfyrun.com)"
|
||||
},
|
||||
{
|
||||
"author": "thecooltechguy",
|
||||
"title": "ComfyUI-MagicAnimate",
|
||||
"reference": "https://github.com/thecooltechguy/ComfyUI-MagicAnimate",
|
||||
"files": [
|
||||
"https://github.com/thecooltechguy/ComfyUI-MagicAnimate"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Easily use Magic Animate within ComfyUI!\n[w/WARN: This extension requires 15GB disk space.]"
|
||||
},
|
||||
{
|
||||
"author": "thecooltechguy",
|
||||
"title": "ComfyUI-ComfyWorkflows",
|
||||
"reference": "https://github.com/thecooltechguy/ComfyUI-ComfyWorkflows",
|
||||
"files": [
|
||||
"https://github.com/thecooltechguy/ComfyUI-ComfyWorkflows"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "The best way to run, share, & discover thousands of ComfyUI workflows."
|
||||
},
|
||||
{
|
||||
"author": "styler00dollar",
|
||||
"title": "ComfyUI-sudo-latent-upscale",
|
||||
@@ -3993,6 +4053,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "This is a custom node of ComfyUI that downloads and loads models from the input URL. The model is temporarily downloaded into memory and not saved to storage.\nThis could be useful when trying out models or when using various models on machines with limited storage. Since the model is downloaded into memory, expect higher memory usage than usual."
|
||||
},
|
||||
{
|
||||
"author": "pkpkTech",
|
||||
"title": "ComfyUI-SaveQueues",
|
||||
"reference": "https://github.com/pkpkTech/ComfyUI-SaveQueues",
|
||||
"files": [
|
||||
"https://github.com/pkpkTech/ComfyUI-SaveQueues"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Add a button to the menu to save and load the running queue and the pending queues.\nThis is intended to be used when you want to exit ComfyUI with queues still remaining."
|
||||
},
|
||||
{
|
||||
"author": "Crystian",
|
||||
"title": "Crystools",
|
||||
@@ -4184,6 +4254,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:3D Pose Editor"
|
||||
},
|
||||
{
|
||||
"author": "chaojie",
|
||||
"title": "ComfyUI-DynamiCrafter",
|
||||
"reference": "https://github.com/chaojie/ComfyUI-DynamiCrafter",
|
||||
"files": [
|
||||
"https://github.com/chaojie/ComfyUI-DynamiCrafter"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Better Dynamic, Higher Resolution, and Stronger Coherence!"
|
||||
},
|
||||
{
|
||||
"author": "chaojie",
|
||||
"title": "ComfyUI-Panda3d",
|
||||
@@ -4311,8 +4391,9 @@
|
||||
"files": [
|
||||
"https://github.com/MrForExample/ComfyUI-3D-Pack"
|
||||
],
|
||||
"nodename_pattern": "^\\[Comfy3D\\]",
|
||||
"install_type": "git-clone",
|
||||
"description": "An extensive node suite that enables ComfyUI to process 3D inputs (Mesh & UV Texture, etc) using cutting edge algorithms (3DGS, NeRF, etc.)"
|
||||
"description": "An extensive node suite that enables ComfyUI to process 3D inputs (Mesh & UV Texture, etc) using cutting edge algorithms (3DGS, NeRF, etc.)\nNOTE: Pre-built python wheels can be download from [a/https://github.com/remsky/ComfyUI3D-Assorted-Wheels](https://github.com/remsky/ComfyUI3D-Assorted-Wheels)"
|
||||
},
|
||||
{
|
||||
"author": "Mr.ForExample",
|
||||
@@ -4321,6 +4402,7 @@
|
||||
"files": [
|
||||
"https://github.com/MrForExample/ComfyUI-AnimateAnyone-Evolved"
|
||||
],
|
||||
"nodename_pattern": "^\\[AnimateAnyone\\]",
|
||||
"install_type": "git-clone",
|
||||
"description": "Improved AnimateAnyone implementation that allows you to use the opse image sequence and reference image to generate stylized video.\nThe current goal of this project is to achieve desired pose2video result with 1+FPS on GPUs that are equal to or better than RTX 3080!🚀\n[w/The torch environment may be compromised due to version issues as some torch-related packages are being reinstalled.]"
|
||||
},
|
||||
@@ -4334,6 +4416,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes: MS kosmos-2 Interrogator, Save Image w/o Metadata, Image Scale Bounding Box. An implementation of Microsoft [a/kosmos-2](https://huggingface.co/microsoft/kosmos-2-patch14-224) image to text transformer."
|
||||
},
|
||||
{
|
||||
"author": "Hangover3832",
|
||||
"title": "ComfyUI-Hangover-Moondream",
|
||||
"reference": "https://github.com/Hangover3832/ComfyUI-Hangover-Moondream",
|
||||
"files": [
|
||||
"https://github.com/Hangover3832/ComfyUI-Hangover-Moondream"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Moondream is a lightweight multimodal large languge model.\nIMPORTANT:According to the creator, Moondream is for research purposes only, commercial use is not allowed!\n[w/WARN:Additional python code will be downloaded from huggingface and executed. You have to trust this creator if you want to use this node!]"
|
||||
},
|
||||
{
|
||||
"author": "tzwm",
|
||||
"title": "ComfyUI Profiler",
|
||||
@@ -4534,6 +4626,26 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes: Format String, Join String List, Load Preset, Load Preset (Advanced), Const String, Const String (multi line). Add useful nodes related to prompt."
|
||||
},
|
||||
{
|
||||
"author": "nkchocoai",
|
||||
"title": "ComfyUI-TextOnSegs",
|
||||
"reference": "https://github.com/nkchocoai/ComfyUI-TextOnSegs",
|
||||
"files": [
|
||||
"https://github.com/nkchocoai/ComfyUI-TextOnSegs"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Add a node for drawing text with CR Draw Text of ComfyUI_Comfyroll_CustomNodes to the area of SEGS detected by Ultralytics Detector of ComfyUI-Impact-Pack."
|
||||
},
|
||||
{
|
||||
"author": "nkchocoai",
|
||||
"title": "ComfyUI-SaveImageWithMetaData",
|
||||
"reference": "https://github.com/nkchocoai/ComfyUI-SaveImageWithMetaData",
|
||||
"files": [
|
||||
"https://github.com/nkchocoai/ComfyUI-SaveImageWithMetaData"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Add a node to save images with metadata (PNGInfo) extracted from the input values of each node.\nSince the values are extracted dynamically, values output by various extension nodes can be added to metadata."
|
||||
},
|
||||
{
|
||||
"author": "JaredTherriault",
|
||||
"title": "ComfyUI-JNodes",
|
||||
@@ -4774,6 +4886,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Better TAESD previews, BlehHyperTile."
|
||||
},
|
||||
{
|
||||
"author": "blepping",
|
||||
"title": "ComfyUI-sonar",
|
||||
"reference": "https://github.com/blepping/ComfyUI-sonar",
|
||||
"files": [
|
||||
"https://github.com/blepping/ComfyUI-sonar"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A janky implementation of Sonar sampling (momentum-based sampling) for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "JerryOrbachJr",
|
||||
"title": "ComfyUI-RandomSize",
|
||||
@@ -4854,6 +4976,26 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:segformer_clothes, segformer_agnostic, segformer_remove_bg, stabel_vition. Nodes for model dress up."
|
||||
},
|
||||
{
|
||||
"author": "StartHua",
|
||||
"title": "Comfyui_joytag",
|
||||
"reference": "https://github.com/StartHua/Comfyui_joytag",
|
||||
"files": [
|
||||
"https://github.com/StartHua/Comfyui_joytag"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "JoyTag is a state of the art AI vision model for tagging images, with a focus on sex positivity and inclusivity. It uses the Danbooru tagging schema, but works across a wide range of images, from hand drawn to photographic.\nDownload the weight and put it under checkpoints: [a/https://huggingface.co/fancyfeast/joytag/tree/main](https://huggingface.co/fancyfeast/joytag/tree/main)"
|
||||
},
|
||||
{
|
||||
"author": "StartHua",
|
||||
"title": "comfyui_segformer_b2_clothes",
|
||||
"reference": "https://github.com/StartHua/Comfyui_segformer_b2_clothes",
|
||||
"files": [
|
||||
"https://github.com/StartHua/Comfyui_segformer_b2_clothes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "SegFormer model fine-tuned on ATR dataset for clothes segmentation but can also be used for human segmentation!\nDownload the weight and put it under checkpoints: [a/https://huggingface.co/mattmdjaga/segformer_b2_clothes](https://huggingface.co/mattmdjaga/segformer_b2_clothes)"
|
||||
},
|
||||
{
|
||||
"author": "ricklove",
|
||||
"title": "comfyui-ricklove",
|
||||
@@ -4916,7 +5058,7 @@
|
||||
},
|
||||
{
|
||||
"author": "TemryL",
|
||||
"title": "ComfyS3: Amazon S3 Integration for ComfyUI",
|
||||
"title": "ComfyS3",
|
||||
"reference": "https://github.com/TemryL/ComfyS3",
|
||||
"files": [
|
||||
"https://github.com/TemryL/ComfyS3"
|
||||
@@ -4954,8 +5096,198 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "Clip text encoder with BREAK formatting like A1111 (uses conditioning concat)"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"author": "MarkoCa1",
|
||||
"title": "ComfyUI_Segment_Mask",
|
||||
"reference": "https://github.com/MarkoCa1/ComfyUI_Segment_Mask",
|
||||
"files": [
|
||||
"https://github.com/MarkoCa1/ComfyUI_Segment_Mask"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Mask cutout based on Segment Anything."
|
||||
},
|
||||
{
|
||||
"author": "antrobot",
|
||||
"title": "antrobots ComfyUI Nodepack",
|
||||
"reference": "https://github.com/antrobot1234/antrobots-comfyUI-nodepack",
|
||||
"files": [
|
||||
"https://github.com/antrobot1234/antrobots-comfyUI-nodepack"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A small node pack containing various things I felt like ought to be in base comfy-UI. Currently includes Some image handling nodes to help with inpainting, a version of KSampler (advanced) that allows for denoise, and a node that can swap it's inputs. Remember to make an issue if you experience any bugs or errors!"
|
||||
},
|
||||
{
|
||||
"author": "bilal-arikan",
|
||||
"title": "ComfyUI_TextAssets",
|
||||
"reference": "https://github.com/bilal-arikan/ComfyUI_TextAssets",
|
||||
"files": [
|
||||
"https://github.com/bilal-arikan/ComfyUI_TextAssets"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "With this node you can upload text files to input folder from your local computer."
|
||||
},
|
||||
{
|
||||
"author": "kadirnar",
|
||||
"title": "ComfyUI-Transformers",
|
||||
"reference": "https://github.com/kadirnar/ComfyUI-Transformers",
|
||||
"files": [
|
||||
"https://github.com/kadirnar/ComfyUI-Transformers"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI-Transformers is a cutting-edge project combining the power of computer vision and natural language processing to create intuitive and user-friendly interfaces. Our goal is to make technology more accessible and engaging."
|
||||
},
|
||||
{
|
||||
"author": "digitaljohn",
|
||||
"title": "ComfyUI-ProPost",
|
||||
"reference": "https://github.com/digitaljohn/comfyui-propost",
|
||||
"files": [
|
||||
"https://github.com/digitaljohn/comfyui-propost"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A set of custom ComfyUI nodes for performing basic post-processing effects including Film Grain and Vignette. These effects can help to take the edge off AI imagery and make them feel more natural."
|
||||
},
|
||||
{
|
||||
"author": "DonBaronFactory",
|
||||
"title": "ComfyUI-Cre8it-Nodes",
|
||||
"reference": "https://github.com/DonBaronFactory/ComfyUI-Cre8it-Nodes",
|
||||
"files": [
|
||||
"https://github.com/DonBaronFactory/ComfyUI-Cre8it-Nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:CRE8IT Serial Prompter, CRE8IT Apply Serial Prompter, CRE8IT Image Sizer. A few simple nodes to facilitate working wiht ComfyUI Workflows"
|
||||
},
|
||||
{
|
||||
"author": "deforum",
|
||||
"title": "Deforum Nodes",
|
||||
"reference": "https://github.com/XmYx/deforum-comfy-nodes",
|
||||
"files": [
|
||||
"https://github.com/XmYx/deforum-comfy-nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Official Deforum animation pipeline tools that provide a unique way to create frame-by-frame generative motion art."
|
||||
},
|
||||
{
|
||||
"author": "adbrasi",
|
||||
"title": "ComfyUI-TrashNodes-DownloadHuggingface",
|
||||
"reference": "https://github.com/adbrasi/ComfyUI-TrashNodes-DownloadHuggingface",
|
||||
"files": [
|
||||
"https://github.com/adbrasi/ComfyUI-TrashNodes-DownloadHuggingface"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI-TrashNodes-DownloadHuggingface is a ComfyUI node designed to facilitate the download of models you have just trained and uploaded to Hugging Face. This node is particularly useful for users who employ Google Colab for training and need to quickly download their models for deployment."
|
||||
},
|
||||
{
|
||||
"author": "mbrostami",
|
||||
"title": "ComfyUI-HF",
|
||||
"reference": "https://github.com/mbrostami/ComfyUI-HF",
|
||||
"files": [
|
||||
"https://github.com/mbrostami/ComfyUI-HF"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI Node to work with Hugging Face repositories"
|
||||
},
|
||||
{
|
||||
"author": "Billius-AI",
|
||||
"title": "ComfyUI-Path-Helper",
|
||||
"reference": "https://github.com/Billius-AI/ComfyUI-Path-Helper",
|
||||
"files": [
|
||||
"https://github.com/Billius-AI/ComfyUI-Path-Helper"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Create Project Root, Add Folder, Add Folder Advanced, Add File Name Prefix, Add File Name Prefix Advanced, ShowPath"
|
||||
},
|
||||
{
|
||||
"author": "Franck-Demongin",
|
||||
"title": "NX_PromptStyler",
|
||||
"reference": "https://github.com/Franck-Demongin/NX_PromptStyler",
|
||||
"files": [
|
||||
"https://github.com/Franck-Demongin/NX_PromptStyler"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node for ComfyUI to create a prompt based on a list of keywords saved in CSV files."
|
||||
},
|
||||
{
|
||||
"author": "xiaoxiaodesha",
|
||||
"title": "hd-nodes-comfyui",
|
||||
"reference": "https://github.com/xiaoxiaodesha/hd_node",
|
||||
"files": [
|
||||
"https://github.com/xiaoxiaodesha/hd_node"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Combine HDMasks, Cover HDMasks, HD FaceIndex, HD SmoothEdge, HD GetMaskArea, HD Image Levels, HD Ultimate SD Upscale"
|
||||
},
|
||||
{
|
||||
"author": "ShmuelRonen",
|
||||
"title": "ComfyUI-SVDResizer",
|
||||
"reference": "https://github.com/ShmuelRonen/ComfyUI-SVDResizer",
|
||||
"files": [
|
||||
"https://github.com/ShmuelRonen/ComfyUI-SVDResizer"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "SVDResizer is a helper for resizing the source image, according to the sizes enabled in Stable Video Diffusion. The rationale behind the possibility of changing the size of the image in steps between the ranges of 576 and 1024, is the use of the greatest common denominator of these two numbers which is 64. SVD is lenient with resizing that adheres to this rule, so the chance of coherent video that is not the standard size of 576X1024 is greater. It is advisable to keep the value 1024 constant and play with the second size to maintain the stability of the result."
|
||||
},
|
||||
{
|
||||
"author": "redhottensors",
|
||||
"title": "ComfyUI-Prediction",
|
||||
"reference": "https://github.com/redhottensors/ComfyUI-Prediction",
|
||||
"files": [
|
||||
"https://github.com/redhottensors/ComfyUI-Prediction"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Fully customizable Classifier Free Guidance for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "Mamaaaamooooo",
|
||||
"title": "Batch Rembg for ComfyUI",
|
||||
"reference": "https://github.com/Mamaaaamooooo/batchImg-rembg-ComfyUI-nodes",
|
||||
"files": [
|
||||
"https://github.com/Mamaaaamooooo/batchImg-rembg-ComfyUI-nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Remove background of plural images."
|
||||
},
|
||||
{
|
||||
"author": "jordoh",
|
||||
"title": "ComfyUI Deepface",
|
||||
"reference": "https://github.com/jordoh/ComfyUI-Deepface",
|
||||
"files": [
|
||||
"https://github.com/jordoh/ComfyUI-Deepface"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI nodes wrapping the [a/deepface](https://github.com/serengil/deepface) library."
|
||||
},
|
||||
{
|
||||
"author": "yuvraj108c",
|
||||
"title": "ComfyUI-Pronodes",
|
||||
"reference": "https://github.com/yuvraj108c/ComfyUI-Pronodes",
|
||||
"files": [
|
||||
"https://github.com/yuvraj108c/ComfyUI-Pronodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A collection of nice utility nodes for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "GavChap",
|
||||
"title": "ComfyUI-CascadeResolutions",
|
||||
"reference": "https://github.com/GavChap/ComfyUI-CascadeResolutions",
|
||||
"files": [
|
||||
"https://github.com/GavChap/ComfyUI-CascadeResolutions"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Cascade Resolutions"
|
||||
},
|
||||
{
|
||||
"author": "yuvraj108c",
|
||||
"title": "ComfyUI-Vsgan",
|
||||
"reference": "https://github.com/yuvraj108c/ComfyUI-Vsgan",
|
||||
"files": [
|
||||
"https://github.com/yuvraj108c/ComfyUI-Vsgan"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Upscale Video Tensorrt"
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
"author": "Ser-Hilary",
|
||||
@@ -5228,7 +5560,17 @@
|
||||
"install_type": "copy",
|
||||
"description": "A node which takes in x, y, width, height, total width, and total height, in order to accurately represent the area of an image which is covered by area-based conditioning."
|
||||
},
|
||||
|
||||
{
|
||||
"author": "AshMartian",
|
||||
"title": "Dir Gir",
|
||||
"reference": "https://github.com/AshMartian/ComfyUI-DirGir",
|
||||
"files": [
|
||||
"https://github.com/AshMartian/ComfyUI-DirGir/raw/main/dir_picker.py",
|
||||
"https://github.com/AshMartian/ComfyUI-DirGir/raw/main/dir_loop.py"
|
||||
],
|
||||
"install_type": "copy",
|
||||
"description": "A collection of ComfyUI directory automation utility nodes. Directory Get It Right adds a GUI directory browser, and smart directory loop/iteration node that supports regex and file extension filtering."
|
||||
},
|
||||
|
||||
{
|
||||
"author": "theally",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1052,7 +1052,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
||||
onclick: (e) => {
|
||||
const last_visited_site = localStorage.getItem("wg_last_visited")
|
||||
if (!!last_visited_site) {
|
||||
window.open(last_visited_site, "comfyui-workflow-gallery");
|
||||
window.open(last_visited_site, last_visited_site);
|
||||
} else {
|
||||
this.handleWorkflowGalleryButtonClick(e)
|
||||
}
|
||||
@@ -1179,7 +1179,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
||||
callback: () => {
|
||||
const url = "https://openart.ai/workflows/dev";
|
||||
localStorage.setItem("wg_last_visited", url);
|
||||
window.open(url, "comfyui-workflow-gallery");
|
||||
window.open(url, url);
|
||||
modifyButtonStyle(url);
|
||||
},
|
||||
},
|
||||
@@ -1188,7 +1188,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
||||
callback: () => {
|
||||
const url = "https://youml.com/?from=comfyui-share";
|
||||
localStorage.setItem("wg_last_visited", url);
|
||||
window.open(url, "comfyui-workflow-gallery");
|
||||
window.open(url, url);
|
||||
modifyButtonStyle(url);
|
||||
},
|
||||
},
|
||||
@@ -1197,7 +1197,16 @@ class ManagerMenuDialog extends ComfyDialog {
|
||||
callback: () => {
|
||||
const url = "https://comfyworkflows.com/";
|
||||
localStorage.setItem("wg_last_visited", url);
|
||||
window.open(url, "comfyui-workflow-gallery");
|
||||
window.open(url, url);
|
||||
modifyButtonStyle(url);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Open 'flowt.ai'",
|
||||
callback: () => {
|
||||
const url = "https://flowt.ai/";
|
||||
localStorage.setItem("wg_last_visited", url);
|
||||
window.open(url, url);
|
||||
modifyButtonStyle(url);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -270,6 +270,138 @@
|
||||
"filename": "easynegative.safetensors",
|
||||
"url": "https://civitai.com/api/download/models/9208"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "stabilityai/comfyui_checkpoints/stable_cascade_stage_b.safetensors",
|
||||
"type": "checkpoints",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "checkpoints/Stable-Cascade",
|
||||
"description": "[4.55GB] Stable Cascade stage_b checkpoints",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stable_cascade_stage_b.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/comfyui_checkpoints/stable_cascade_stage_b.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/comfyui_checkpoints/stable_cascade_stage_c.safetensors",
|
||||
"type": "checkpoints",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "checkpoints/Stable-Cascade",
|
||||
"description": "[9.22GB] Stable Cascade stage_c checkpoints",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stable_cascade_stage_c.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/comfyui_checkpoints/stable_cascade_stage_c.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_a.safetensors (VAE)",
|
||||
"type": "VAE",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "vae/Stable-Cascade",
|
||||
"description": "[73.7MB] Stable Cascade: stage_a",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_a.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_a.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: effnet_encoder.safetensors (VAE)",
|
||||
"type": "VAE",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "vae/Stable-Cascade",
|
||||
"description": "[81.5MB] Stable Cascade: effnet_encoder.\nVAE encoder for stage_c latent.",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "effnet_encoder.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/effnet_encoder.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_b.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[6.25GB] Stable Cascade: stage_b",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_b.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_b.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_b_bf16.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[3.13GB] Stable Cascade: stage_b/bf16",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_b_bf16.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_b_bf16.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_b_lite.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[2.8GB] Stable Cascade: stage_b/lite",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_b_lite.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_b_lite.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_b_lite.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[1.4GB] Stable Cascade: stage_b/bf16,lite",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_b_lite_bf16.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_b_lite_bf16.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_c.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[14.4GB] Stable Cascade: stage_c",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_c.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_c.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_c_bf16.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[7.18GB] Stable Cascade: stage_c/bf16",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_c_bf16.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_c_bf16.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_c_lite.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[4.12GB] Stable Cascade: stage_c/lite",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_c_lite.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_c_lite.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_c_lite.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[2.06GB] Stable Cascade: stage_c/bf16,lite",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_c_lite_bf16.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_c_lite_bf16.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: text_encoder (CLIP)",
|
||||
"type": "clip",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "clip/Stable-Cascade",
|
||||
"description": "[1.39GB] Stable Cascade: text_encoder",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "model.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/text_encoder/model.safetensors"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "SDXL-Turbo 1.0 (fp16)",
|
||||
"type": "checkpoints",
|
||||
@@ -667,8 +799,8 @@
|
||||
"save_path": "default",
|
||||
"description": "TemporalNet was a ControlNet model designed to enhance the temporal consistency of generated outputs",
|
||||
"reference": "https://huggingface.co/CiaraRowles/TemporalNet2",
|
||||
"filename": "temporalnetversion2.ckpt",
|
||||
"url": "https://huggingface.co/CiaraRowles/TemporalNet2/resolve/main/temporalnetversion2.ckpt"
|
||||
"filename": "temporalnetversion2.safetensors",
|
||||
"url": "https://huggingface.co/CiaraRowles/TemporalNet2/resolve/main/temporalnetversion2.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "CiaraRowles/TemporalNet1XL (1.0)",
|
||||
@@ -1246,56 +1378,220 @@
|
||||
},
|
||||
|
||||
{
|
||||
"name": "animatediff/mmd_sd_v14.ckpt (comfyui-animatediff)",
|
||||
"name": "animatediff/mmd_sd_v14.ckpt (comfyui-animatediff) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/comfyui-animatediff/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the ArtVentureX/AnimateDiff extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "AnimateDiff",
|
||||
"description": "Pressing 'install' directly downloads the model from the ArtVentureX/AnimateDiff extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "mm_sd_v14.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v14.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/mm_sd_v15.ckpt (comfyui-animatediff)",
|
||||
"name": "animatediff/mm_sd_v15.ckpt (comfyui-animatediff) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/comfyui-animatediff/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the ArtVentureX/AnimateDiff extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "AnimateDiff",
|
||||
"description": "Pressing 'install' directly downloads the model from the ArtVentureX/AnimateDiff extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "mm_sd_v15.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15.ckpt"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "animatediff/mmd_sd_v14.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "animatediff/mmd_sd_v14.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "mm_sd_v14.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v14.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/mm_sd_v15.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "animatediff/mm_sd_v15.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "mm_sd_v15.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/mm_sd_v15_v2.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "animatediff/mm_sd_v15_v2.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "mm_sd_v15_v2.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15_v2.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v3_sd15_mm.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v3_sd15_mm.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v3_sd15_mm.ckpt"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "animatediff/mm_sdxl_v10_beta.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SDXL",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "mm_sdxl_v10_beta.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sdxl_v10_beta.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "AD_Stabilized_Motion/mm-Stabilized_high.pth (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/manshoety/AD_Stabilized_Motion",
|
||||
"filename": "mm-Stabilized_high.pth",
|
||||
"url": "https://huggingface.co/manshoety/AD_Stabilized_Motion/resolve/main/mm-Stabilized_high.pth"
|
||||
},
|
||||
{
|
||||
"name": "AD_Stabilized_Motion/mm-Stabilized_mid.pth (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/manshoety/AD_Stabilized_Motion",
|
||||
"filename": "mm-Stabilized_mid.pth",
|
||||
"url": "https://huggingface.co/manshoety/AD_Stabilized_Motion/resolve/main/mm-Stabilized_mid.pth"
|
||||
},
|
||||
{
|
||||
"name": "CiaraRowles/temporaldiff-v1-animatediff.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/CiaraRowles/TemporalDiff",
|
||||
"filename": "temporaldiff-v1-animatediff.ckpt",
|
||||
"url": "https://huggingface.co/CiaraRowles/TemporalDiff/resolve/main/temporaldiff-v1-animatediff.ckpt"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "animatediff/v2_lora_PanLeft.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_PanLeft.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_PanLeft.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_PanRight.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_PanRight.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_PanRight.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_RollingAnticlockwise.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_RollingAnticlockwise.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_RollingAnticlockwise.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_RollingClockwise.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_RollingClockwise.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_RollingClockwise.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_TiltDown.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_TiltDown.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_TiltDown.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_TiltUp.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_TiltUp.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_TiltUp.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_ZoomIn.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_ZoomIn.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_ZoomIn.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_ZoomOut.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_ZoomOut.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_ZoomOut.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "LongAnimatediff/lt_long_mm_32_frames.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/Lightricks/LongAnimateDiff",
|
||||
"filename": "lt_long_mm_32_frames.ckpt",
|
||||
"url": "https://huggingface.co/Lightricks/LongAnimateDiff/resolve/main/lt_long_mm_32_frames.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "LongAnimatediff/lt_long_mm_16_64_frames.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/Lightricks/LongAnimateDiff",
|
||||
"filename": "lt_long_mm_16_64_frames.ckpt",
|
||||
"url": "https://huggingface.co/Lightricks/LongAnimateDiff/resolve/main/lt_long_mm_16_64_frames.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "LongAnimatediff/lt_long_mm_16_64_frames_v1.1.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/Lightricks/LongAnimateDiff",
|
||||
"filename": "lt_long_mm_16_64_frames_v1.1.ckpt",
|
||||
"url": "https://huggingface.co/Lightricks/LongAnimateDiff/resolve/main/lt_long_mm_16_64_frames_v1.1.ckpt"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"name": "animatediff/v3_sd15_sparsectrl_rgb.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "controlnet",
|
||||
@@ -1316,16 +1612,6 @@
|
||||
"filename": "v3_sd15_sparsectrl_scribble.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v3_sd15_sparsectrl_scribble.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v3_sd15_mm.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v3_sd15_mm.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v3_sd15_mm.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v3_sd15_adapter.ckpt",
|
||||
"type": "lora",
|
||||
@@ -1337,158 +1623,6 @@
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v3_sd15_adapter.ckpt"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "animatediff/mm_sdxl_v10_beta.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "animatediff",
|
||||
"base": "SDXL",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "mm_sdxl_v10_beta.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sdxl_v10_beta.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "AD_Stabilized_Motion/mm-Stabilized_high.pth (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/manshoety/AD_Stabilized_Motion",
|
||||
"filename": "mm-Stabilized_high.pth",
|
||||
"url": "https://huggingface.co/manshoety/AD_Stabilized_Motion/resolve/main/mm-Stabilized_high.pth"
|
||||
},
|
||||
{
|
||||
"name": "AD_Stabilized_Motion/mm-Stabilized_mid.pth (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/manshoety/AD_Stabilized_Motion",
|
||||
"filename": "mm-Stabilized_mid.pth",
|
||||
"url": "https://huggingface.co/manshoety/AD_Stabilized_Motion/resolve/main/mm-Stabilized_mid.pth"
|
||||
},
|
||||
{
|
||||
"name": "CiaraRowles/temporaldiff-v1-animatediff.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/CiaraRowles/TemporalDiff",
|
||||
"filename": "temporaldiff-v1-animatediff.ckpt",
|
||||
"url": "https://huggingface.co/CiaraRowles/TemporalDiff/resolve/main/temporaldiff-v1-animatediff.ckpt"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "animatediff/v2_lora_PanLeft.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_PanLeft.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_PanLeft.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_PanRight.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_PanRight.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_PanRight.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_RollingAnticlockwise.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_RollingAnticlockwise.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_RollingAnticlockwise.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_RollingClockwise.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_RollingClockwise.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_RollingClockwise.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_TiltDown.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_TiltDown.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_TiltDown.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_TiltUp.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_TiltUp.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_TiltUp.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_ZoomIn.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_ZoomIn.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_ZoomIn.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_ZoomOut.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_ZoomOut.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_ZoomOut.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "LongAnimatediff/lt_long_mm_32_frames.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/Lightricks/LongAnimateDiff",
|
||||
"filename": "lt_long_mm_32_frames.ckpt",
|
||||
"url": "https://huggingface.co/Lightricks/LongAnimateDiff/resolve/main/lt_long_mm_32_frames.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "LongAnimatediff/lt_long_mm_16_64_frames.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/Lightricks/LongAnimateDiff",
|
||||
"filename": "lt_long_mm_16_64_frames.ckpt",
|
||||
"url": "https://huggingface.co/Lightricks/LongAnimateDiff/resolve/main/lt_long_mm_16_64_frames.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "LongAnimatediff/lt_long_mm_16_64_frames_v1.1.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/Lightricks/LongAnimateDiff",
|
||||
"filename": "lt_long_mm_16_64_frames_v1.1.ckpt",
|
||||
"url": "https://huggingface.co/Lightricks/LongAnimateDiff/resolve/main/lt_long_mm_16_64_frames_v1.1.ckpt"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "TencentARC/motionctrl.pth",
|
||||
"type": "checkpoints",
|
||||
@@ -1810,6 +1944,97 @@
|
||||
"reference": "https://huggingface.co/TencentARC/PhotoMaker",
|
||||
"filename": "photomaker-v1.bin",
|
||||
"url": "https://huggingface.co/TencentARC/PhotoMaker/resolve/main/photomaker-v1.bin"
|
||||
},
|
||||
{
|
||||
"name": "1k3d68.onnx",
|
||||
"type": "insightface",
|
||||
"base": "inswapper",
|
||||
"save_path": "insightface/models/antelopev2",
|
||||
"description": "Antelopev2 1k3d68.onnx model for InstantId. (InstantId needs all Antelopev2 models)",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_InstantID#installation",
|
||||
"filename": "1k3d68.onnx",
|
||||
"url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/1k3d68.onnx"
|
||||
},
|
||||
{
|
||||
"name": "2d106det.onnx",
|
||||
"type": "insightface",
|
||||
"base": "inswapper",
|
||||
"save_path": "insightface/models/antelopev2",
|
||||
"description": "Antelopev2 2d106det.onnx model for InstantId. (InstantId needs all Antelopev2 models)",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_InstantID#installation",
|
||||
"filename": "2d106det.onnx",
|
||||
"url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/2d106det.onnx"
|
||||
},
|
||||
{
|
||||
"name": "genderage.onnx",
|
||||
"type": "insightface",
|
||||
"base": "inswapper",
|
||||
"save_path": "insightface/models/antelopev2",
|
||||
"description": "Antelopev2 genderage.onnx model for InstantId. (InstantId needs all Antelopev2 models)",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_InstantID#installation",
|
||||
"filename": "genderage.onnx",
|
||||
"url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/genderage.onnx"
|
||||
},
|
||||
{
|
||||
"name": "glintr100.onnx",
|
||||
"type": "insightface",
|
||||
"base": "inswapper",
|
||||
"save_path": "insightface/models/antelopev2",
|
||||
"description": "Antelopev2 glintr100.onnx model for InstantId. (InstantId needs all Antelopev2 models)",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_InstantID#installation",
|
||||
"filename": "glintr100.onnx",
|
||||
"url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/glintr100.onnx"
|
||||
},
|
||||
{
|
||||
"name": "scrfd_10g_bnkps.onnx",
|
||||
"type": "insightface",
|
||||
"base": "inswapper",
|
||||
"save_path": "insightface/models/antelopev2",
|
||||
"description": "Antelopev2 scrfd_10g_bnkps.onnx model for InstantId. (InstantId needs all Antelopev2 models)",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_InstantID#installation",
|
||||
"filename": "scrfd_10g_bnkps.onnx",
|
||||
"url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/scrfd_10g_bnkps.onnx"
|
||||
},
|
||||
{
|
||||
"name": "ip-adapter.bin",
|
||||
"type": "instantid",
|
||||
"base": "SDXL",
|
||||
"save_path": "instantid",
|
||||
"description": "InstantId main model based on IpAdapter",
|
||||
"reference": "https://huggingface.co/InstantX/InstantID",
|
||||
"filename": "ip-adapter.bin",
|
||||
"url": "https://huggingface.co/InstantX/InstantID/resolve/main/ip-adapter.bin"
|
||||
},
|
||||
{
|
||||
"name": "diffusion_pytorch_model.safetensors",
|
||||
"type": "controlnet",
|
||||
"base": "SDXL",
|
||||
"save_path": "controlnet/instantid",
|
||||
"description": "InstantId controlnet model",
|
||||
"reference": "https://huggingface.co/InstantX/InstantID",
|
||||
"filename": "diffusion_pytorch_model.safetensors",
|
||||
"url": "https://huggingface.co/InstantX/InstantID/resolve/main/ControlNetModel/diffusion_pytorch_model.safetensors"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "efficient_sam_s_cpu.jit [ComfyUI-YoloWorld-EfficientSAM]",
|
||||
"type": "efficient_sam",
|
||||
"base": "efficient_sam",
|
||||
"save_path": "custom_nodes/ComfyUI-YoloWorld-EfficientSAM",
|
||||
"description": "Install efficient_sam_s_cpu.jit into ComfyUI-YoloWorld-EfficientSAM",
|
||||
"reference": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/tree/main",
|
||||
"filename": "efficient_sam_s_cpu.jit",
|
||||
"url": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/resolve/main/efficient_sam_s_cpu.jit"
|
||||
},
|
||||
{
|
||||
"name": "efficient_sam_s_gpu.jit [ComfyUI-YoloWorld-EfficientSAM]",
|
||||
"type": "efficient_sam",
|
||||
"base": "efficient_sam",
|
||||
"save_path": "custom_nodes/ComfyUI-YoloWorld-EfficientSAM",
|
||||
"description": "Install efficient_sam_s_gpu.jit into ComfyUI-YoloWorld-EfficientSAM",
|
||||
"reference": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/tree/main",
|
||||
"filename": "efficient_sam_s_gpu.jit",
|
||||
"url": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/resolve/main/efficient_sam_s_gpu.jit"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
+86
-17
@@ -9,7 +9,56 @@
|
||||
"description": "If you see this message, your ComfyUI-Manager is outdated.\nDev channel provides only the list of the developing nodes. If you want to find the complete node list, please go to the Default channel."
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"author": "huizhang0110",
|
||||
"title": "ComfyUI_Easy_Nodes_hui",
|
||||
"reference": "https://github.com/huizhang0110/ComfyUI_Easy_Nodes_hui",
|
||||
"files": [
|
||||
"https://github.com/huizhang0110/ComfyUI_Easy_Nodes_hui"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:EasyEmptyLatentImage"
|
||||
},
|
||||
{
|
||||
"author": "tuckerdarby",
|
||||
"title": "ComfyUI-TDNodes [WIP]",
|
||||
"reference": "https://github.com/tuckerdarby/ComfyUI-TDNodes",
|
||||
"files": [
|
||||
"https://github.com/tuckerdarby/ComfyUI-TDNodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:KSampler (RAVE), KSampler (TF), Object Tracker, KSampler Batched, Video Tracker Prompt, TemporalNet Preprocessor, Instance Tracker Prompt, Instance Diffusion Loader, Hand Tracker Node"
|
||||
},
|
||||
{
|
||||
"author": "shadowcz007",
|
||||
"title": "comfyui-musicgen",
|
||||
"reference": "https://github.com/shadowcz007/comfyui-musicgen",
|
||||
"files": [
|
||||
"https://github.com/shadowcz007/comfyui-musicgen"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Musicgen"
|
||||
},
|
||||
{
|
||||
"author": "Extraltodeus",
|
||||
"title": "ComfyUI-variableCFGandAntiBurn [WIP]",
|
||||
"reference": "https://github.com/Extraltodeus/ComfyUI-variableCFGandAntiBurn",
|
||||
"files": [
|
||||
"https://github.com/Extraltodeus/ComfyUI-variableCFGandAntiBurn"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Continuous CFG rescaler (pre CFG), Intermediary latent merge (post CFG), Intensity/Brightness limiter (post CFG), Dynamic renoising (post CFG), Automatic CFG scale (pre/post CFG), CFG multiplier per channel (pre CFG), Self-Attention Guidance delayed activation mod (post CFG)"
|
||||
},
|
||||
{
|
||||
"author": "shadowcz007",
|
||||
"title": "comfyui-CLIPSeg",
|
||||
"reference": "https://github.com/shadowcz007/comfyui-CLIPSeg",
|
||||
"files": [
|
||||
"https://github.com/shadowcz007/comfyui-CLIPSeg"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Download [a/CLIPSeg](https://huggingface.co/CIDAS/clipseg-rd64-refined/tree/main), move to : models/clipseg"
|
||||
},
|
||||
{
|
||||
"author": "dezi-ai",
|
||||
"title": "ComfyUI Animate LCM",
|
||||
@@ -18,17 +67,47 @@
|
||||
"https://github.com/dezi-ai/ComfyUI-AnimateLCM"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes: Configurator for AnimateLCM, Renderer for AnimateLCM to render the video."
|
||||
"description": "ComfyUI implementation for [a/AnimateLCM](https://animatelcm.github.io/) [[a/paper](https://arxiv.org/abs/2402.00769)].\b[w/This extension includes a large number of nodes imported from the existing custom nodes, increasing the likelihood of conflicts.]"
|
||||
},
|
||||
{
|
||||
"author": "blepping",
|
||||
"title": "ComfyUI-sonar (WIP)",
|
||||
"reference": "https://github.com/blepping/ComfyUI-sonar",
|
||||
"author": "ZHO-ZHO-ZHO",
|
||||
"title": "ComfyUI-BRIA_AI-RMBG",
|
||||
"reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-BRIA_AI-RMBG",
|
||||
"files": [
|
||||
"https://github.com/blepping/ComfyUI-sonar"
|
||||
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-BRIA_AI-RMBG"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Extremely WIP and untested implementation of Sonar sampling. Currently it may not be even close to working properly. Only supports Euler and Euler Ancestral sampling. See [a/stable-diffusion-webui-sonar](https://github.com/Kahsolt/stable-diffusion-webui-sonar) for a more in-depth explanation."
|
||||
"description": "Unofficial [a/BRIA Background Removal v1.4](https://huggingface.co/briaai/RMBG-1.4) of BRIA RMBG Model for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "stutya",
|
||||
"title": "ComfyUI-Terminal [UNSAFE]",
|
||||
"reference": "https://github.com/stutya/ComfyUI-Terminal",
|
||||
"files": [
|
||||
"https://github.com/stutya/ComfyUI-Terminal"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Run Terminal Commands from ComfyUI.\n[w/This extension poses a risk of executing arbitrary commands through workflow execution. Please be cautious.]"
|
||||
},
|
||||
{
|
||||
"author": "marcueberall",
|
||||
"title": "ComfyUI-BuildPath",
|
||||
"reference": "https://github.com/marcueberall/ComfyUI-BuildPath",
|
||||
"files": [
|
||||
"https://github.com/marcueberall/ComfyUI-BuildPath"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes: Build Path Adv."
|
||||
},
|
||||
{
|
||||
"author": "LotzF",
|
||||
"title": "ComfyUI simple ChatGPT completion [UNSAFE]",
|
||||
"reference": "https://github.com/LotzF/ComfyUI-Simple-Chat-GPT-completion",
|
||||
"files": [
|
||||
"https://github.com/LotzF/ComfyUI-Simple-Chat-GPT-completion"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A simple node to request ChatGPT completions. [w/Do not share your workflows including the API key! I'll take no responsibility for your leaked keys.]"
|
||||
},
|
||||
{
|
||||
"author": "kappa54m",
|
||||
@@ -120,16 +199,6 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "WIP"
|
||||
},
|
||||
{
|
||||
"author": "kadirnar",
|
||||
"title": "ComfyUI-Transformers",
|
||||
"reference": "https://github.com/kadirnar/ComfyUI-Transformers",
|
||||
"files": [
|
||||
"https://github.com/kadirnar/ComfyUI-Transformers"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:DepthEstimation."
|
||||
},
|
||||
{
|
||||
"author": "MrAdamBlack",
|
||||
"title": "CheckProgress [WIP]",
|
||||
|
||||
+20
@@ -10,6 +10,26 @@
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"author": "ccvv804",
|
||||
"title": "ComfyUI StableCascade using diffusers for Low VRAM [DEPRECATED]",
|
||||
"reference": "https://github.com/ccvv804/ComfyUI-DiffusersStableCascade-LowVRAM",
|
||||
"files": [
|
||||
"https://github.com/ccvv804/ComfyUI-DiffusersStableCascade-LowVRAM"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Works with RTX 4070ti 12GB.\nSimple quick wrapper for [a/https://huggingface.co/stabilityai/stable-cascade](https://huggingface.co/stabilityai/stable-cascade)\nComfy is going to implement this properly soon, this repo is just for quick testing for the impatient!"
|
||||
},
|
||||
{
|
||||
"author": "kijai",
|
||||
"title": "ComfyUI StableCascade using diffusers [DEPRECATED]",
|
||||
"reference": "https://github.com/kijai/ComfyUI-DiffusersStableCascade",
|
||||
"files": [
|
||||
"https://github.com/kijai/ComfyUI-DiffusersStableCascade"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Simple quick wrapper for [a/https://huggingface.co/stabilityai/stable-cascade](https://huggingface.co/stabilityai/stable-cascade)\nComfy is going to implement this properly soon, this repo is just for quick testing for the impatient!"
|
||||
},
|
||||
{
|
||||
"author": "solarpush",
|
||||
"title": "comfyui_sendimage_node [REMOVED]",
|
||||
|
||||
+362
-391
@@ -10,6 +10,367 @@
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"author": "ZHO-ZHO-ZHO",
|
||||
"title": "ComfyUI YoloWorld-EfficientSAM",
|
||||
"reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-YoloWorld-EfficientSAM",
|
||||
"files": [
|
||||
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-YoloWorld-EfficientSAM"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Unofficial implementation of [a/YOLO-World + EfficientSAM](https://huggingface.co/spaces/SkalskiP/YOLO-World) & [a/YOLO-World](https://github.com/AILab-CVC/YOLO-World) for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "nkchocoai",
|
||||
"title": "ComfyUI-SaveImageWithMetaData",
|
||||
"reference": "https://github.com/nkchocoai/ComfyUI-SaveImageWithMetaData",
|
||||
"files": [
|
||||
"https://github.com/nkchocoai/ComfyUI-SaveImageWithMetaData"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Add a node to save images with metadata (PNGInfo) extracted from the input values of each node.\nSince the values are extracted dynamically, values output by various extension nodes can be added to metadata."
|
||||
},
|
||||
{
|
||||
"author": "yuvraj108c",
|
||||
"title": "ComfyUI-Vsgan",
|
||||
"reference": "https://github.com/yuvraj108c/ComfyUI-Vsgan",
|
||||
"files": [
|
||||
"https://github.com/yuvraj108c/ComfyUI-Vsgan"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Upscale Video Tensorrt"
|
||||
},
|
||||
{
|
||||
"author": "Kijai",
|
||||
"title": "Animatediff MotionLoRA Trainer",
|
||||
"reference": "https://github.com/kijai/ComfyUI-ADMotionDirector",
|
||||
"files": [
|
||||
"https://github.com/kijai/ComfyUI-ADMotionDirector"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This is a trainer for AnimateDiff MotionLoRAs, based on the implementation of MotionDirector by ExponentialML."
|
||||
},
|
||||
{
|
||||
"author": "GavChap",
|
||||
"title": "ComfyUI-CascadeResolutions",
|
||||
"reference": "https://github.com/GavChap/ComfyUI-CascadeResolutions",
|
||||
"files": [
|
||||
"https://github.com/GavChap/ComfyUI-CascadeResolutions"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Cascade Resolutions"
|
||||
},
|
||||
{
|
||||
"author": "blepping",
|
||||
"title": "ComfyUI-sonar",
|
||||
"reference": "https://github.com/blepping/ComfyUI-sonar",
|
||||
"files": [
|
||||
"https://github.com/blepping/ComfyUI-sonar"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A janky implementation of Sonar sampling (momentum-based sampling) for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "StartHua",
|
||||
"title": "comfyui_segformer_b2_clothes",
|
||||
"reference": "https://github.com/StartHua/Comfyui_segformer_b2_clothes",
|
||||
"files": [
|
||||
"https://github.com/StartHua/Comfyui_segformer_b2_clothes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "SegFormer model fine-tuned on ATR dataset for clothes segmentation but can also be used for human segmentation!\nDownload the weight and put it under checkpoints: [a/https://huggingface.co/mattmdjaga/segformer_b2_clothes](https://huggingface.co/mattmdjaga/segformer_b2_clothes)"
|
||||
},
|
||||
{
|
||||
"author": "AshMartian",
|
||||
"title": "Dir Gir",
|
||||
"reference": "https://github.com/AshMartian/ComfyUI-DirGir",
|
||||
"files": [
|
||||
"https://github.com/AshMartian/ComfyUI-DirGir/raw/main/dir_picker.py",
|
||||
"https://github.com/AshMartian/ComfyUI-DirGir/raw/main/dir_loop.py"
|
||||
],
|
||||
"install_type": "copy",
|
||||
"description": "A collection of ComfyUI directory automation utility nodes. Directory Get It Right adds a GUI directory browser, and smart directory loop/iteration node that supports regex and file extension filtering."
|
||||
},
|
||||
{
|
||||
"author": "ccvv804",
|
||||
"title": "ComfyUI StableCascade using diffusers for Low VRAM",
|
||||
"reference": "https://github.com/ccvv804/ComfyUI-DiffusersStableCascade-LowVRAM",
|
||||
"files": [
|
||||
"https://github.com/ccvv804/ComfyUI-DiffusersStableCascade-LowVRAM"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Works with RTX 4070ti 12GB.\nSimple quick wrapper for [a/https://huggingface.co/stabilityai/stable-cascade](https://huggingface.co/stabilityai/stable-cascade)\nComfy is going to implement this properly soon, this repo is just for quick testing for the impatient!"
|
||||
},
|
||||
{
|
||||
"author": "yuvraj108c",
|
||||
"title": "ComfyUI-Pronodes",
|
||||
"reference": "https://github.com/yuvraj108c/ComfyUI-Pronodes",
|
||||
"files": [
|
||||
"https://github.com/yuvraj108c/ComfyUI-Pronodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A collection of nice utility nodes for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "pkpkTech",
|
||||
"title": "ComfyUI-SaveQueues",
|
||||
"reference": "https://github.com/pkpkTech/ComfyUI-SaveQueues",
|
||||
"files": [
|
||||
"https://github.com/pkpkTech/ComfyUI-SaveQueues"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Add a button to the menu to save and load the running queue and the pending queues.\nThis is intended to be used when you want to exit ComfyUI with queues still remaining."
|
||||
},
|
||||
{
|
||||
"author": "jordoh",
|
||||
"title": "ComfyUI Deepface",
|
||||
"reference": "https://github.com/jordoh/ComfyUI-Deepface",
|
||||
"files": [
|
||||
"https://github.com/jordoh/ComfyUI-Deepface"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI nodes wrapping the [a/deepface](https://github.com/serengil/deepface) library."
|
||||
},
|
||||
{
|
||||
"author": "kijai",
|
||||
"title": "ComfyUI StableCascade using diffusers",
|
||||
"reference": "https://github.com/kijai/ComfyUI-DiffusersStableCascade",
|
||||
"files": [
|
||||
"https://github.com/kijai/ComfyUI-DiffusersStableCascade"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Simple quick wrapper for [a/https://huggingface.co/stabilityai/stable-cascade](https://huggingface.co/stabilityai/stable-cascade)\nComfy is going to implement this properly soon, this repo is just for quick testing for the impatient!"
|
||||
},
|
||||
{
|
||||
"author": "Extraltodeus",
|
||||
"title": "ComfyUI-AutomaticCFG",
|
||||
"reference": "https://github.com/Extraltodeus/ComfyUI-AutomaticCFG",
|
||||
"files": [
|
||||
"https://github.com/Extraltodeus/ComfyUI-AutomaticCFG"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "My own version 'from scratch' of a self-rescaling CFG. It isn't much but it's honest work.\nTLDR: set your CFG at 8 to try it. No burned images and artifacts anymore. CFG is also a bit more sensitive because it's a proportion around 8. Low scale like 4 also gives really nice results since your CFG is not the CFG anymore. Also in general even with relatively low settings it seems to improve the quality."
|
||||
},
|
||||
{
|
||||
"author": "Mamaaaamooooo",
|
||||
"title": "Batch Rembg for ComfyUI",
|
||||
"reference": "https://github.com/Mamaaaamooooo/batchImg-rembg-ComfyUI-nodes",
|
||||
"files": [
|
||||
"https://github.com/Mamaaaamooooo/batchImg-rembg-ComfyUI-nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Remove background of plural images."
|
||||
},
|
||||
{
|
||||
"author": "ShmuelRonen",
|
||||
"title": "ComfyUI-SVDResizer",
|
||||
"reference": "https://github.com/ShmuelRonen/ComfyUI-SVDResizer",
|
||||
"files": [
|
||||
"https://github.com/ShmuelRonen/ComfyUI-SVDResizer"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "SVDResizer is a helper for resizing the source image, according to the sizes enabled in Stable Video Diffusion. The rationale behind the possibility of changing the size of the image in steps between the ranges of 576 and 1024, is the use of the greatest common denominator of these two numbers which is 64. SVD is lenient with resizing that adheres to this rule, so the chance of coherent video that is not the standard size of 576X1024 is greater. It is advisable to keep the value 1024 constant and play with the second size to maintain the stability of the result."
|
||||
},
|
||||
{
|
||||
"author": "xiaoxiaodesha",
|
||||
"title": "hd-nodes-comfyui",
|
||||
"reference": "https://github.com/xiaoxiaodesha/hd_node",
|
||||
"files": [
|
||||
"https://github.com/xiaoxiaodesha/hd_node"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Combine HDMasks, Cover HDMasks, HD FaceIndex, HD SmoothEdge, HD GetMaskArea, HD Image Levels, HD Ultimate SD Upscale"
|
||||
},
|
||||
{
|
||||
"author": "StartHua",
|
||||
"title": "Comfyui_joytag",
|
||||
"reference": "https://github.com/StartHua/Comfyui_joytag",
|
||||
"files": [
|
||||
"https://github.com/StartHua/Comfyui_joytag"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "JoyTag is a state of the art AI vision model for tagging images, with a focus on sex positivity and inclusivity. It uses the Danbooru tagging schema, but works across a wide range of images, from hand drawn to photographic.\nDownload the weight and put it under checkpoints: [a/https://huggingface.co/fancyfeast/joytag/tree/main](https://huggingface.co/fancyfeast/joytag/tree/main)"
|
||||
},
|
||||
{
|
||||
"author": "redhottensors",
|
||||
"title": "ComfyUI-Prediction",
|
||||
"reference": "https://github.com/redhottensors/ComfyUI-Prediction",
|
||||
"files": [
|
||||
"https://github.com/redhottensors/ComfyUI-Prediction"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Fully customizable Classifier Free Guidance for ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "nkchocoai",
|
||||
"title": "ComfyUI-TextOnSegs",
|
||||
"reference": "https://github.com/nkchocoai/ComfyUI-TextOnSegs",
|
||||
"files": [
|
||||
"https://github.com/nkchocoai/ComfyUI-TextOnSegs"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Add a node for drawing text with CR Draw Text of ComfyUI_Comfyroll_CustomNodes to the area of SEGS detected by Ultralytics Detector of ComfyUI-Impact-Pack."
|
||||
},
|
||||
{
|
||||
"author": "cubiq",
|
||||
"title": "ComfyUI InstantID (Native Support)",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_InstantID",
|
||||
"files": [
|
||||
"https://github.com/cubiq/ComfyUI_InstantID"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Native [a/InstantID](https://github.com/InstantID/InstantID) support for ComfyUI.\nThis extension differs from the many already available as it doesn't use diffusers but instead implements InstantID natively and it fully integrates with ComfyUI.\nPlease note this still could be considered beta stage, looking forward to your feedback."
|
||||
},
|
||||
{
|
||||
"author": "Franck-Demongin",
|
||||
"title": "NX_PromptStyler",
|
||||
"reference": "https://github.com/Franck-Demongin/NX_PromptStyler",
|
||||
"files": [
|
||||
"https://github.com/Franck-Demongin/NX_PromptStyler"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A custom node for ComfyUI to create a prompt based on a list of keywords saved in CSV files."
|
||||
},
|
||||
{
|
||||
"author": "Billius-AI",
|
||||
"title": "ComfyUI-Path-Helper",
|
||||
"reference": "https://github.com/Billius-AI/ComfyUI-Path-Helper",
|
||||
"files": [
|
||||
"https://github.com/Billius-AI/ComfyUI-Path-Helper"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Create Project Root, Add Folder, Add Folder Advanced, Add File Name Prefix, Add File Name Prefix Advanced, ShowPath"
|
||||
},
|
||||
{
|
||||
"author": "mbrostami",
|
||||
"title": "ComfyUI-HF",
|
||||
"reference": "https://github.com/mbrostami/ComfyUI-HF",
|
||||
"files": [
|
||||
"https://github.com/mbrostami/ComfyUI-HF"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI Node to work with Hugging Face repositories"
|
||||
},
|
||||
{
|
||||
"author": "digitaljohn",
|
||||
"title": "ComfyUI-ProPost",
|
||||
"reference": "https://github.com/digitaljohn/comfyui-propost",
|
||||
"files": [
|
||||
"https://github.com/digitaljohn/comfyui-propost"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A set of custom ComfyUI nodes for performing basic post-processing effects including Film Grain and Vignette. These effects can help to take the edge off AI imagery and make them feel more natural."
|
||||
},
|
||||
{
|
||||
"author": "deforum",
|
||||
"title": "Deforum Nodes",
|
||||
"reference": "https://github.com/XmYx/deforum-comfy-nodes",
|
||||
"files": [
|
||||
"https://github.com/XmYx/deforum-comfy-nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Official Deforum animation pipeline tools that provide a unique way to create frame-by-frame generative motion art."
|
||||
},
|
||||
{
|
||||
"author": "adbrasi",
|
||||
"title": "ComfyUI-TrashNodes-DownloadHuggingface",
|
||||
"reference": "https://github.com/adbrasi/ComfyUI-TrashNodes-DownloadHuggingface",
|
||||
"files": [
|
||||
"https://github.com/adbrasi/ComfyUI-TrashNodes-DownloadHuggingface"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI-TrashNodes-DownloadHuggingface is a ComfyUI node designed to facilitate the download of models you have just trained and uploaded to Hugging Face. This node is particularly useful for users who employ Google Colab for training and need to quickly download their models for deployment."
|
||||
},
|
||||
{
|
||||
"author": "DonBaronFactory",
|
||||
"title": "ComfyUI-Cre8it-Nodes",
|
||||
"reference": "https://github.com/DonBaronFactory/ComfyUI-Cre8it-Nodes",
|
||||
"files": [
|
||||
"https://github.com/DonBaronFactory/ComfyUI-Cre8it-Nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:CRE8IT Serial Prompter, CRE8IT Apply Serial Prompter, CRE8IT Image Sizer. A few simple nodes to facilitate working wiht ComfyUI Workflows"
|
||||
},
|
||||
{
|
||||
"author": "dezi-ai",
|
||||
"title": "ComfyUI Animate LCM",
|
||||
"reference": "https://github.com/dezi-ai/ComfyUI-AnimateLCM",
|
||||
"files": [
|
||||
"https://github.com/dezi-ai/ComfyUI-AnimateLCM"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI implementation for [a/AnimateLCM](https://animatelcm.github.io/) [[a/paper](https://arxiv.org/abs/2402.00769)]."
|
||||
},
|
||||
{
|
||||
"author": "kadirnar",
|
||||
"title": "ComfyUI-Transformers",
|
||||
"reference": "https://github.com/kadirnar/ComfyUI-Transformers",
|
||||
"files": [
|
||||
"https://github.com/kadirnar/ComfyUI-Transformers"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI-Transformers is a cutting-edge project combining the power of computer vision and natural language processing to create intuitive and user-friendly interfaces. Our goal is to make technology more accessible and engaging."
|
||||
},
|
||||
{
|
||||
"author": "chaojie",
|
||||
"title": "ComfyUI-DynamiCrafter",
|
||||
"reference": "https://github.com/chaojie/ComfyUI-DynamiCrafter",
|
||||
"files": [
|
||||
"https://github.com/chaojie/ComfyUI-DynamiCrafter"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Better Dynamic, Higher Resolution, and Stronger Coherence!"
|
||||
},
|
||||
{
|
||||
"author": "bilal-arikan",
|
||||
"title": "ComfyUI_TextAssets",
|
||||
"reference": "https://github.com/bilal-arikan/ComfyUI_TextAssets",
|
||||
"files": [
|
||||
"https://github.com/bilal-arikan/ComfyUI_TextAssets"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "With this node you can upload text files to input folder from your local computer."
|
||||
},
|
||||
{
|
||||
"author": "ZHO-ZHO-ZHO",
|
||||
"title": "ComfyUI SegMoE",
|
||||
"reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-SegMoE",
|
||||
"files": [
|
||||
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-SegMoE"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Unofficial implementation of [a/SegMoE: Segmind Mixture of Diffusion Experts](https://github.com/segmind/segmoe) for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "ZHO-ZHO-ZHO",
|
||||
"title": "ComfyUI-SVD-ZHO (WIP)",
|
||||
"reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-SVD-ZHO",
|
||||
"files": [
|
||||
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-SVD-ZHO"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "My Workflows + Auxiliary nodes for Stable Video Diffusion (SVD)"
|
||||
},
|
||||
{
|
||||
"author": "MarkoCa1",
|
||||
"title": "ComfyUI_Segment_Mask",
|
||||
"reference": "https://github.com/MarkoCa1/ComfyUI_Segment_Mask",
|
||||
"files": [
|
||||
"https://github.com/MarkoCa1/ComfyUI_Segment_Mask"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Mask cutout based on Segment Anything."
|
||||
},
|
||||
{
|
||||
"author": "antrobot",
|
||||
"title": "antrobots-comfyUI-nodepack",
|
||||
"reference": "https://github.com/antrobot1234/antrobots-comfyUI-nodepack",
|
||||
"files": [
|
||||
"https://github.com/antrobot1234/antrobots-comfyUI-nodepack"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A small node pack containing various things I felt like ought to be in base comfy-UI. Currently includes Some image handling nodes to help with inpainting, a version of KSampler (advanced) that allows for denoise, and a node that can swap it's inputs. Remember to make an issue if you experience any bugs or errors!"
|
||||
},
|
||||
{
|
||||
"author": "dfl",
|
||||
"title": "comfyui-clip-with-break",
|
||||
@@ -52,7 +413,7 @@
|
||||
},
|
||||
{
|
||||
"author": "davask",
|
||||
"title": "MarasIT Nodes",
|
||||
"title": "🐰 MarasIT Nodes",
|
||||
"reference": "https://github.com/davask/ComfyUI-MarasIT-Nodes",
|
||||
"files": [
|
||||
"https://github.com/davask/ComfyUI-MarasIT-Nodes"
|
||||
@@ -359,396 +720,6 @@
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Just a simple substring node that takes text and length as input, and outputs the first length characters."
|
||||
},
|
||||
{
|
||||
"author": "Nlar",
|
||||
"title": "ComfyUI_CartoonSegmentation",
|
||||
"reference": "https://github.com/Nlar/ComfyUI_CartoonSegmentation",
|
||||
"files": [
|
||||
"https://github.com/Nlar/ComfyUI_CartoonSegmentation"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Front end ComfyUI nodes for CartoonSegmentation Based upon the work of the CartoonSegmentation repository this project will provide a front end to some of the features."
|
||||
},
|
||||
{
|
||||
"author": "Acly",
|
||||
"title": "ComfyUI Inpaint Nodes",
|
||||
"reference": "https://github.com/Acly/comfyui-inpaint-nodes",
|
||||
"files": [
|
||||
"https://github.com/Acly/comfyui-inpaint-nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Experimental nodes for better inpainting with ComfyUI. Adds two nodes which allow using [a/Fooocus](https://github.com/Acly/comfyui-inpaint-nodes) inpaint model. It's a small and flexible patch which can be applied to any SDXL checkpoint and will transform it into an inpaint model. This model can then be used like other inpaint models, and provides the same benefits. [a/Read more](https://github.com/lllyasviel/Fooocus/discussions/414)"
|
||||
},
|
||||
{
|
||||
"author": "Abdullah Ozmantar",
|
||||
"title": "InstaSwap Face Swap Node for ComfyUI",
|
||||
"reference": "https://github.com/abdozmantar/ComfyUI-InstaSwap",
|
||||
"files": [
|
||||
"https://github.com/abdozmantar/ComfyUI-InstaSwap"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A quick and easy ComfyUI custom nodes for ultra-quality, lightning-speed face swapping of humans."
|
||||
},
|
||||
{
|
||||
"author": "ZHO-ZHO-ZHO",
|
||||
"title": "ComfyUI PhotoMaker (ZHO)",
|
||||
"reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-PhotoMaker-ZHO",
|
||||
"files": [
|
||||
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-PhotoMaker-ZHO"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Unofficial implementation of [a/PhotoMaker](https://github.com/TencentARC/PhotoMaker) for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "ZHO-ZHO-ZHO",
|
||||
"title": "ComfyUI-InstantID",
|
||||
"reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-InstantID",
|
||||
"files": [
|
||||
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-InstantID"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Unofficial implementation of [a/InstantID](https://github.com/InstantID/InstantID) for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "FlyingFireCo",
|
||||
"title": "tiled_ksampler",
|
||||
"reference": "https://github.com/FlyingFireCo/tiled_ksampler",
|
||||
"files": [
|
||||
"https://github.com/FlyingFireCo/tiled_ksampler"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Tiled KSampler, Asymmetric Tiled KSampler, Circular VAEDecode."
|
||||
},
|
||||
{
|
||||
"author": "abdozmantar",
|
||||
"title": "InstaSwap Face Swap Node for ComfyUI",
|
||||
"reference": "https://github.com/abdozmantar/ComfyUI-InstaSwap",
|
||||
"files": [
|
||||
"https://github.com/abdozmantar/ComfyUI-InstaSwap"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Fastest Face Swap Extension Node for ComfyUI, Single node and FastTrack: Lightning-Speed Facial Transformation for your projects."
|
||||
},
|
||||
{
|
||||
"author": "pkpkTech",
|
||||
"title": "ComfyUI-ngrok",
|
||||
"reference": "https://github.com/pkpkTech/ComfyUI-ngrok",
|
||||
"files": [
|
||||
"https://github.com/pkpkTech/ComfyUI-ngrok"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Use ngrok to allow external access to ComfyUI.\nNOTE: Need to manually modify a token inside the __init__.py file."
|
||||
},
|
||||
{
|
||||
"author": "Daniel Lewis",
|
||||
"title": "ComfyUI-TTS",
|
||||
"reference": "https://github.com/daniel-lewis-ab/ComfyUI-TTS",
|
||||
"files": [
|
||||
"https://github.com/daniel-lewis-ab/ComfyUI-TTS"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Text To Speech (TTS) for ComfyUI"
|
||||
},
|
||||
{
|
||||
"author": "thecooltechguy",
|
||||
"title": "ComfyUI-ComfyWorkflows",
|
||||
"reference": "https://github.com/thecooltechguy/ComfyUI-ComfyWorkflows",
|
||||
"files": [
|
||||
"https://github.com/thecooltechguy/ComfyUI-ComfyWorkflows"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "The best way to run, share, & discover thousands of ComfyUI workflows."
|
||||
},
|
||||
{
|
||||
"author": "Shraknard",
|
||||
"title": "ComfyUI-Remover",
|
||||
"reference": "https://github.com/Shraknard/ComfyUI-Remover",
|
||||
"files": [
|
||||
"https://github.com/Shraknard/ComfyUI-Remover"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Custom node for ComfyUI that makes parts of the image transparent (face, background...)"
|
||||
},
|
||||
{
|
||||
"author": "adriflex",
|
||||
"title": "ComfyUI_Blender_Texdiff",
|
||||
"reference": "https://github.com/adriflex/ComfyUI_Blender_Texdiff",
|
||||
"files": [
|
||||
"https://github.com/adriflex/ComfyUI_Blender_Texdiff"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Blender viewport color, Blender Viewport depth"
|
||||
},
|
||||
{
|
||||
"author": "chaojie",
|
||||
"title": "ComfyUI-RAFT",
|
||||
"reference": "https://github.com/chaojie/ComfyUI-RAFT",
|
||||
"files": [
|
||||
"https://github.com/chaojie/ComfyUI-RAFT"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This is an ComfyUI implementation of RAFT to generate motion brush"
|
||||
},
|
||||
{
|
||||
"author": "DimaChaichan",
|
||||
"title": "LAizypainter-Exporter-ComfyUI",
|
||||
"reference": "https://github.com/DimaChaichan/LAizypainter-Exporter-ComfyUI",
|
||||
"files": [
|
||||
"https://github.com/DimaChaichan/LAizypainter-Exporter-ComfyUI"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This exporter is a plugin for ComfyUI, which can export tasks for [a/LAizypainter](https://github.com/DimaChaichan/LAizypainter).\nLAizypainter is a Photoshop plugin with which you can send tasks directly to a Stable Diffusion server. More information about a [a/Task](https://github.com/DimaChaichan/LAizypainter?tab=readme-ov-file#task)"
|
||||
},
|
||||
{
|
||||
"author": "Qais Malkawi",
|
||||
"title": "ComfyUI-Qais-Helper",
|
||||
"reference": "https://github.com/QaisMalkawi/ComfyUI-QaisHelper",
|
||||
"files": [
|
||||
"https://github.com/QaisMalkawi/ComfyUI-QaisHelper"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This Extension adds a few custom QOL nodes that ComfyUI lacks by default."
|
||||
},
|
||||
{
|
||||
"author": "longgui0318",
|
||||
"title": "comfyui-mask-util",
|
||||
"reference": "https://github.com/longgui0318/comfyui-mask-util",
|
||||
"files": [
|
||||
"https://github.com/longgui0318/comfyui-mask-util"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Split Masks"
|
||||
},
|
||||
{
|
||||
"author": "shiimizu",
|
||||
"title": "ComfyUI PhotoMaker Plus",
|
||||
"reference": "https://github.com/shiimizu/ComfyUI-PhotoMaker-Plus",
|
||||
"files": [
|
||||
"https://github.com/shiimizu/ComfyUI-PhotoMaker-Plus"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI reference implementation for [a/PhotoMaker](https://github.com/TencentARC/PhotoMaker) models. [w/WARN:The repository name has been changed. For those who have previously installed it, please delete custom_nodes/ComfyUI-PhotoMaker from disk and reinstall this.]"
|
||||
},
|
||||
{
|
||||
"author": "darkpixel",
|
||||
"title": "DarkPrompts",
|
||||
"reference": "https://github.com/darkpixel/darkprompts",
|
||||
"files": [
|
||||
"https://github.com/darkpixel/darkprompts"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Slightly better random prompt generation tools that allow combining and picking prompts from both file and text input sources."
|
||||
},
|
||||
{
|
||||
"author": "Taremin",
|
||||
"title": "WebUI Monaco Prompt",
|
||||
"reference": "https://github.com/Taremin/webui-monaco-prompt",
|
||||
"files": [
|
||||
"https://github.com/Taremin/webui-monaco-prompt"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Make it possible to edit the prompt using the Monaco Editor, an editor implementation used in VSCode.\nNOTE: This extension supports both ComfyUI and A1111 simultaneously."
|
||||
},
|
||||
{
|
||||
"author": "JcandZero",
|
||||
"title": "ComfyUI_GLM4Node",
|
||||
"reference": "https://github.com/JcandZero/ComfyUI_GLM4Node",
|
||||
"files": [
|
||||
"https://github.com/JcandZero/ComfyUI_GLM4Node"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "GLM4 Vision Integration"
|
||||
},
|
||||
{
|
||||
"author": "miosp",
|
||||
"title": "ComfyUI-FBCNN",
|
||||
"reference": "https://github.com/Miosp/ComfyUI-FBCNN",
|
||||
"files": [
|
||||
"https://github.com/Miosp/ComfyUI-FBCNN"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A node for JPEG de-artifacting using [a/FBCNN](https://github.com/jiaxi-jiang/FBCNN)."
|
||||
},
|
||||
{
|
||||
"author": "chaojie",
|
||||
"title": "ComfyUI-LightGlue",
|
||||
"reference": "https://github.com/chaojie/ComfyUI-LightGlue",
|
||||
"files": [
|
||||
"https://github.com/chaojie/ComfyUI-LightGlue"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This is an ComfyUI implementation of LightGlue to generate motion brush"
|
||||
},
|
||||
{
|
||||
"author": "Mr.ForExample",
|
||||
"title": "ComfyUI-AnimateAnyone-Evolved",
|
||||
"reference": "https://github.com/MrForExample/ComfyUI-AnimateAnyone-Evolved",
|
||||
"files": [
|
||||
"https://github.com/MrForExample/ComfyUI-AnimateAnyone-Evolved"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Improved AnimateAnyone implementation that allows you to use the opse image sequence and reference image to generate stylized video.\nThe current goal of this project is to achieve desired pose2video result with 1+FPS on GPUs that are equal to or better than RTX 3080!🚀\n[w/The torch environment may be compromised due to version issues as some torch-related packages are being reinstalled.]"
|
||||
},
|
||||
{
|
||||
"author": "chaojie",
|
||||
"title": "ComfyUI-I2VGEN-XL",
|
||||
"reference": "https://github.com/chaojie/ComfyUI-I2VGEN-XL",
|
||||
"files": [
|
||||
"https://github.com/chaojie/ComfyUI-I2VGEN-XL"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This is an implementation of [a/i2vgen-xl](https://github.com/ali-vilab/i2vgen-xl)"
|
||||
},
|
||||
{
|
||||
"author": "Inzaniak",
|
||||
"title": "Ranbooru for ComfyUI",
|
||||
"reference": "https://github.com/Inzaniak/comfyui-ranbooru",
|
||||
"files": [
|
||||
"https://github.com/Inzaniak/comfyui-ranbooru"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Ranbooru is an extension for the comfyUI. The purpose of this extension is to add a node that gets a random set of tags from boorus pictures. This is mostly being used to help me test my checkpoints on a large variety of"
|
||||
},
|
||||
{
|
||||
"author": "Taremin",
|
||||
"title": "ComfyUI String Tools",
|
||||
"reference": "https://github.com/Taremin/comfyui-string-tools",
|
||||
"files": [
|
||||
"https://github.com/Taremin/comfyui-string-tools"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": " This extension provides the StringToolsConcat node, which concatenates multiple texts, and the StringToolsRandomChoice node, which selects one randomly from multiple texts."
|
||||
},
|
||||
{
|
||||
"author": "dave-palt",
|
||||
"title": "comfyui_DSP_imagehelpers",
|
||||
"reference": "https://github.com/dave-palt/comfyui_DSP_imagehelpers",
|
||||
"files": [
|
||||
"https://github.com/dave-palt/comfyui_DSP_imagehelpers"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes: DSP Image Concat"
|
||||
},
|
||||
{
|
||||
"author": "chaojie",
|
||||
"title": "ComfyUI-Moore-AnimateAnyone",
|
||||
"reference": "https://github.com/chaojie/ComfyUI-Moore-AnimateAnyone",
|
||||
"files": [
|
||||
"https://github.com/chaojie/ComfyUI-Moore-AnimateAnyone"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes: Run python tools/download_weights.py first to download weights automatically"
|
||||
},
|
||||
{
|
||||
"author": "chflame163",
|
||||
"title": "ComfyUI Layer Style",
|
||||
"reference": "https://github.com/chflame163/ComfyUI_LayerStyle",
|
||||
"files": [
|
||||
"https://github.com/chflame163/ComfyUI_LayerStyle"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A set of nodes for ComfyUI it generate image like Adobe Photoshop's Layer Style. the Drop Shadow is first completed node, and follow-up work is in progress."
|
||||
},
|
||||
{
|
||||
"author": "kijai",
|
||||
"title": "ComfyUI-DDColor",
|
||||
"reference": "https://github.com/kijai/ComfyUI-DDColor",
|
||||
"files": [
|
||||
"https://github.com/kijai/ComfyUI-DDColor"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Node to use [a/DDColor](https://github.com/piddnad/DDColor) in ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "prozacgod",
|
||||
"title": "ComfyUI Multi-Workspace",
|
||||
"reference": "https://github.com/prozacgod/comfyui-pzc-multiworkspace",
|
||||
"files": [
|
||||
"https://github.com/prozacgod/comfyui-pzc-multiworkspace"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A simple, quick, and dirty implementation of multiple workspaces within ComfyUI."
|
||||
},
|
||||
{
|
||||
"author": "Siberpone",
|
||||
"title": "Lazy Pony Prompter",
|
||||
"reference": "https://github.com/Siberpone/lazy-pony-prompter",
|
||||
"files": [
|
||||
"https://github.com/Siberpone/lazy-pony-prompter"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A pony prompt helper extension for AUTOMATIC1111's Stable Diffusion Web UI and ComfyUI that utilizes the full power of your favorite booru query syntax. Currently supports [a/Derpibooru](https://derpibooru/org) and [a/E621](https://e621.net/)."
|
||||
},
|
||||
{
|
||||
"author": "chaojie",
|
||||
"title": "ComfyUI-MotionCtrl-SVD",
|
||||
"reference": "https://github.com/chaojie/ComfyUI-MotionCtrl-SVD",
|
||||
"files": [
|
||||
"https://github.com/chaojie/ComfyUI-MotionCtrl-SVD"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes: Download the weights of MotionCtrl-SVD [a/motionctrl_svd.ckpt](https://huggingface.co/TencentARC/MotionCtrl/blob/main/motionctrl_svd.ckpt) and put it to ComfyUI/models/checkpoints"
|
||||
},
|
||||
{
|
||||
"author": "JaredTherriault",
|
||||
"title": "ComfyUI-JNodes",
|
||||
"reference": "https://github.com/JaredTherriault/ComfyUI-JNodes",
|
||||
"files": [
|
||||
"https://github.com/JaredTherriault/ComfyUI-JNodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "python and web UX improvements for ComfyUI.\n[w/'DynamicPrompts.js' and 'EditAttention.js' from the core, along with 'ImageFeed.js' and 'favicon.js' from the custom scripts of pythongosssss, are not compatible. Therefore, manual deletion of these files is required to use this web extension.]"
|
||||
},
|
||||
{
|
||||
"author": "nkchocoai",
|
||||
"title": "ComfyUI-SizeFromPresets",
|
||||
"reference": "https://github.com/nkchocoai/ComfyUI-SizeFromPresets",
|
||||
"files": [
|
||||
"https://github.com/nkchocoai/ComfyUI-SizeFromPresets"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Add a node that outputs width and height of the size selected from the preset (.csv)."
|
||||
},
|
||||
{
|
||||
"author": "HAL41",
|
||||
"title": "ComfyUI aichemy nodes",
|
||||
"reference": "https://github.com/HAL41/ComfyUI-aichemy-nodes",
|
||||
"files": [
|
||||
"https://github.com/HAL41/ComfyUI-aichemy-nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Simple node to handle scaling of YOLOv8 segmentation masks"
|
||||
},
|
||||
{
|
||||
"author": "abyz22",
|
||||
"title": "image_control",
|
||||
"reference": "https://github.com/abyz22/image_control",
|
||||
"files": [
|
||||
"https://github.com/abyz22/image_control"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:abyz22_Padding Image, abyz22_ImpactWildcardEncode, abyz22_setimageinfo, abyz22_SaveImage, abyz22_ImpactWildcardEncode_GetPrompt, abyz22_SetQueue, abyz22_drawmask, abyz22_FirstNonNull, abyz22_blendimages, abyz22_blend_onecolor. Please check workflow in [a/https://github.com/abyz22/image_control](https://github.com/abyz22/image_control)"
|
||||
},
|
||||
{
|
||||
"author": "foxtrot-roger",
|
||||
"title": "RF Nodes",
|
||||
"reference": "https://github.com/foxtrot-roger/comfyui-rf-nodes",
|
||||
"files": [
|
||||
"https://github.com/foxtrot-roger/comfyui-rf-nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A bunch of nodes that can be useful to manipulate primitive types (numbers, text, ...) Also some helpers to generate text and timestamps."
|
||||
},
|
||||
{
|
||||
"author": "LarryJane491",
|
||||
"title": "Lora-Training-in-Comfy",
|
||||
"reference": "https://github.com/LarryJane491/Lora-Training-in-Comfy",
|
||||
"files": [
|
||||
"https://github.com/LarryJane491/Lora-Training-in-Comfy"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This custom node lets you train LoRA directly in ComfyUI! By default, it saves directly in your ComfyUI lora folder. That means you just have to refresh after training (...and select the LoRA) to test it!"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+496
-46
File diff suppressed because it is too large
Load Diff
+239
-305
@@ -1,5 +1,208 @@
|
||||
{
|
||||
"models": [
|
||||
{
|
||||
"name": "efficient_sam_s_cpu.jit [ComfyUI-YoloWorld-EfficientSAM]",
|
||||
"type": "efficient_sam",
|
||||
"base": "efficient_sam",
|
||||
"save_path": "custom_nodes/ComfyUI-YoloWorld-EfficientSAM",
|
||||
"description": "Install efficient_sam_s_cpu.jit into ComfyUI-YoloWorld-EfficientSAM",
|
||||
"reference": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/tree/main",
|
||||
"filename": "efficient_sam_s_cpu.jit",
|
||||
"url": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/resolve/main/efficient_sam_s_cpu.jit"
|
||||
},
|
||||
{
|
||||
"name": "efficient_sam_s_gpu.jit [ComfyUI-YoloWorld-EfficientSAM]",
|
||||
"type": "efficient_sam",
|
||||
"base": "efficient_sam",
|
||||
"save_path": "custom_nodes/ComfyUI-YoloWorld-EfficientSAM",
|
||||
"description": "Install efficient_sam_s_gpu.jit into ComfyUI-YoloWorld-EfficientSAM",
|
||||
"reference": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/tree/main",
|
||||
"filename": "efficient_sam_s_gpu.jit",
|
||||
"url": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/resolve/main/efficient_sam_s_gpu.jit"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "stabilityai/comfyui_checkpoints/stable_cascade_stage_b.safetensors",
|
||||
"type": "checkpoints",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "checkpoints/Stable-Cascade",
|
||||
"description": "[4.55GB] Stable Cascade stage_b checkpoints",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stable_cascade_stage_b.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/comfyui_checkpoints/stable_cascade_stage_b.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/comfyui_checkpoints/stable_cascade_stage_c.safetensors",
|
||||
"type": "checkpoints",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "checkpoints/Stable-Cascade",
|
||||
"description": "[9.22GB] Stable Cascade stage_c checkpoints",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stable_cascade_stage_c.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/comfyui_checkpoints/stable_cascade_stage_c.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: effnet_encoder.safetensors (VAE)",
|
||||
"type": "VAE",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "vae/Stable-Cascade",
|
||||
"description": "[81.5MB] Stable Cascade: effnet_encoder.\nVAE encoder for stage_c latent.",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "effnet_encoder.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/effnet_encoder.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_a.safetensors (VAE)",
|
||||
"type": "VAE",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "vae/Stable-Cascade",
|
||||
"description": "[73.7MB] Stable Cascade: stage_a",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_a.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_a.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_b.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[6.25GB] Stable Cascade: stage_b",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_b.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_b.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_b_bf16.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[3.13GB] Stable Cascade: stage_b/bf16",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_b_bf16.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_b_bf16.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_b_lite.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[2.8GB] Stable Cascade: stage_b/lite",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_b_lite.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_b_lite.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_b_lite.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[1.4GB] Stable Cascade: stage_b/bf16,lite",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_b_lite_bf16.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_b_lite_bf16.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_c.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[14.4GB] Stable Cascade: stage_c",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_c.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_c.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_c_bf16.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[7.18GB] Stable Cascade: stage_c/bf16",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_c_bf16.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_c_bf16.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_c_lite.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[4.12GB] Stable Cascade: stage_c/lite",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_c_lite.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_c_lite.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: stage_c_lite.safetensors (UNET)",
|
||||
"type": "unet",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "unet/Stable-Cascade",
|
||||
"description": "[2.06GB] Stable Cascade: stage_c/bf16,lite",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "stage_c_lite_bf16.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/stage_c_lite_bf16.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "stabilityai/Stable Cascade: text_encoder (CLIP)",
|
||||
"type": "clip",
|
||||
"base": "Stable Cascade",
|
||||
"save_path": "clip/Stable-Cascade",
|
||||
"description": "[1.39GB] Stable Cascade: text_encoder",
|
||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||
"filename": "model.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/stable-cascade/resolve/main/text_encoder/model.safetensors"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "1k3d68.onnx",
|
||||
"type": "insightface",
|
||||
"base": "inswapper",
|
||||
"save_path": "insightface/models/antelopev2",
|
||||
"description": "Antelopev2 1k3d68.onnx model for InstantId. (InstantId needs all Antelopev2 models)",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_InstantID#installation",
|
||||
"filename": "1k3d68.onnx",
|
||||
"url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/1k3d68.onnx"
|
||||
},
|
||||
{
|
||||
"name": "2d106det.onnx",
|
||||
"type": "insightface",
|
||||
"base": "inswapper",
|
||||
"save_path": "insightface/models/antelopev2",
|
||||
"description": "Antelopev2 2d106det.onnx model for InstantId. (InstantId needs all Antelopev2 models)",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_InstantID#installation",
|
||||
"filename": "2d106det.onnx",
|
||||
"url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/2d106det.onnx"
|
||||
},
|
||||
{
|
||||
"name": "genderage.onnx",
|
||||
"type": "insightface",
|
||||
"base": "inswapper",
|
||||
"save_path": "insightface/models/antelopev2",
|
||||
"description": "Antelopev2 genderage.onnx model for InstantId. (InstantId needs all Antelopev2 models)",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_InstantID#installation",
|
||||
"filename": "genderage.onnx",
|
||||
"url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/genderage.onnx"
|
||||
},
|
||||
{
|
||||
"name": "glintr100.onnx",
|
||||
"type": "insightface",
|
||||
"base": "inswapper",
|
||||
"save_path": "insightface/models/antelopev2",
|
||||
"description": "Antelopev2 glintr100.onnx model for InstantId. (InstantId needs all Antelopev2 models)",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_InstantID#installation",
|
||||
"filename": "glintr100.onnx",
|
||||
"url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/glintr100.onnx"
|
||||
},
|
||||
{
|
||||
"name": "scrfd_10g_bnkps.onnx",
|
||||
"type": "insightface",
|
||||
"base": "inswapper",
|
||||
"save_path": "insightface/models/antelopev2",
|
||||
"description": "Antelopev2 scrfd_10g_bnkps.onnx model for InstantId. (InstantId needs all Antelopev2 models)",
|
||||
"reference": "https://github.com/cubiq/ComfyUI_InstantID#installation",
|
||||
"filename": "scrfd_10g_bnkps.onnx",
|
||||
"url": "https://huggingface.co/MonsterMMORPG/tools/resolve/main/scrfd_10g_bnkps.onnx"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "photomaker-v1.bin",
|
||||
"type": "photomaker",
|
||||
@@ -175,11 +378,11 @@
|
||||
},
|
||||
|
||||
{
|
||||
"name": "LongAnimatediff/lt_long_mm_16_64_frames_v1.1.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "LongAnimatediff/lt_long_mm_16_64_frames_v1.1.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/Lightricks/LongAnimateDiff",
|
||||
"filename": "lt_long_mm_16_64_frames_v1.1.ckpt",
|
||||
"url": "https://huggingface.co/Lightricks/LongAnimateDiff/resolve/main/lt_long_mm_16_64_frames_v1.1.ckpt"
|
||||
@@ -258,21 +461,21 @@
|
||||
"url": "https://huggingface.co/stabilityai/stable-zero123/resolve/main/stable_zero123.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "LongAnimatediff/lt_long_mm_32_frames.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "LongAnimatediff/lt_long_mm_32_frames.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/Lightricks/LongAnimateDiff",
|
||||
"filename": "lt_long_mm_32_frames.ckpt",
|
||||
"url": "https://huggingface.co/Lightricks/LongAnimateDiff/resolve/main/lt_long_mm_32_frames.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "LongAnimatediff/lt_long_mm_16_64_frames.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "LongAnimatediff/lt_long_mm_16_64_frames.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/Lightricks/LongAnimateDiff",
|
||||
"filename": "lt_long_mm_16_64_frames.ckpt",
|
||||
"url": "https://huggingface.co/Lightricks/LongAnimateDiff/resolve/main/lt_long_mm_16_64_frames.ckpt"
|
||||
@@ -420,363 +623,94 @@
|
||||
},
|
||||
|
||||
{
|
||||
"name": "animatediff/mm_sdxl_v10_beta.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "animatediff/mm_sdxl_v10_beta.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "animatediff",
|
||||
"base": "SDXL",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "mm_sdxl_v10_beta.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sdxl_v10_beta.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_PanLeft.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "animatediff/v2_lora_PanLeft.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_PanLeft.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_PanLeft.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_PanRight.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "animatediff/v2_lora_PanRight.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_PanRight.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_PanRight.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_RollingAnticlockwise.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "animatediff/v2_lora_RollingAnticlockwise.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_RollingAnticlockwise.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_RollingAnticlockwise.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_RollingClockwise.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "animatediff/v2_lora_RollingClockwise.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_RollingClockwise.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_RollingClockwise.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_TiltDown.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "animatediff/v2_lora_TiltDown.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_TiltDown.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_TiltDown.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_TiltUp.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "animatediff/v2_lora_TiltUp.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_TiltUp.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_TiltUp.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_ZoomIn.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "animatediff/v2_lora_ZoomIn.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_ZoomIn.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_ZoomIn.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/v2_lora_ZoomOut.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"name": "animatediff/v2_lora_ZoomOut.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||
"type": "motion lora",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"save_path": "animatediff_motion_lora",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node.",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "v2_lora_ZoomOut.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/v2_lora_ZoomOut.ckpt"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "CiaraRowles/TemporalNet1XL (1.0)",
|
||||
"type": "controlnet",
|
||||
"base": "SD1.5",
|
||||
"save_path": "controlnet/TemporalNet1XL",
|
||||
"description": "This is TemporalNet1XL, it is a re-train of the controlnet TemporalNet1 with Stable Diffusion XL.",
|
||||
"reference": "https://huggingface.co/CiaraRowles/controlnet-temporalnet-sdxl-1.0",
|
||||
"filename": "diffusion_pytorch_model.safetensors",
|
||||
"url": "https://huggingface.co/CiaraRowles/controlnet-temporalnet-sdxl-1.0/resolve/main/diffusion_pytorch_model.safetensors"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "LCM LoRA SD1.5",
|
||||
"type": "lora",
|
||||
"base": "SD1.5",
|
||||
"save_path": "loras/lcm/SD1.5",
|
||||
"description": "Latent Consistency LoRA for SD1.5",
|
||||
"reference": "https://huggingface.co/latent-consistency/lcm-lora-sdv1-5",
|
||||
"filename": "pytorch_lora_weights.safetensors",
|
||||
"url": "https://huggingface.co/latent-consistency/lcm-lora-sdv1-5/resolve/main/pytorch_lora_weights.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "LCM LoRA SSD-1B",
|
||||
"type": "lora",
|
||||
"base": "SSD-1B",
|
||||
"save_path": "loras/lcm/SSD-1B",
|
||||
"description": "Latent Consistency LoRA for SSD-1B",
|
||||
"reference": "https://huggingface.co/latent-consistency/lcm-lora-ssd-1b",
|
||||
"filename": "pytorch_lora_weights.safetensors",
|
||||
"url": "https://huggingface.co/latent-consistency/lcm-lora-ssd-1b/resolve/main/pytorch_lora_weights.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "LCM LoRA SDXL",
|
||||
"type": "lora",
|
||||
"base": "SSD-1B",
|
||||
"save_path": "loras/lcm/SDXL",
|
||||
"description": "Latent Consistency LoRA for SDXL",
|
||||
"reference": "https://huggingface.co/latent-consistency/lcm-lora-sdxl",
|
||||
"filename": "pytorch_lora_weights.safetensors",
|
||||
"url": "https://huggingface.co/latent-consistency/lcm-lora-sdxl/resolve/main/pytorch_lora_weights.safetensors"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "face_yolov8m-seg_60.pt (segm)",
|
||||
"type": "Ultralytics",
|
||||
"base": "Ultralytics",
|
||||
"save_path": "ultralytics/segm",
|
||||
"description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.",
|
||||
"reference": "https://github.com/hben35096/assets/releases/tag/yolo8",
|
||||
"filename": "face_yolov8m-seg_60.pt",
|
||||
"url": "https://github.com/hben35096/assets/releases/download/yolo8/face_yolov8m-seg_60.pt"
|
||||
},
|
||||
{
|
||||
"name": "face_yolov8n-seg2_60.pt (segm)",
|
||||
"type": "Ultralytics",
|
||||
"base": "Ultralytics",
|
||||
"save_path": "ultralytics/segm",
|
||||
"description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.",
|
||||
"reference": "https://github.com/hben35096/assets/releases/tag/yolo8",
|
||||
"filename": "face_yolov8n-seg2_60.pt",
|
||||
"url": "https://github.com/hben35096/assets/releases/download/yolo8/face_yolov8n-seg2_60.pt"
|
||||
},
|
||||
{
|
||||
"name": "hair_yolov8n-seg_60.pt (segm)",
|
||||
"type": "Ultralytics",
|
||||
"base": "Ultralytics",
|
||||
"save_path": "ultralytics/segm",
|
||||
"description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.",
|
||||
"reference": "https://github.com/hben35096/assets/releases/tag/yolo8",
|
||||
"filename": "hair_yolov8n-seg_60.pt",
|
||||
"url": "https://github.com/hben35096/assets/releases/download/yolo8/hair_yolov8n-seg_60.pt"
|
||||
},
|
||||
{
|
||||
"name": "skin_yolov8m-seg_400.pt (segm)",
|
||||
"type": "Ultralytics",
|
||||
"base": "Ultralytics",
|
||||
"save_path": "ultralytics/segm",
|
||||
"description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.",
|
||||
"reference": "https://github.com/hben35096/assets/releases/tag/yolo8",
|
||||
"filename": "skin_yolov8m-seg_400.pt",
|
||||
"url": "https://github.com/hben35096/assets/releases/download/yolo8/skin_yolov8m-seg_400.pt"
|
||||
},
|
||||
{
|
||||
"name": "skin_yolov8n-seg_400.pt (segm)",
|
||||
"type": "Ultralytics",
|
||||
"base": "Ultralytics",
|
||||
"save_path": "ultralytics/segm",
|
||||
"description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.",
|
||||
"reference": "https://github.com/hben35096/assets/releases/tag/yolo8",
|
||||
"filename": "skin_yolov8n-seg_400.pt",
|
||||
"url": "https://github.com/hben35096/assets/releases/download/yolo8/skin_yolov8n-seg_400.pt"
|
||||
},
|
||||
{
|
||||
"name": "skin_yolov8n-seg_800.pt (segm)",
|
||||
"type": "Ultralytics",
|
||||
"base": "Ultralytics",
|
||||
"save_path": "ultralytics/segm",
|
||||
"description": "These are the available models in the UltralyticsDetectorProvider of Impact Pack.",
|
||||
"reference": "https://github.com/hben35096/assets/releases/tag/yolo8",
|
||||
"filename": "skin_yolov8n-seg_800.pt",
|
||||
"url": "https://github.com/hben35096/assets/releases/download/yolo8/skin_yolov8n-seg_800.pt"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "CiaraRowles/temporaldiff-v1-animatediff.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/CiaraRowles/TemporalDiff",
|
||||
"filename": "temporaldiff-v1-animatediff.ckpt",
|
||||
"url": "https://huggingface.co/CiaraRowles/TemporalDiff/resolve/main/temporaldiff-v1-animatediff.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "animatediff/mm_sd_v15_v2.ckpt (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/guoyww/animatediff",
|
||||
"filename": "mm_sd_v15_v2.ckpt",
|
||||
"url": "https://huggingface.co/guoyww/animatediff/resolve/main/mm_sd_v15_v2.ckpt"
|
||||
},
|
||||
{
|
||||
"name": "AD_Stabilized_Motion/mm-Stabilized_high.pth (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/manshoety/AD_Stabilized_Motion",
|
||||
"filename": "mm-Stabilized_high.pth",
|
||||
"url": "https://huggingface.co/manshoety/AD_Stabilized_Motion/resolve/main/mm-Stabilized_high.pth"
|
||||
},
|
||||
{
|
||||
"name": "AD_Stabilized_Motion/mm-Stabilized_mid.pth (ComfyUI-AnimateDiff-Evolved)",
|
||||
"type": "animatediff",
|
||||
"base": "SD1.x",
|
||||
"save_path": "custom_nodes/ComfyUI-AnimateDiff-Evolved/models",
|
||||
"description": "Pressing 'install' directly downloads the model from the Kosinkadink/ComfyUI-AnimateDiff-Evolved extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||
"reference": "https://huggingface.co/manshoety/AD_Stabilized_Motion",
|
||||
"filename": "mm-Stabilized_mid.pth",
|
||||
"url": "https://huggingface.co/manshoety/AD_Stabilized_Motion/resolve/main/mm-Stabilized_mid.pth"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "GFPGANv1.4.pth",
|
||||
"type": "GFPGAN",
|
||||
"base": "GFPGAN",
|
||||
"save_path": "facerestore_models",
|
||||
"description": "Face Restoration Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.",
|
||||
"reference": "https://github.com/TencentARC/GFPGAN/releases",
|
||||
"filename": "GFPGANv1.4.pth",
|
||||
"url": "https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/GFPGANv1.4.pth"
|
||||
},
|
||||
{
|
||||
"name": "codeformer.pth",
|
||||
"type": "CodeFormer",
|
||||
"base": "CodeFormer",
|
||||
"save_path": "facerestore_models",
|
||||
"description": "Face Restoration Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.",
|
||||
"reference": "https://github.com/sczhou/CodeFormer/releases",
|
||||
"filename": "codeformer.pth",
|
||||
"url": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth"
|
||||
},
|
||||
{
|
||||
"name": "detection_Resnet50_Final.pth",
|
||||
"type": "facexlib",
|
||||
"base": "facexlib",
|
||||
"save_path": "facerestore_models",
|
||||
"description": "Face Detection Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.",
|
||||
"reference": "https://github.com/xinntao/facexlib",
|
||||
"filename": "detection_Resnet50_Final.pth",
|
||||
"url": "https://github.com/xinntao/facexlib/releases/download/v0.1.0/detection_Resnet50_Final.pth"
|
||||
},
|
||||
{
|
||||
"name": "detection_mobilenet0.25_Final.pth",
|
||||
"type": "facexlib",
|
||||
"base": "facexlib",
|
||||
"save_path": "facerestore_models",
|
||||
"description": "Face Detection Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.",
|
||||
"reference": "https://github.com/xinntao/facexlib",
|
||||
"filename": "detection_mobilenet0.25_Final.pth",
|
||||
"url": "https://github.com/xinntao/facexlib/releases/download/v0.1.0/detection_mobilenet0.25_Final.pth"
|
||||
},
|
||||
{
|
||||
"name": "yolov5l-face.pth",
|
||||
"type": "facexlib",
|
||||
"base": "facexlib",
|
||||
"save_path": "facedetection",
|
||||
"description": "Face Detection Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.",
|
||||
"reference": "https://github.com/xinntao/facexlib",
|
||||
"filename": "yolov5l-face.pth",
|
||||
"url": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/yolov5l-face.pth"
|
||||
},
|
||||
{
|
||||
"name": "yolov5n-face.pth",
|
||||
"type": "facexlib",
|
||||
"base": "facexlib",
|
||||
"save_path": "facedetection",
|
||||
"description": "Face Detection Models. Download the model required for using the 'Facerestore CF (Code Former)' custom node.",
|
||||
"reference": "https://github.com/xinntao/facexlib",
|
||||
"filename": "yolov5n-face.pth",
|
||||
"url": "https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/yolov5n-face.pth"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "diffusers/stable-diffusion-xl-1.0-inpainting-0.1 (UNET/fp16)",
|
||||
"type": "unet",
|
||||
"base": "SDXL",
|
||||
"save_path": "unet/xl-inpaint-0.1",
|
||||
"description": "[5.14GB] Stable Diffusion XL inpainting model 0.1. You need UNETLoader instead of CheckpointLoader.",
|
||||
"reference": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
|
||||
"filename": "diffusion_pytorch_model.fp16.safetensors",
|
||||
"url": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1/resolve/main/unet/diffusion_pytorch_model.fp16.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "diffusers/stable-diffusion-xl-1.0-inpainting-0.1 (UNET)",
|
||||
"type": "unet",
|
||||
"base": "SDXL",
|
||||
"save_path": "unet/xl-inpaint-0.1",
|
||||
"description": "[10.3GB] Stable Diffusion XL inpainting model 0.1. You need UNETLoader instead of CheckpointLoader.",
|
||||
"reference": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
|
||||
"filename": "diffusion_pytorch_model.safetensors",
|
||||
"url": "https://huggingface.co/diffusers/stable-diffusion-xl-1.0-inpainting-0.1/resolve/main/unet/diffusion_pytorch_model.safetensors"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Inswapper (face swap)",
|
||||
"type": "insightface",
|
||||
"base" : "inswapper",
|
||||
"save_path": "insightface",
|
||||
"description": "Checkpoint of the insightface swapper model (used by Comfy-Roop and comfy_mtb)",
|
||||
"reference": "https://huggingface.co/deepinsight/inswapper/",
|
||||
"filename": "inswapper_128.onnx",
|
||||
"url": "https://huggingface.co/deepinsight/inswapper/resolve/main/inswapper_128.onnx"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "CLIPVision model (stabilityai/clip_vision_g)",
|
||||
"type": "clip_vision",
|
||||
"base": "vit-g",
|
||||
"save_path": "clip_vision",
|
||||
"description": "[3.69GB] clip_g vision model",
|
||||
"reference": "https://huggingface.co/stabilityai/control-lora",
|
||||
"filename": "clip_vision_g.safetensors",
|
||||
"url": "https://huggingface.co/stabilityai/control-lora/resolve/main/revision/clip_vision_g.safetensors"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "CLIPVision model (IP-Adapter) CLIP-ViT-H-14-laion2B-s32B-b79K",
|
||||
"type": "clip_vision",
|
||||
"base": "ViT-H",
|
||||
"save_path": "clip_vision",
|
||||
"description": "[2.5GB] CLIPVision model (needed for IP-Adapter)",
|
||||
"reference": "https://huggingface.co/h94/IP-Adapter",
|
||||
"filename": "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors",
|
||||
"url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/image_encoder/model.safetensors"
|
||||
},
|
||||
{
|
||||
"name": "CLIPVision model (IP-Adapter) CLIP-ViT-bigG-14-laion2B-39B-b160k",
|
||||
"type": "clip_vision",
|
||||
"base": "ViT-G",
|
||||
"save_path": "clip_vision",
|
||||
"description": "[3.69GB] CLIPVision model (needed for IP-Adapter)",
|
||||
"reference": "https://huggingface.co/h94/IP-Adapter",
|
||||
"filename": "CLIP-ViT-bigG-14-laion2B-39B-b160k.safetensors",
|
||||
"url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/image_encoder/model.safetensors"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+20
@@ -99,6 +99,26 @@
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Load Image Dedup"
|
||||
},
|
||||
{
|
||||
"author": "IvanRybakov",
|
||||
"title": "comfyui-node-int-to-string-convertor",
|
||||
"reference": "https://github.com/IvanRybakov/comfyui-node-int-to-string-convertor",
|
||||
"files": [
|
||||
"https://github.com/IvanRybakov/comfyui-node-int-to-string-convertor"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:Int To String Convertor"
|
||||
},
|
||||
{
|
||||
"author": "yowipr",
|
||||
"title": "ComfyUI-Manual",
|
||||
"reference": "https://github.com/yowipr/ComfyUI-Manual",
|
||||
"files": [
|
||||
"https://github.com/yowipr/ComfyUI-Manual"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Nodes:M_Layer, M_Output"
|
||||
}
|
||||
]
|
||||
}
|
||||
+11
-9
@@ -3,6 +3,8 @@ import torch
|
||||
|
||||
import comfy.utils
|
||||
|
||||
from .utils import BIGMIN, BIGMAX
|
||||
|
||||
|
||||
class MergeStrategies:
|
||||
MATCH_A = "match A"
|
||||
@@ -36,7 +38,7 @@ class SplitLatents:
|
||||
return {
|
||||
"required": {
|
||||
"latents": ("LATENT",),
|
||||
"split_index": ("INT", {"default": 0, "step": 1, "min": -99999999999}),
|
||||
"split_index": ("INT", {"default": 0, "step": 1, "min": BIGMIN, "max": BIGMAX}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -61,7 +63,7 @@ class SplitImages:
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"split_index": ("INT", {"default": 0, "step": 1, "min": -99999999999}),
|
||||
"split_index": ("INT", {"default": 0, "step": 1, "min": BIGMIN, "max": BIGMAX}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -83,7 +85,7 @@ class SplitMasks:
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
"split_index": ("INT", {"default": 0, "step": 1, "min": -99999999999}),
|
||||
"split_index": ("INT", {"default": 0, "step": 1, "min": BIGMIN, "max": BIGMAX}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -258,7 +260,7 @@ class SelectEveryNthLatent:
|
||||
return {
|
||||
"required": {
|
||||
"latents": ("LATENT",),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -279,7 +281,7 @@ class SelectEveryNthImage:
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -300,7 +302,7 @@ class SelectEveryNthMask:
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -378,7 +380,7 @@ class DuplicateLatents:
|
||||
return {
|
||||
"required": {
|
||||
"latents": ("LATENT",),
|
||||
"multiply_by": ("INT", {"default": 1, "min": 1, "step": 1})
|
||||
"multiply_by": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,7 +405,7 @@ class DuplicateImages:
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"multiply_by": ("INT", {"default": 1, "min": 1, "step": 1})
|
||||
"multiply_by": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +429,7 @@ class DuplicateMasks:
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
"multiply_by": ("INT", {"default": 1, "min": 1, "step": 1})
|
||||
"multiply_by": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -7,7 +7,7 @@ from PIL import Image, ImageOps
|
||||
import folder_paths
|
||||
from comfy.k_diffusion.utils import FolderOfImages
|
||||
from .logger import logger
|
||||
from .utils import calculate_file_hash, get_sorted_dir_files_from_directory, validate_path
|
||||
from .utils import BIGMAX, calculate_file_hash, get_sorted_dir_files_from_directory, validate_path
|
||||
|
||||
|
||||
def is_changed_load_images(directory: str, image_load_cap: int = 0, skip_first_images: int = 0, select_every_nth: int = 1):
|
||||
@@ -93,9 +93,9 @@ class LoadImagesFromDirectoryUpload:
|
||||
"directory": (directories,),
|
||||
},
|
||||
"optional": {
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"skip_first_images": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"skip_first_images": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,9 +127,9 @@ class LoadImagesFromDirectoryPath:
|
||||
"directory": ("STRING", {"default": "X://path/to/images", "vhs_path_extensions": []}),
|
||||
},
|
||||
"optional": {
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"skip_first_images": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"image_load_cap": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"skip_first_images": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -8,7 +8,7 @@ import cv2
|
||||
import folder_paths
|
||||
from comfy.utils import common_upscale
|
||||
from .logger import logger
|
||||
from .utils import calculate_file_hash, get_sorted_dir_files_from_directory, get_audio, lazy_eval, hash_path, validate_path
|
||||
from .utils import BIGMAX, DIMMAX, calculate_file_hash, get_sorted_dir_files_from_directory, get_audio, lazy_eval, hash_path, validate_path
|
||||
|
||||
|
||||
video_extensions = ['webm', 'mp4', 'mkv', 'gif']
|
||||
@@ -156,13 +156,13 @@ class LoadVideoUpload:
|
||||
files.append(f)
|
||||
return {"required": {
|
||||
"video": (sorted(files),),
|
||||
"force_rate": ("INT", {"default": 0, "min": 0, "max": 24, "step": 1}),
|
||||
"force_rate": ("INT", {"default": 0, "min": 0, "max": 60, "step": 1}),
|
||||
"force_size": (["Disabled", "Custom Height", "Custom Width", "Custom", "256x?", "?x256", "256x256", "512x?", "?x512", "512x512"],),
|
||||
"custom_width": ("INT", {"default": 512, "min": 0, "step": 8}),
|
||||
"custom_height": ("INT", {"default": 512, "min": 0, "step": 8}),
|
||||
"frame_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"skip_first_frames": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"custom_width": ("INT", {"default": 512, "min": 0, "max": DIMMAX, "step": 8}),
|
||||
"custom_height": ("INT", {"default": 512, "min": 0, "max": DIMMAX, "step": 8}),
|
||||
"frame_load_cap": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"skip_first_frames": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"batch_manager": ("VHS_BatchManager",)
|
||||
@@ -200,13 +200,13 @@ class LoadVideoPath:
|
||||
return {
|
||||
"required": {
|
||||
"video": ("STRING", {"default": "X://insert/path/here.mp4", "vhs_path_extensions": video_extensions}),
|
||||
"force_rate": ("INT", {"default": 0, "min": 0, "max": 24, "step": 1}),
|
||||
"force_rate": ("INT", {"default": 0, "min": 0, "max": 60, "step": 1}),
|
||||
"force_size": (["Disabled", "Custom Height", "Custom Width", "Custom", "256x?", "?x256", "256x256", "512x?", "?x512", "512x512"],),
|
||||
"custom_width": ("INT", {"default": 512, "min": 0, "step": 8}),
|
||||
"custom_height": ("INT", {"default": 512, "min": 0, "step": 8}),
|
||||
"frame_load_cap": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"skip_first_frames": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "step": 1}),
|
||||
"custom_width": ("INT", {"default": 512, "min": 0, "max": DIMMAX, "step": 8}),
|
||||
"custom_height": ("INT", {"default": 512, "min": 0, "max": DIMMAX, "step": 8}),
|
||||
"frame_load_cap": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"skip_first_frames": ("INT", {"default": 0, "min": 0, "max": BIGMAX, "step": 1}),
|
||||
"select_every_nth": ("INT", {"default": 1, "min": 1, "max": BIGMAX, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"batch_manager": ("VHS_BatchManager",)
|
||||
|
||||
+32
-7
@@ -4,8 +4,9 @@ import json
|
||||
import subprocess
|
||||
import numpy as np
|
||||
import re
|
||||
import datetime
|
||||
from typing import List
|
||||
from PIL import Image
|
||||
from PIL import Image, ExifTags
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
from pathlib import Path
|
||||
|
||||
@@ -15,7 +16,7 @@ from .image_latent_nodes import *
|
||||
from .load_video_nodes import LoadVideoUpload, LoadVideoPath
|
||||
from .load_images_nodes import LoadImagesFromDirectoryUpload, LoadImagesFromDirectoryPath
|
||||
from .batched_nodes import VAEEncodeBatched, VAEDecodeBatched
|
||||
from .utils import ffmpeg_path, get_audio, hash_path, validate_path, requeue_workflow
|
||||
from .utils import ffmpeg_path, get_audio, hash_path, validate_path, requeue_workflow, gifski_path
|
||||
|
||||
folder_paths.folder_names_and_paths["VHS_video_formats"] = (
|
||||
[
|
||||
@@ -45,6 +46,9 @@ def get_video_formats():
|
||||
video_format_path = folder_paths.get_full_path("VHS_video_formats", format_name + ".json")
|
||||
with open(video_format_path, 'r') as stream:
|
||||
video_format = json.load(stream)
|
||||
if "gifski_pass" in video_format and gifski_path is None:
|
||||
#Skip format
|
||||
continue
|
||||
widgets = [w[0] for w in gen_format_widgets(video_format)]
|
||||
if (len(widgets) > 0):
|
||||
formats.append(["video/" + format_name, widgets])
|
||||
@@ -106,7 +110,7 @@ def ffmpeg_process(args, video_format, video_metadata, file_path, env):
|
||||
with open(metadata_path, "w") as f:
|
||||
f.write(";FFMETADATA1\n")
|
||||
f.write(metadata)
|
||||
m_args = args[:1] + ["-i", metadata_path] + args[1:]
|
||||
m_args = args[:1] + ["-i", metadata_path] + args[1:] + ["-metadata", "creation_time=now"]
|
||||
with subprocess.Popen(m_args + [file_path], stderr=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE, env=env) as proc:
|
||||
try:
|
||||
@@ -223,6 +227,7 @@ class VideoCombine:
|
||||
for x in extra_pnginfo:
|
||||
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
|
||||
video_metadata[x] = extra_pnginfo[x]
|
||||
metadata.add_text("CreationTime", datetime.datetime.now().isoformat(" ")[:19])
|
||||
|
||||
if batch_manager is not None and unique_id in batch_manager.outputs:
|
||||
(counter, output_process) = batch_manager.outputs[unique_id]
|
||||
@@ -263,6 +268,11 @@ class VideoCombine:
|
||||
image_kwargs = {}
|
||||
if format_ext == "gif":
|
||||
image_kwargs['disposal'] = 2
|
||||
if format_ext == "webp":
|
||||
#Save timestamp information
|
||||
exif = Image.Exif()
|
||||
exif[ExifTags.IFD.Exif] = {36867: datetime.datetime.now().isoformat(" ")[:19]}
|
||||
image_kwargs['exif'] = exif
|
||||
file = f"{filename}_{counter:05}.{format_ext}"
|
||||
file_path = os.path.join(full_output_folder, file)
|
||||
images = tensor_to_bytes(images)
|
||||
@@ -365,11 +375,26 @@ class VideoCombine:
|
||||
|
||||
output_files.append(file_path)
|
||||
|
||||
if "gifski_pass" in video_format:
|
||||
gif_output = f"{filename}_{counter:05}.gif"
|
||||
gif_output_path = os.path.join( full_output_folder, gif_output)
|
||||
gifski_args = [gifski_path] + video_format["gifski_pass"] \
|
||||
+ ["-o", gif_output_path, file_path]
|
||||
try:
|
||||
res = subprocess.run(gifski_args, env=env, check=True, capture_output=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise Exception("An error occured in the gifski subprocess:\n" \
|
||||
+ e.stderr.decode("utf-8"))
|
||||
if res.stderr:
|
||||
print(res.stderr.decode("utf-8"), end="", file=sys.stderr)
|
||||
#output format is actually an image and should be correctly marked
|
||||
#TODO: Evaluate a more consistent solution for this
|
||||
format = "image/gif"
|
||||
output_files.append(gif_output_path)
|
||||
file = gif_output
|
||||
|
||||
# Audio Injection after video is created, saves additional video with -audio.mp4
|
||||
|
||||
# Create audio file if input was provided
|
||||
if audio:
|
||||
elif audio is not None and audio() is not False:
|
||||
# Create audio file if input was provided
|
||||
output_file_with_audio = f"{filename}_{counter:05}-audio.{video_format['extension']}"
|
||||
output_file_with_audio_path = os.path.join(full_output_folder, output_file_with_audio)
|
||||
if "audio_pass" not in video_format:
|
||||
|
||||
+22
-4
@@ -8,6 +8,11 @@ import re
|
||||
import server
|
||||
from .logger import logger
|
||||
|
||||
BIGMIN = -(2**53-1)
|
||||
BIGMAX = (2**53-1)
|
||||
|
||||
DIMMAX = 8192
|
||||
|
||||
def ffmpeg_suitability(path):
|
||||
try:
|
||||
version = subprocess.run([path, "-version"], check=True,
|
||||
@@ -31,7 +36,7 @@ def ffmpeg_suitability(path):
|
||||
|
||||
|
||||
if "VHS_FORCE_FFMPEG_PATH" in os.environ:
|
||||
ffmpeg_path = os.env["VHS_FORCE_FFMPEG_PATH"]
|
||||
ffmpeg_path = os.environ.get("VHS_FORCE_FFMPEG_PATH")
|
||||
else:
|
||||
ffmpeg_paths = []
|
||||
try:
|
||||
@@ -51,9 +56,17 @@ else:
|
||||
if len(ffmpeg_paths) == 0:
|
||||
logger.error("No valid ffmpeg found.")
|
||||
ffmpeg_path = None
|
||||
elif len(ffmpeg_paths) == 1:
|
||||
#Evaluation of suitability isn't required, can take sole option
|
||||
#to reduce startup time
|
||||
ffmpeg_path = ffmpeg_paths[0]
|
||||
else:
|
||||
ffmpeg_path = max(ffmpeg_paths, key=ffmpeg_suitability)
|
||||
|
||||
gifski_path = os.environ.get("VHS_GIFSKI", None)
|
||||
if gifski_path is None:
|
||||
gifski_path = os.environ.get("JOV_GIFSKI", None)
|
||||
if gifski_path is None:
|
||||
gifski_path = shutil.which("gifski")
|
||||
|
||||
def get_sorted_dir_files_from_directory(directory: str, skip_first_images: int=0, select_every_nth: int=1, extensions: Iterable=None):
|
||||
directory = directory.strip()
|
||||
@@ -131,8 +144,13 @@ def get_audio(file, start_time=0, duration=0):
|
||||
args += ["-ss", str(start_time)]
|
||||
if duration > 0:
|
||||
args += ["-t", str(duration)]
|
||||
return subprocess.run(args + ["-f", "wav", "-"],
|
||||
stdout=subprocess.PIPE, check=True).stdout
|
||||
try:
|
||||
res = subprocess.run(args + ["-f", "wav", "-"],
|
||||
stdout=subprocess.PIPE, check=True).stdout
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.warning(f"Failed to extract audio from: {file}")
|
||||
return False
|
||||
return res
|
||||
|
||||
|
||||
def lazy_eval(func):
|
||||
|
||||
@@ -7,7 +7,7 @@ import numpy as np
|
||||
import pandas as pd
|
||||
import re
|
||||
|
||||
from .ScheduleFuncs import addWeighted, check_is_number, parse_weight, prepare_prompt, SDXLencode, reverseConcatenation
|
||||
from .ScheduleFuncs import *
|
||||
|
||||
def prepare_batch_prompt(prompt_series, max_frames, frame_idx, prompt_weight_1=0, prompt_weight_2=0, prompt_weight_3=0,
|
||||
prompt_weight_4=0): # calculate expressions from the text input and return a string
|
||||
@@ -19,9 +19,9 @@ def prepare_batch_prompt(prompt_series, max_frames, frame_idx, prompt_weight_1=0
|
||||
for match in regex.finditer(prompt_parsed):
|
||||
matched_string = match.group(0)
|
||||
parsed_string = matched_string.replace('t', f'{frame_idx}').replace("pw_a", f"{prompt_weight_1}").replace("pw_b",
|
||||
f"{prompt_weight_2}").replace(
|
||||
"pw_c", f"{prompt_weight_3}").replace("pw_d", f"{prompt_weight_4}").replace("max_f", f"{max_f}").replace('`',
|
||||
'') # replace t, max_f and `` respectively
|
||||
f"{prompt_weight_2}").replace("pw_c", f"{prompt_weight_3}").replace("pw_d",
|
||||
f"{prompt_weight_4}").replace("max_f",
|
||||
f"{max_f}").replace('`', '') # replace t, max_f and `` respectively
|
||||
parsed_value = numexpr.evaluate(parsed_string)
|
||||
prompt_parsed = prompt_parsed.replace(matched_string, str(parsed_value))
|
||||
return prompt_parsed.strip()
|
||||
@@ -67,6 +67,8 @@ def interpolate_prompt_series(animation_prompts, max_frames, start_frame, pre_te
|
||||
|
||||
max_f = max_frames # needed for numexpr even though it doesn't look like it's in use.
|
||||
parsed_animation_prompts = {}
|
||||
|
||||
|
||||
for key, value in animation_prompts.items():
|
||||
if check_is_number(key): # default case 0:(1 + t %5), 30:(5-t%2)
|
||||
parsed_animation_prompts[key] = value
|
||||
@@ -101,16 +103,16 @@ def interpolate_prompt_series(animation_prompts, max_frames, start_frame, pre_te
|
||||
next_key = 0
|
||||
|
||||
if type(prompt_weight_1) in {int, float}:
|
||||
prompt_weight_1 = tuple([prompt_weight_1] * max_frames)
|
||||
prompt_weight_1 = tuple([prompt_weight_1] * max_frames)
|
||||
|
||||
if type(prompt_weight_2) in {int, float}:
|
||||
prompt_weight_2 = tuple([prompt_weight_2] * max_frames)
|
||||
prompt_weight_2 = tuple([prompt_weight_2] * max_frames)
|
||||
|
||||
if type(prompt_weight_3) in {int, float}:
|
||||
prompt_weight_3 = tuple([prompt_weight_3] * max_frames)
|
||||
prompt_weight_3 = tuple([prompt_weight_3] * max_frames)
|
||||
|
||||
if type(prompt_weight_4) in {int, float}:
|
||||
prompt_weight_4 = tuple([prompt_weight_4] * max_frames)
|
||||
prompt_weight_4 = tuple([prompt_weight_4] * max_frames)
|
||||
|
||||
# For every keyframe prompt except the last
|
||||
for i in range(0, len(sorted_prompts) - 1):
|
||||
@@ -150,7 +152,7 @@ def interpolate_prompt_series(animation_prompts, max_frames, start_frame, pre_te
|
||||
index_offset = 0
|
||||
# Evaluate the current and next prompt's expressions
|
||||
for i in range(start_frame, len(cur_prompt_series)):
|
||||
cur_prompt_series[i] = prepare_batch_prompt(cur_prompt_series[i], max_frames, i, float(prompt_weight_1[0]),
|
||||
cur_prompt_series[i] = prepare_batch_prompt(cur_prompt_series[i], max_frames, i, prompt_weight_1[i],
|
||||
prompt_weight_2[i], prompt_weight_3[i], prompt_weight_4[i])
|
||||
nxt_prompt_series[i] = prepare_batch_prompt(nxt_prompt_series[i], max_frames, i, prompt_weight_1[i],
|
||||
prompt_weight_2[i], prompt_weight_3[i], prompt_weight_4[i])
|
||||
@@ -167,17 +169,11 @@ def interpolate_prompt_series(animation_prompts, max_frames, start_frame, pre_te
|
||||
# if it is an in-between frame and the prompts differ, composable diffusion will be performed.
|
||||
return (cur_prompt_series, nxt_prompt_series, weight_series)
|
||||
|
||||
|
||||
def BatchPoolAnimConditioning(cur_prompt_series, nxt_prompt_series, weight_series, clip):
|
||||
pooled_out = []
|
||||
cond_out = []
|
||||
|
||||
def pad_with_clip_tokens(tensor, target_length):
|
||||
pad_token = clip.cond_stage_model.clip_l.special_tokens['pad']
|
||||
tokens_to_pad = clip.tokenize(pad_token)
|
||||
tokens_to_pad = tokens_to_pad.unsqueeze(0).expand(1, target_length, tokens_to_pad.shape[2])
|
||||
tokens_to_pad = tokens_to_pad.to(tensor.device)
|
||||
return torch.cat([tensor, tokens_to_pad], dim=1)
|
||||
|
||||
for i in range(len(cur_prompt_series)):
|
||||
tokens = clip.tokenize(str(cur_prompt_series[i]))
|
||||
cond_to, pooled_to = clip.encode_from_tokens(tokens, return_pooled=True)
|
||||
@@ -187,7 +183,6 @@ def BatchPoolAnimConditioning(cur_prompt_series, nxt_prompt_series, weight_serie
|
||||
cond_from, pooled_from = clip.encode_from_tokens(tokens, return_pooled=True)
|
||||
else:
|
||||
cond_from, pooled_from = torch.zeros_like(cond_to), torch.zeros_like(pooled_to)
|
||||
pooled_from = pad_with_clip_tokens(pooled_from, cond_to.shape[1])
|
||||
|
||||
interpolated_conditioning = addWeighted([[cond_to, {"pooled_output": pooled_to}]],
|
||||
[[cond_from, {"pooled_output": pooled_from}]],
|
||||
@@ -196,8 +191,8 @@ def BatchPoolAnimConditioning(cur_prompt_series, nxt_prompt_series, weight_serie
|
||||
interpolated_cond = interpolated_conditioning[0][0]
|
||||
interpolated_pooled = interpolated_conditioning[0][1].get("pooled_output", pooled_from)
|
||||
|
||||
pooled_out.append(interpolated_pooled)
|
||||
cond_out.append(interpolated_cond)
|
||||
pooled_out.append(interpolated_pooled)
|
||||
|
||||
final_pooled_output = torch.cat(pooled_out, dim=0)
|
||||
final_conditioning = torch.cat(cond_out, dim=0)
|
||||
@@ -206,6 +201,8 @@ def BatchPoolAnimConditioning(cur_prompt_series, nxt_prompt_series, weight_serie
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def BatchGLIGENConditioning(cur_prompt_series, nxt_prompt_series, weight_series, clip):
|
||||
pooled_out = []
|
||||
cond_out = []
|
||||
|
||||
@@ -12,31 +12,44 @@ import json
|
||||
|
||||
#Addweighted function from Comfyui
|
||||
def addWeighted(conditioning_to, conditioning_from, conditioning_to_strength):
|
||||
out = []
|
||||
out = []
|
||||
|
||||
if len(conditioning_from) > 1:
|
||||
print("Warning: ConditioningAverage conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to.")
|
||||
if len(conditioning_from) > 1:
|
||||
print("Warning: ConditioningAverage conditioning_from contains more than 1 cond, only the first one will actually be applied to conditioning_to.")
|
||||
|
||||
cond_from = conditioning_from[0][0]
|
||||
pooled_output_from = conditioning_from[0][1].get("pooled_output", None)
|
||||
cond_from = conditioning_from[0][0]
|
||||
pooled_output_from = conditioning_from[0][1].get("pooled_output", None)
|
||||
|
||||
for i in range(len(conditioning_to)):
|
||||
t1 = conditioning_to[i][0]
|
||||
pooled_output_to = conditioning_to[i][1].get("pooled_output", pooled_output_from)
|
||||
t0 = cond_from[:,:t1.shape[1]]
|
||||
if t0.shape[1] < t1.shape[1]:
|
||||
t0 = torch.cat([t0] + [torch.zeros((1, (t1.shape[1] - t0.shape[1]), t1.shape[2]))], dim=1)
|
||||
for i in range(len(conditioning_to)):
|
||||
t1 = conditioning_to[i][0]
|
||||
pooled_output_to = conditioning_to[i][1].get("pooled_output", pooled_output_from)
|
||||
|
||||
tw = torch.mul(t1, conditioning_to_strength) + torch.mul(t0, (1.0 - conditioning_to_strength))
|
||||
t_to = conditioning_to[i][1].copy()
|
||||
if pooled_output_from is not None and pooled_output_to is not None:
|
||||
t_to["pooled_output"] = torch.mul(pooled_output_to, conditioning_to_strength) + torch.mul(pooled_output_from, (1.0 - conditioning_to_strength))
|
||||
elif pooled_output_from is not None:
|
||||
t_to["pooled_output"] = pooled_output_from
|
||||
max_size = max(t1.shape[1], cond_from.shape[1])
|
||||
t0 = pad_with_zeros(cond_from, max_size)
|
||||
t1 = pad_with_zeros(t1, max_size)
|
||||
|
||||
n = [tw, t_to]
|
||||
out.append(n)
|
||||
return out
|
||||
tw = torch.mul(t1, conditioning_to_strength) + torch.mul(t0, (1.0 - conditioning_to_strength))
|
||||
t_to = conditioning_to[i][1].copy()
|
||||
|
||||
if pooled_output_from is not None and pooled_output_to is not None:
|
||||
# Pad pooled outputs if available
|
||||
pooled_output_to = pad_with_zeros(pooled_output_to, max_size)
|
||||
pooled_output_from = pad_with_zeros(pooled_output_from, max_size)
|
||||
t_to["pooled_output"] = torch.mul(pooled_output_to, conditioning_to_strength) + torch.mul(pooled_output_from, (1.0 - conditioning_to_strength))
|
||||
elif pooled_output_from is not None:
|
||||
t_to["pooled_output"] = pooled_output_from
|
||||
|
||||
n = [tw, t_to]
|
||||
out.append(n)
|
||||
|
||||
return out
|
||||
|
||||
def pad_with_zeros(tensor, target_length):
|
||||
current_length = tensor.shape[1]
|
||||
if current_length < target_length:
|
||||
padding = torch.zeros(tensor.shape[0], target_length - current_length, tensor.shape[2]).to(tensor.device)
|
||||
tensor = torch.cat([tensor, padding], dim=1)
|
||||
return tensor
|
||||
|
||||
def reverseConcatenation(final_conditioning, final_pooled_output, max_frames):
|
||||
# Split the final_conditioning and final_pooled_output tensors into their original components
|
||||
|
||||
@@ -162,8 +162,7 @@ def set_model_patch_replace(model, patch_kwargs, key):
|
||||
if "attn2" not in to["patches_replace"]:
|
||||
to["patches_replace"]["attn2"] = {}
|
||||
if key not in to["patches_replace"]["attn2"]:
|
||||
patch = CrossAttentionPatch(**patch_kwargs)
|
||||
to["patches_replace"]["attn2"][key] = patch
|
||||
to["patches_replace"]["attn2"][key] = CrossAttentionPatch(**patch_kwargs)
|
||||
else:
|
||||
to["patches_replace"]["attn2"][key].set_new_condition(**patch_kwargs)
|
||||
|
||||
@@ -426,7 +425,7 @@ class IPAdapter(nn.Module):
|
||||
|
||||
class CrossAttentionPatch:
|
||||
# forward for patching
|
||||
def __init__(self, weight, ipadapter, number, cond, uncond, weight_type, mask=None, sigma_start=0.0, sigma_end=1.0, unfold_batch=False):
|
||||
def __init__(self, weight, ipadapter, number, cond, uncond, weight_type="original", mask=None, sigma_start=0.0, sigma_end=1.0, unfold_batch=False):
|
||||
self.weights = [weight]
|
||||
self.ipadapters = [ipadapter]
|
||||
self.conds = [cond]
|
||||
@@ -441,7 +440,7 @@ class CrossAttentionPatch:
|
||||
self.k_key = str(self.number*2+1) + "_to_k_ip"
|
||||
self.v_key = str(self.number*2+1) + "_to_v_ip"
|
||||
|
||||
def set_new_condition(self, weight, ipadapter, number, cond, uncond, weight_type, mask=None, sigma_start=0.0, sigma_end=1.0, unfold_batch=False):
|
||||
def set_new_condition(self, weight, ipadapter, number, cond, uncond, weight_type="original", mask=None, sigma_start=0.0, sigma_end=1.0, unfold_batch=False):
|
||||
self.weights.append(weight)
|
||||
self.ipadapters.append(ipadapter)
|
||||
self.conds.append(cond)
|
||||
@@ -455,7 +454,8 @@ class CrossAttentionPatch:
|
||||
def __call__(self, n, context_attn2, value_attn2, extra_options):
|
||||
org_dtype = n.dtype
|
||||
cond_or_uncond = extra_options["cond_or_uncond"]
|
||||
sigma = extra_options["sigmas"][0].item() if 'sigmas' in extra_options else 999999999.9
|
||||
sigma = extra_options["sigmas"][0] if 'sigmas' in extra_options else None
|
||||
sigma = sigma.item() if sigma is not None else 999999999.9
|
||||
|
||||
# extra options for AnimateDiff
|
||||
ad_params = extra_options['ad_params'] if "ad_params" in extra_options else None
|
||||
@@ -719,7 +719,7 @@ class IPAdapterApply:
|
||||
face = insightface.get(face_img[i])
|
||||
if face:
|
||||
face_embed.append(torch.from_numpy(face[0].normed_embedding).unsqueeze(0))
|
||||
face_clipvision.append(NPToTensor(insightface_face_align.norm_crop(face_img[i], landmark=face[0].kps, image_size=224)))
|
||||
face_clipvision.append(NPToTensor(insightface_face_align.norm_crop(face_img[i], landmark=face[0].kps, image_size=256)))
|
||||
|
||||
if 640 not in size:
|
||||
print(f"\033[33mINFO: InsightFace detection resolution lowered to {size}.\033[0m")
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
/__pycache__/
|
||||
/luts/*.cube
|
||||
/luts/*.cube
|
||||
/fonts/*.ttf
|
||||
/fonts/*.otf
|
||||
|
||||
@@ -6,14 +6,19 @@ import random
|
||||
import os
|
||||
import operator as op
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageColor, ImageFilter
|
||||
import io
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import torchvision.transforms.v2 as T
|
||||
|
||||
from nodes import MAX_RESOLUTION, SaveImage
|
||||
from nodes import MAX_RESOLUTION, SaveImage, common_ksampler
|
||||
import folder_paths
|
||||
import comfy.utils
|
||||
import comfy.samplers
|
||||
|
||||
STOCHASTIC_SAMPLERS = ["euler_ancestral", "dpm_2_ancestral", "dpmpp_2s_ancestral", "dpmpp_sde", "dpmpp_sde_gpu", "dpmpp_2m_sde", "dpmpp_2m_sde_gpu", "dpmpp_3m_sde", "dpmpp_3m_sde_gpu", "ddpm"]
|
||||
|
||||
def p(image):
|
||||
return image.permute([0,3,1,2])
|
||||
@@ -326,61 +331,6 @@ class ExtractKeyframes:
|
||||
|
||||
return (image[keyframes], ','.join(map(str, keyframes)),)
|
||||
|
||||
"""
|
||||
class NoiseFromImage:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"adjust_levels": ("FLOAT", { "default": 1.00, "min": 0.00, "max": 20.00, "step": 0.05, }),
|
||||
#"noise_intensity": ("FLOAT", { "default": 1.00, "min": 0.00, "max": 1.00, "step": 0.05, }),
|
||||
"noise_density": ("FLOAT", { "default": 0.05, "min": 0.00, "max": 1.00, "step": 0.05, }),
|
||||
"noise_scale": ("FLOAT", { "default": 0.2, "min": 0.00, "max": 1.00, "step": 0.05, }),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "essentials"
|
||||
|
||||
def execute(self, image, noise_seed, adjust_levels, noise_density, noise_scale):
|
||||
generator = torch.manual_seed(noise_seed)
|
||||
|
||||
image = image.mean(dim=3).unsqueeze(-1).repeat(1, 1, 1, 3)
|
||||
|
||||
# Adjust image levels
|
||||
image = (1 - adjust_levels) * torch.mean(image) + adjust_levels * image
|
||||
image = torch.clamp(image, 0, 1)
|
||||
|
||||
# Create noise
|
||||
fine_noise = torch.rand([image.shape[0], image.shape[1], image.shape[2], image.shape[3]], dtype=image.dtype, layout=image.layout, generator=generator, device="cpu")
|
||||
fine_noise = fine_noise * (fine_noise > 1-noise_density).float() # Lower density
|
||||
fine_noise = (fine_noise * 16).round() / 16
|
||||
coarse_noise = F.interpolate(p(fine_noise), scale_factor=noise_scale, mode='bilinear', align_corners=False)
|
||||
coarse_noise = F.interpolate(coarse_noise, size=(image.shape[1], image.shape[2]), mode='bilinear', align_corners=False)
|
||||
coarse_noise = pb(coarse_noise)
|
||||
|
||||
# Merge noises
|
||||
noise = ((1 - image) * coarse_noise + image * fine_noise)
|
||||
noise = torch.clamp(noise, 0, 1)
|
||||
noise = image * noise
|
||||
|
||||
# Change noise intensity
|
||||
#noise = noise * noise_intensity
|
||||
#print(noise.min(), noise.max())
|
||||
#noise = torch.clamp(noise, 0, 1)
|
||||
|
||||
# Apply noise to image
|
||||
#noise = torch.clamp((1-noise_intensity) * image + noise, 0, 1)
|
||||
|
||||
#out = image + fine_noise * mask * noise_intensity
|
||||
|
||||
return (noise,)
|
||||
"""
|
||||
|
||||
class MaskFlip:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -424,11 +374,9 @@ class MaskBlur:
|
||||
if size % 2 == 0:
|
||||
size+= 1
|
||||
|
||||
blurred = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 1)
|
||||
blurred = p(blurred)
|
||||
blurred = mask.unsqueeze(1)
|
||||
blurred = T.GaussianBlur(size, amount)(blurred)
|
||||
blurred = pb(blurred)
|
||||
blurred = blurred[:, :, :, 0]
|
||||
blurred = blurred.squeeze(1)
|
||||
|
||||
return(blurred,)
|
||||
|
||||
@@ -1071,22 +1019,155 @@ class CLIPTextEncodeSDXLSimplified:
|
||||
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
|
||||
return ([[cond, {"pooled_output": pooled, "width": width, "height": height, "crop_w": crop_w, "crop_h": crop_h, "target_width": target_width, "target_height": target_height}]], )
|
||||
|
||||
class SDXLResolutionPicker:
|
||||
class KSamplerVariationsStochastic:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":{
|
||||
"model": ("MODEL",),
|
||||
"latent_image": ("LATENT", ),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 25, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
|
||||
"sampler": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"variation_seed": ("INT:seed", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"variation_strength": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0, "step":0.05, "round": 0.01}),
|
||||
#"variation_sampler": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"cfg_scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step":0.05, "round": 0.01}),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("LATENT", )
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "essentials"
|
||||
|
||||
def execute(self, model, latent_image, noise_seed, steps, cfg, sampler, scheduler, positive, negative, variation_seed, variation_strength, cfg_scale, variation_sampler="dpmpp_2m_sde"):
|
||||
# Stage 1: composition sampler
|
||||
force_full_denoise = False # return with leftover noise = "enable"
|
||||
disable_noise = False # add noise = "enable"
|
||||
|
||||
end_at_step = max(int(steps * (1-variation_strength)), 1)
|
||||
start_at_step = 0
|
||||
|
||||
work_latent = latent_image.copy()
|
||||
batch_size = work_latent["samples"].shape[0]
|
||||
work_latent["samples"] = work_latent["samples"][0].unsqueeze(0)
|
||||
|
||||
stage1 = common_ksampler(model, noise_seed, steps, cfg, sampler, scheduler, positive, negative, work_latent, denoise=1.0, disable_noise=disable_noise, start_step=start_at_step, last_step=end_at_step, force_full_denoise=force_full_denoise)[0]
|
||||
print(stage1)
|
||||
if batch_size > 1:
|
||||
stage1["samples"] = stage1["samples"].clone().repeat(batch_size, 1, 1, 1)
|
||||
|
||||
# Stage 2: variation sampler
|
||||
force_full_denoise = True
|
||||
disable_noise = True
|
||||
cfg = max(cfg * cfg_scale, 1.0)
|
||||
start_at_step = end_at_step
|
||||
end_at_step = steps
|
||||
|
||||
return common_ksampler(model, variation_seed, steps, cfg, variation_sampler, scheduler, positive, negative, stage1, denoise=1.0, disable_noise=disable_noise, start_step=start_at_step, last_step=end_at_step, force_full_denoise=force_full_denoise)
|
||||
|
||||
# From https://github.com/BlenderNeko/ComfyUI_Noise/
|
||||
def slerp(val, low, high):
|
||||
dims = low.shape
|
||||
|
||||
low = low.reshape(dims[0], -1)
|
||||
high = high.reshape(dims[0], -1)
|
||||
|
||||
low_norm = low/torch.norm(low, dim=1, keepdim=True)
|
||||
high_norm = high/torch.norm(high, dim=1, keepdim=True)
|
||||
|
||||
low_norm[low_norm != low_norm] = 0.0
|
||||
high_norm[high_norm != high_norm] = 0.0
|
||||
|
||||
omega = torch.acos((low_norm*high_norm).sum(1))
|
||||
so = torch.sin(omega)
|
||||
res = (torch.sin((1.0-val)*omega)/so).unsqueeze(1)*low + (torch.sin(val*omega)/so).unsqueeze(1) * high
|
||||
|
||||
return res.reshape(dims)
|
||||
|
||||
class KSamplerVariationsWithNoise:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"model": ("MODEL", ),
|
||||
"latent_image": ("LATENT", ),
|
||||
"main_seed": ("INT:seed", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"variation_strength": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0, "step":0.01, "round": 0.01}),
|
||||
#"start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
|
||||
#"end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}),
|
||||
#"return_with_leftover_noise": (["disable", "enable"], ),
|
||||
"variation_seed": ("INT:seed", {"default": random.randint(0, 0xffffffffffffffff), "min": 0, "max": 0xffffffffffffffff}),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "essentials"
|
||||
|
||||
def execute(self, model, latent_image, main_seed, steps, cfg, sampler_name, scheduler, positive, negative, variation_strength, variation_seed):
|
||||
generator = torch.manual_seed(main_seed)
|
||||
batch_size, _, height, width = latent_image["samples"].shape
|
||||
base_noise = torch.randn((1, 4, height, width), dtype=torch.float32, device="cpu", generator=generator).repeat(batch_size, 1, 1, 1).cpu()
|
||||
|
||||
generator = torch.manual_seed(variation_seed)
|
||||
variation_noise = torch.randn((batch_size, 4, height, width), dtype=torch.float32, device="cpu", generator=generator).cpu()
|
||||
|
||||
slerp_noise = slerp(variation_strength, base_noise, variation_noise)
|
||||
|
||||
device = comfy.model_management.get_torch_device()
|
||||
end_at_step = steps #min(steps, end_at_step)
|
||||
start_at_step = 0 #min(start_at_step, end_at_step)
|
||||
real_model = None
|
||||
comfy.model_management.load_model_gpu(model)
|
||||
real_model = model.model
|
||||
sampler = comfy.samplers.KSampler(real_model, steps=steps, device=device, sampler=sampler_name, scheduler=scheduler, denoise=1.0, model_options=model.model_options)
|
||||
sigmas = sampler.sigmas
|
||||
sigma = sigmas[start_at_step] - sigmas[end_at_step]
|
||||
sigma /= model.model.latent_format.scale_factor
|
||||
sigma = sigma.cpu().numpy()
|
||||
|
||||
work_latent = latent_image.copy()
|
||||
work_latent["samples"] = latent_image["samples"].clone() + slerp_noise * sigma
|
||||
|
||||
force_full_denoise = True
|
||||
#if return_with_leftover_noise == "enable":
|
||||
# force_full_denoise = False
|
||||
|
||||
disable_noise = True
|
||||
|
||||
return common_ksampler(model, main_seed, steps, cfg, sampler_name, scheduler, positive, negative, work_latent, denoise=1.0, disable_noise=disable_noise, start_step=start_at_step, last_step=end_at_step, force_full_denoise=force_full_denoise)
|
||||
|
||||
class SDXLEmptyLatentSizePicker:
|
||||
def __init__(self):
|
||||
self.device = comfy.model_management.intermediate_device()
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"resolution": (["704x1408 (0.5)","704x1344 (0.52)","768x1344 (0.57)","768x1280 (0.6)","832x1216 (0.68)","832x1152 (0.72)","896x1152 (0.78)","896x1088 (0.82)","960x1088 (0.88)","960x1024 (0.94)","1024x1024 (1.0)","1024x960 (1.07)","1088x960 (1.13)","1088x896 (1.21)","1152x896 (1.29)","1152x832 (1.38)","1216x832 (1.46)","1280x768 (1.67)","1344x768 (1.75)","1344x704 (1.91)","1408x704 (2.0)","1472x704 (2.09)","1536x640 (2.4)","1600x640 (2.5)","1664x576 (2.89)","1728x576 (3.0)",], {"default": "1024x1024 (1.0)"}),
|
||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("INT","INT",)
|
||||
RETURN_NAMES = ("width", "height",)
|
||||
RETURN_TYPES = ("LATENT","INT","INT",)
|
||||
RETURN_NAMES = ("LATENT","width", "height",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "essentials"
|
||||
|
||||
def execute(self, resolution):
|
||||
def execute(self, resolution, batch_size):
|
||||
width, height = resolution.split(" ")[0].split("x")
|
||||
width = int(width)
|
||||
height = int(height)
|
||||
|
||||
return (width, height,)
|
||||
latent = torch.zeros([batch_size, 4, height // 8, width // 8], device=self.device)
|
||||
|
||||
return ({"samples":latent}, width, height,)
|
||||
|
||||
LUTS_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "luts")
|
||||
# From https://github.com/yoonsikp/pycubelut/blob/master/pycubelut.py (MIT license)
|
||||
@@ -1125,7 +1206,7 @@ class ImageApplyLUT:
|
||||
lut.table[:, :, :, dim] = np.clip(lut.table[:, :, :, dim], lut.domain[0, dim], lut.domain[1, dim])
|
||||
|
||||
out = []
|
||||
for img in image: # TODO: is this more resrouce efficient? should we use a batch instead?
|
||||
for img in image: # TODO: is this more resource efficient? should we use a batch instead?
|
||||
lut_img = img.numpy().copy()
|
||||
|
||||
is_non_default_domain = not np.array_equal(lut.domain, np.array([[0., 0., 0.], [1., 1., 1.]]))
|
||||
@@ -1150,6 +1231,255 @@ class ImageApplyLUT:
|
||||
|
||||
return (out, )
|
||||
|
||||
FONTS_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fonts")
|
||||
class DrawText:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", { "multiline": True, "default": "Hello, World!" }),
|
||||
"font": ([f for f in os.listdir(FONTS_DIR) if f.endswith('.ttf') or f.endswith('.otf')], ),
|
||||
"size": ("INT", { "default": 56, "min": 1, "max": 9999, "step": 1 }),
|
||||
"color": ("STRING", { "multiline": False, "default": "#FFFFFF" }),
|
||||
"background_color": ("STRING", { "multiline": False, "default": "#00000000" }),
|
||||
"shadow_distance": ("INT", { "default": 0, "min": 0, "max": 100, "step": 1 }),
|
||||
"shadow_blur": ("INT", { "default": 0, "min": 0, "max": 100, "step": 1 }),
|
||||
"shadow_color": ("STRING", { "multiline": False, "default": "#000000" }),
|
||||
"alignment": (["left", "center", "right"],),
|
||||
"width": ("INT", { "default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1 }),
|
||||
"height": ("INT", { "default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1 }),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "essentials"
|
||||
|
||||
def execute(self, text, font, size, color, background_color, shadow_distance, shadow_blur, shadow_color, alignment, width, height):
|
||||
font = ImageFont.truetype(os.path.join(FONTS_DIR, font), size)
|
||||
|
||||
lines = text.split("\n")
|
||||
|
||||
# Calculate the width and height of the text
|
||||
text_width = max(font.getbbox(line)[2] for line in lines)
|
||||
line_height = font.getmask(text).getbbox()[3] + font.getmetrics()[1] # add descent to height
|
||||
text_height = line_height * len(lines)
|
||||
|
||||
width = width if width > 0 else text_width
|
||||
height = height if height > 0 else text_height
|
||||
|
||||
background_color = ImageColor.getrgb(background_color)
|
||||
image = Image.new('RGBA', (width + shadow_distance, height + shadow_distance), color=background_color)
|
||||
|
||||
image_shadow = None
|
||||
if shadow_distance > 0:
|
||||
image_shadow = Image.new('RGBA', (width + shadow_distance, height + shadow_distance), color=background_color)
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
line_width = font.getbbox(line)[2]
|
||||
#text_height =font.getbbox(line)[3]
|
||||
if alignment == "left":
|
||||
x = 0
|
||||
elif alignment == "center":
|
||||
x = (width - line_width) / 2
|
||||
elif alignment == "right":
|
||||
x = width - line_width
|
||||
y = i * line_height
|
||||
|
||||
draw = ImageDraw.Draw(image)
|
||||
draw.text((x, y), line, font=font, fill=color)
|
||||
|
||||
if image_shadow is not None:
|
||||
draw = ImageDraw.Draw(image_shadow)
|
||||
draw.text((x + shadow_distance, y + shadow_distance), line, font=font, fill=shadow_color)
|
||||
|
||||
if image_shadow is not None:
|
||||
image_shadow = image_shadow.filter(ImageFilter.GaussianBlur(shadow_blur))
|
||||
image = Image.alpha_composite(image_shadow, image)
|
||||
|
||||
image = pb(T.ToTensor()(image).unsqueeze(0))
|
||||
mask = image[:, :, :, 3] if image.shape[3] == 4 else torch.ones_like(image[:, :, :, 0])
|
||||
|
||||
return (image[:, :, :, :3], mask,)
|
||||
|
||||
class RemBGSession:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"model": (["u2net: general purpose", "u2netp: lightweight general purpose", "u2net_human_seg: human segmentation", "u2net_cloth_seg: cloths Parsing", "silueta: very small u2net", "isnet-general-use: general purpose", "isnet-anime: anime illustrations", "sam: general purpose"],),
|
||||
"providers": (['CPU', 'CUDA', 'ROCM', 'DirectML', 'OpenVINO', 'CoreML', 'Tensorrt', 'Azure'],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("REMBG_SESSION",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "essentials"
|
||||
|
||||
def execute(self, model, providers):
|
||||
from rembg import new_session as rembg_new_session
|
||||
|
||||
model = model.split(":")[0]
|
||||
return (rembg_new_session(model, providers=[providers+"ExecutionProvider"]),)
|
||||
|
||||
class ImageRemoveBackground:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"rembg_session": ("REMBG_SESSION",),
|
||||
"image": ("IMAGE",),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "essentials"
|
||||
|
||||
def execute(self, rembg_session, image):
|
||||
from rembg import remove as rembg
|
||||
|
||||
image = p(image)
|
||||
output = []
|
||||
for img in image:
|
||||
img = T.ToPILImage()(img)
|
||||
img = rembg(img, session=rembg_session)
|
||||
output.append(T.ToTensor()(img))
|
||||
|
||||
output = torch.stack(output, dim=0)
|
||||
output = pb(output)
|
||||
mask = output[:, :, :, 3] if output.shape[3] == 4 else torch.ones_like(output[:, :, :, 0])
|
||||
|
||||
return(output[:, :, :, :3], mask,)
|
||||
|
||||
class NoiseFromImage:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"noise_size": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01 }),
|
||||
"color_noise": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01 }),
|
||||
"mask_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01 }),
|
||||
"mask_scale_diff": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01 }),
|
||||
"noise_strenght": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01 }),
|
||||
"saturation": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 100.0, "step": 0.1 }),
|
||||
"contrast": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.1 }),
|
||||
"blur": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.1 }),
|
||||
},
|
||||
"optional": {
|
||||
"noise_mask": ("IMAGE",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE","IMAGE",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "essentials"
|
||||
|
||||
def execute(self, image, noise_size, color_noise, mask_strength, mask_scale_diff, noise_strenght, saturation, contrast, blur, noise_mask=None):
|
||||
torch.manual_seed(0)
|
||||
|
||||
elastic_alpha = max(image.shape[1], image.shape[2])# * noise_size
|
||||
elastic_sigma = elastic_alpha / 400 * noise_size
|
||||
|
||||
blur_size = int(6 * blur+1)
|
||||
if blur_size % 2 == 0:
|
||||
blur_size+= 1
|
||||
|
||||
if noise_mask is None:
|
||||
noise_mask = image
|
||||
|
||||
# Ensure noise mask is the same size as the image
|
||||
if noise_mask.shape[1:] != image.shape[1:]:
|
||||
noise_mask = F.interpolate(p(noise_mask), size=(image.shape[1], image.shape[2]), mode='bicubic', align_corners=False)
|
||||
noise_mask = pb(noise_mask)
|
||||
# Ensure we have the same number of masks and images
|
||||
if noise_mask.shape[0] > image.shape[0]:
|
||||
noise_mask = noise_mask[:image.shape[0]]
|
||||
else:
|
||||
noise_mask = torch.cat((noise_mask, noise_mask[-1:].repeat((image.shape[0]-noise_mask.shape[0], 1, 1, 1))), dim=0)
|
||||
|
||||
# Convert image to grayscale mask
|
||||
noise_mask = noise_mask.mean(dim=3).unsqueeze(-1)
|
||||
|
||||
# add color noise
|
||||
imgs = p(image.clone())
|
||||
if color_noise > 0:
|
||||
color_noise = torch.normal(torch.zeros_like(imgs), std=color_noise)
|
||||
|
||||
#color_noise = torch.rand_like(imgs) * (color_noise * 2) - color_noise
|
||||
|
||||
color_noise *= (imgs - imgs.min()) / (imgs.max() - imgs.min())
|
||||
|
||||
imgs = imgs + color_noise
|
||||
imgs = imgs.clamp(0, 1)
|
||||
|
||||
# create fine noise
|
||||
fine_noise = []
|
||||
for n in imgs:
|
||||
avg_color = n.mean(dim=[1,2])
|
||||
|
||||
tmp_noise = T.ElasticTransform(alpha=elastic_alpha, sigma=elastic_sigma, fill=avg_color.tolist())(n)
|
||||
#tmp_noise = T.functional.adjust_saturation(tmp_noise, 2.0)
|
||||
tmp_noise = T.GaussianBlur(blur_size, blur)(tmp_noise)
|
||||
tmp_noise = T.ColorJitter(contrast=(contrast,contrast), saturation=(saturation,saturation))(tmp_noise)
|
||||
fine_noise.append(tmp_noise)
|
||||
|
||||
#tmp_noise = F.interpolate(tmp_noise, scale_factor=.1, mode='bilinear', align_corners=False)
|
||||
#tmp_noise = F.interpolate(tmp_noise, size=(tmp_noise.shape[1], tmp_noise.shape[2]), mode='bilinear', align_corners=False)
|
||||
|
||||
#tmp_noise = T.ElasticTransform(alpha=elastic_alpha, sigma=elastic_sigma/3, fill=avg_color.tolist())(n)
|
||||
#tmp_noise = T.GaussianBlur(blur_size, blur)(tmp_noise)
|
||||
#tmp_noise = T.functional.adjust_saturation(tmp_noise, saturation)
|
||||
#tmp_noise = T.ColorJitter(contrast=(contrast,contrast), saturation=(saturation,saturation))(tmp_noise)
|
||||
#fine_noise.append(tmp_noise)
|
||||
|
||||
imgs = None
|
||||
del imgs
|
||||
|
||||
fine_noise = torch.stack(fine_noise, dim=0)
|
||||
fine_noise = pb(fine_noise)
|
||||
#fine_noise = torch.stack(fine_noise, dim=0)
|
||||
#fine_noise = pb(fine_noise)
|
||||
mask_scale_diff = min(mask_scale_diff, 0.99)
|
||||
if mask_scale_diff > 0:
|
||||
coarse_noise = F.interpolate(p(fine_noise), scale_factor=1-mask_scale_diff, mode='area')
|
||||
coarse_noise = F.interpolate(coarse_noise, size=(fine_noise.shape[1], fine_noise.shape[2]), mode='bilinear', align_corners=False)
|
||||
coarse_noise = pb(coarse_noise)
|
||||
else:
|
||||
coarse_noise = fine_noise
|
||||
|
||||
#noise_mask = noise_mask * mask_strength + (1 - mask_strength)
|
||||
# merge fine and coarse noise
|
||||
output = (1 - noise_mask) * coarse_noise + noise_mask * fine_noise
|
||||
#noise_mask = noise_mask * mask_strength
|
||||
if mask_strength < 1:
|
||||
noise_mask = noise_mask.pow(mask_strength)
|
||||
noise_mask = torch.nan_to_num(noise_mask).clamp(0, 1)
|
||||
output = noise_mask * output + (1 - noise_mask) * image
|
||||
|
||||
# apply noise to image
|
||||
output = output * noise_strenght + image * (1 - noise_strenght)
|
||||
output = output.clamp(0, 1)
|
||||
|
||||
return (output,noise_mask.repeat(1,1,1,3),)
|
||||
|
||||
class RemoveLatentMask:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "samples": ("LATENT",),}}
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
FUNCTION = "execute"
|
||||
|
||||
CATEGORY = "essentials"
|
||||
|
||||
def execute(self, samples):
|
||||
s = samples.copy()
|
||||
if "noise_mask" in s:
|
||||
del s["noise_mask"]
|
||||
|
||||
return (s,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"GetImageSize+": GetImageSize,
|
||||
|
||||
@@ -1167,7 +1497,6 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImageCompositeFromMaskBatch+": ImageCompositeFromMaskBatch,
|
||||
"ExtractKeyframes+": ExtractKeyframes,
|
||||
"ImageApplyLUT+": ImageApplyLUT,
|
||||
#"NoiseFromImage+": NoiseFromImage,
|
||||
|
||||
"MaskBlur+": MaskBlur,
|
||||
"MaskFlip+": MaskFlip,
|
||||
@@ -1185,8 +1514,18 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ModelCompile+": ModelCompile,
|
||||
"BatchCount+": BatchCount,
|
||||
|
||||
"KSamplerVariationsStochastic+": KSamplerVariationsStochastic,
|
||||
"KSamplerVariationsWithNoise+": KSamplerVariationsWithNoise,
|
||||
"CLIPTextEncodeSDXL+": CLIPTextEncodeSDXLSimplified,
|
||||
"SDXLResolutionPicker+": SDXLResolutionPicker,
|
||||
"SDXLEmptyLatentSizePicker+": SDXLEmptyLatentSizePicker,
|
||||
|
||||
"DrawText+": DrawText,
|
||||
"RemBGSession+": RemBGSession,
|
||||
"ImageRemoveBackground+": ImageRemoveBackground,
|
||||
|
||||
"RemoveLatentMask+": RemoveLatentMask,
|
||||
|
||||
#"NoiseFromImage~": NoiseFromImage,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -1205,7 +1544,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImageCompositeFromMaskBatch+": "🔧 Image Composite From Mask Batch",
|
||||
"ExtractKeyframes+": "🔧 Extract Keyframes (experimental)",
|
||||
"ImageApplyLUT+": "🔧 Image Apply LUT",
|
||||
#"NoiseFromImage+": "🔧 Noise From Image",
|
||||
|
||||
"MaskBlur+": "🔧 Mask Blur",
|
||||
"MaskFlip+": "🔧 Mask Flip",
|
||||
@@ -1214,7 +1552,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"MaskExpandBatch+": "🔧 Mask Expand Batch",
|
||||
"TransitionMask+": "🔧 Transition Mask",
|
||||
"MaskFromColor+": "🔧 Mask From Color",
|
||||
"MaskFromBatch+": "🔧 MaskFromBatch",
|
||||
"MaskFromBatch+": "🔧 Mask From Batch",
|
||||
|
||||
"SimpleMath+": "🔧 Simple Math",
|
||||
"ConsoleDebug+": "🔧 Console Debug",
|
||||
@@ -1223,6 +1561,16 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ModelCompile+": "🔧 Compile Model",
|
||||
"BatchCount+": "🔧 Batch Count",
|
||||
|
||||
"KSamplerVariationsStochastic+": "🔧 KSampler Stochastic Variations",
|
||||
"KSamplerVariationsWithNoise+": "🔧 KSampler Variations with Noise Injection",
|
||||
"CLIPTextEncodeSDXL+": "🔧 SDXLCLIPTextEncode",
|
||||
"SDXLResolutionPicker+": "🔧 SDXL Resolutions",
|
||||
"SDXLEmptyLatentSizePicker+": "🔧 SDXL Empty Latent Size Picker",
|
||||
|
||||
"DrawText+": "🔧 Draw Text",
|
||||
"RemBGSession+": "🔧 RemBG Session",
|
||||
"ImageRemoveBackground+": "🔧 Image Remove Background",
|
||||
|
||||
"RemoveLatentMask+": "🔧 Remove Latent Mask",
|
||||
|
||||
#"NoiseFromImage~": "🔧 Noise From Image",
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
numba
|
||||
colour-science
|
||||
colour-science
|
||||
rembg
|
||||
@@ -247,7 +247,8 @@ Set `bbox_detector` and `pose_estimator` according to this picture. You can try
|
||||
If onnxruntime is installed successfully and the checkpoint used endings with `.onnx`, it will replace default cv2 backend to take advantage of GPU. Note that if you are using NVidia card, this method currently can only works on CUDA 11.8 (ComfyUI_windows_portable_nvidia_cu118_or_cpu.7z) unless you compile onnxruntime yourself.
|
||||
|
||||
1. Know your onnxruntime build:
|
||||
* * NVidia/AMD GPU: `onnxruntime-gpu`
|
||||
* * NVidia CUDA 11.x or bellow/AMD GPU: `onnxruntime-gpu`
|
||||
* * NVidia CUDA 12.x: `onnxruntime-gpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/`
|
||||
* * DirectML: `onnxruntime-directml`
|
||||
* * OpenVINO: `onnxruntime-openvino`
|
||||
|
||||
@@ -283,7 +284,11 @@ Note that if this is your first time using ComfyUI, please test if it can run on
|
||||
* sam: [dhkim2810/MobileSAM/mobile_sam.pt](https://huggingface.co/dhkim2810/MobileSAM/blob/main/mobile_sam.pt)
|
||||
* uniformer: [lllyasviel/Annotators/upernet_global_small.pth](https://huggingface.co/lllyasviel/Annotators/blob/main/upernet_global_small.pth)
|
||||
* zoe: [lllyasviel/Annotators/ZoeD_M12_N.pt](https://huggingface.co/lllyasviel/Annotators/blob/main/ZoeD_M12_N.pt)
|
||||
|
||||
* teed: [bdsqlsz/qinglong_controlnet-lllite/7_model.pth](https://huggingface.co/bdsqlsz/qinglong_controlnet-lllite/blob/main/7_model.pth)
|
||||
* depth_anything: Either [LiheYoung/Depth-Anything/checkpoints/depth_anything_vitl14.pth](https://huggingface.co/spaces/LiheYoung/Depth-Anything/blob/main/checkpoints/depth_anything_vitl14.pth), [LiheYoung/Depth-Anything/checkpoints/depth_anything_vitb14.pth](https://huggingface.co/spaces/LiheYoung/Depth-Anything/blob/main/checkpoints/depth_anything_vitb14.pth) or [LiheYoung/Depth-Anything/checkpoints/depth_anything_vits14.pth](https://huggingface.co/spaces/LiheYoung/Depth-Anything/blob/main/checkpoints/depth_anything_vits14.pth)
|
||||
* diffusion_edge: Either [hr16/Diffusion-Edge/diffusion_edge_indoor.pt](https://huggingface.co/hr16/Diffusion-Edge/blob/main/diffusion_edge_indoor.pt), [hr16/Diffusion-Edge/diffusion_edge_urban.pt](https://huggingface.co/hr16/Diffusion-Edge/blob/main/diffusion_edge_urban.pt) or [hr16/Diffusion-Edge/diffusion_edge_natrual.pt](https://huggingface.co/hr16/Diffusion-Edge/blob/main/diffusion_edge_natrual.pt)
|
||||
* unimatch: Either [hr16/Unimatch/gmflow-scale2-regrefine6-mixdata.pth](https://huggingface.co/hr16/Unimatch/blob/main/gmflow-scale2-regrefine6-mixdata.pth), [hr16/Unimatch/gmflow-scale2-mixdata.pth](https://huggingface.co/hr16/Unimatch/blob/main/gmflow-scale2-mixdata.pth) or [hr16/Unimatch/gmflow-scale1-mixdata.pth](https://huggingface.co/hr16/Unimatch/blob/main/gmflow-scale1-mixdata.pth)
|
||||
* zoe_depth_anything: Either [LiheYoung/Depth-Anything/checkpoints_metric_depth/depth_anything_metric_depth_indoor.pt](https://huggingface.co/spaces/LiheYoung/Depth-Anything/blob/main/checkpoints_metric_depth/depth_anything_metric_depth_indoor.pt) or [LiheYoung/Depth-Anything/checkpoints_metric_depth/depth_anything_metric_depth_outdoor.pt](https://huggingface.co/spaces/LiheYoung/Depth-Anything/blob/main/checkpoints_metric_depth/depth_anything_metric_depth_outdoor.pt)
|
||||
# 500 Stars 😄
|
||||
<a href="https://star-history.com/#Fannovel16/comfyui_controlnet_aux&Date">
|
||||
<picture>
|
||||
|
||||
+6
-4
@@ -107,9 +107,11 @@ class FacialPartColoringFromPoseKps:
|
||||
return (torch.from_numpy(np_frames).float() / 255.,)
|
||||
|
||||
def draw_kps(self, pose_frame, mode, **facial_part_colors):
|
||||
canvas = np.zeros((pose_frame["canvas_height"], pose_frame["canvas_width"], 3), dtype=np.uint8)
|
||||
for (person, part_name) in itertools.product(pose_frame["people"], FACIAL_PARTS):
|
||||
facial_kps = rearrange(np.array(person['face_keypoints_2d']), "(n c) -> n c", n=70, c=3)[:, :2]
|
||||
width, height = pose_frame["canvas_width"], pose_frame["canvas_height"]
|
||||
canvas = np.zeros((height, width, 3), dtype=np.uint8)
|
||||
for person, part_name in itertools.product(pose_frame["people"], FACIAL_PARTS):
|
||||
n = len(person["face_keypoints_2d"]) // 3
|
||||
facial_kps = rearrange(np.array(person["face_keypoints_2d"]), "(n c) -> n c", n=n, c=3)[:, :2] * (width, height)
|
||||
facial_kps = facial_kps.astype(np.int32)
|
||||
part_color = ImageColor.getrgb(facial_part_colors[part_name])[:3]
|
||||
if mode == "circle":
|
||||
@@ -136,4 +138,4 @@ NODE_CLASS_MAPPINGS = {
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"SavePoseKpsAsJsonFile": "Save Pose Keypoints",
|
||||
"FacialPartColoringFromPoseKps": "Colorize Facial Parts from PoseKPS"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ VAR_DICT = dict(
|
||||
DENSEPOSE_MODEL_NAME = "LayerNorm/DensePose-TorchScript-with-hint-image",
|
||||
MESH_GRAPHORMER_MODEL_NAME = "hr16/ControlNet-HandRefiner-pruned",
|
||||
SAM_MODEL_NAME = "dhkim2810/MobileSAM",
|
||||
UNIMATCH_MODEL_NAME = "hr16/Unimatch",
|
||||
DEPTH_ANYTHING_MODEL_NAME = "LiheYoung/Depth-Anything", #HF Space
|
||||
DIFFUSION_EDGE_MODEL_NAME = "hr16/Diffusion-Edge"
|
||||
)
|
||||
re_result_dict = {}
|
||||
for preprocc in os.listdir(aux_dir):
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ class DiffusionEdgeDetector:
|
||||
self.device = "cpu"
|
||||
|
||||
@classmethod
|
||||
def from_pretrained(cls, pretrained_model_or_path=DIFFUSION_EDGE_MODEL_NAME, filename="depth_anything_vitl14.pth"):
|
||||
def from_pretrained(cls, pretrained_model_or_path=DIFFUSION_EDGE_MODEL_NAME, filename="diffusion_edge_indoor.pt"):
|
||||
model_path = custom_hf_download(pretrained_model_or_path, filename)
|
||||
model = DiffusionEdge(prepare_args(model_path))
|
||||
return cls(model)
|
||||
|
||||
@@ -6,6 +6,8 @@ class Example:
|
||||
-------------
|
||||
INPUT_TYPES (dict):
|
||||
Tell the main program input parameters of nodes.
|
||||
IS_CHANGED:
|
||||
optional method to control when the node is re executed.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
@@ -89,6 +91,17 @@ class Example:
|
||||
image = 1.0 - image
|
||||
return (image,)
|
||||
|
||||
"""
|
||||
The node will always be re executed if any of the inputs change but
|
||||
this method can be used to force the node to execute again even when the inputs don't change.
|
||||
You can make this node return a number or a string. This value will be compared to the one returned the last time the node was
|
||||
executed, if it is different the node will be executed again.
|
||||
This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash
|
||||
changes between executions the LoadImage node is executed again.
|
||||
"""
|
||||
#@classmethod
|
||||
#def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen):
|
||||
# return ""
|
||||
|
||||
# A dictionary that contains all nodes you want to export with their names
|
||||
# NOTE: names should be globally unique
|
||||
|
||||
@@ -65,6 +65,15 @@ Note, you can right-click on a bunch of the rgthree-comfy nodes and select `🛟
|
||||
> 
|
||||
> </details>
|
||||
|
||||
## Bookmark (🔖)
|
||||
> Place the bookmark node anywhere on screen to quickly navigate to that with a shortcut key.
|
||||
> <details>
|
||||
> <summary>ℹ️ <i>See More Information</i></summary>
|
||||
>
|
||||
> - Define the `sahortvut_key` to press to go right to that bookmark node, anchored in the top left
|
||||
> - You can also define the zoom level as well!
|
||||
> </details>
|
||||
|
||||
|
||||
## Context / Context Big
|
||||
> Pass along in general flow properties, and merge in new data. Similar to some other node suites "pipes" but easier merging, is more easily interoperable with standard nodes by both combining and exploding all in a single node.
|
||||
@@ -78,6 +87,21 @@ Note, you can right-click on a bunch of the rgthree-comfy nodes and select `🛟
|
||||
> 
|
||||
> </details>
|
||||
|
||||
## Image Comparer
|
||||
> The Image Comparer node compares two images on top of each other.
|
||||
> <details>
|
||||
> <summary>ℹ️ <i>More Information</i></summary>
|
||||
>
|
||||
> - **Note:** The right-click menu may show image options (Open Image, Save Image, etc.) which will correspond to the first image (image_a) if clicked on the left-half of the node, or the second image if on the right half of the node.
|
||||
> - **Inputs:**
|
||||
> - `image_a` _Required._ The first image to use to compare. If image_b is not supplied and image_a is a batch, the comparer will use the first two images of image_a.
|
||||
> - `image_b` _Optional._ The second image to use to compare. Optional only if image_a is a batch with two images.
|
||||
> - **Properties:** You can change the following properties (by right-clicking on the node, and select "Properties" or "Properties Panel" from the menu):
|
||||
> - `comparer_mode` - Choose between "Slide" and "Click". Defaults to "Slide".
|
||||
|
||||
|
||||
## Image Inset Crop
|
||||
> The node that lets you crop an input image by either pixel value, or percentage value.
|
||||
|
||||
|
||||
## Display Any
|
||||
@@ -99,11 +123,9 @@ Note, you can right-click on a bunch of the rgthree-comfy nodes and select `🛟
|
||||
> needing a separate Lora Loaders
|
||||
> </details>
|
||||
|
||||
|
||||
## Power Prompt - Simple
|
||||
> Same as Power Prompt above, but without LORA support; made for a slightly cleaner negative prompt _(since negative prompts do not support loras)_.
|
||||
|
||||
|
||||
## SDXL Power Prompt - Positive
|
||||
> The SDXL sibling to the Power Prompt above. It contains the text_g and text_l as separate text inputs, as well a couple more input slots necessary to ensure proper clipe encoding. Combine with
|
||||
|
||||
@@ -119,13 +141,49 @@ Note, you can right-click on a bunch of the rgthree-comfy nodes and select `🛟
|
||||
> <summary>ℹ️ <i>More Information</i></summary>
|
||||
>
|
||||
> - Pass in several context nodes and the Context Switch will automatically choose the first non-null context to continue onward with.
|
||||
> - Wondering how to toggle contexts to null? Use in conjuction with the **Fast Muter**
|
||||
> - Wondering how to toggle contexts to null? Use in conjuction with the **Fast Muter** or **Fast Groups Muter**
|
||||
>
|
||||
> </details>
|
||||
|
||||
## Any Switch
|
||||
> A powerful node to similar to the Context Switch above, that chooses the first input that is not null/empty.
|
||||
> <details>
|
||||
> <summary>ℹ️ <i>More Information</i></summary>
|
||||
>
|
||||
> - Pass in several inmputs of the same type and the Any Switch will automatically choose the first non-null value to continue onward with.
|
||||
> - Wondering how to toggle contexts to null? Use in conjuction with the **Fast Muter** or **Fast Groups Muter**
|
||||
>
|
||||
> </details>
|
||||
|
||||
|
||||
## Fast Groups Muter
|
||||
> The Fast Groups Muter is an input-less node that automatically collects all groups in your current workflow and allows you to quickly mute and unmute all nodes within the group.
|
||||
> <details>
|
||||
> <summary>ℹ️ <i>More Information</i></summary>
|
||||
>
|
||||
> - Groups will automatically be shown, though you can filter, sort and more from the **node Properties** _(by right-clicking on the node, and select "Properties" or "Properties Panel" from the menu)_. Properties include:
|
||||
> - `matchColors` - Only add groups that match the provided colors. Can be ComfyUI colors (red, pale_blue) or hex codes (#a4d399). Multiple can be added, comma delimited.
|
||||
> - `matchTitle` - Filter the list of toggles by title match (string match, or regular expression).
|
||||
> - `showNav` - Add / remove a quick navigation arrow to take you to the group. (default: true)
|
||||
> - `sort` - Sort the toggles' order by "alphanumeric", graph "position", or "custom alphabet". (default: "position")
|
||||
> - `customSortAlphabet` - When the sort property is "custom alphabet" you can define the alphabet to use here, which will match the beginning of each group name and sort against it. If group titles do not match any custom alphabet entry, then they will be put after groups that do, ordered alphanumerically.
|
||||
>
|
||||
> This can be a list of single characters, like "zyxw..." or comma delimited strings for more control, like "sdxl,pro,sd,n,p".
|
||||
>
|
||||
> Note, when two group title match the same custom alphabet entry, the normal alphanumeric alphabet breaks the tie. For instance, a custom alphabet of "e,s,d" will order groups names like "SDXL, SEGS, Detailer" eventhough the custom alphabet has an "e" before "d" (where one may expect "SE" to be before "SD").
|
||||
>
|
||||
> To have "SEGS" appear before "SDXL" you can use longer strings. For instance, the custom alphabet value of "se,s,f" would work here.
|
||||
> - `toggleRestriction` - Optionally, attempt to restrict the number of widgets that can be enabled to a maximum of one, or always one.
|
||||
>
|
||||
> _Note: If using "max one" or "always one" then this is only enforced when clicking a toggle on this node; if nodes within groups are changed outside of the initial toggle click, then these restriction will not be enforced, and could result in a state where more than one toggle is enabled. This could also happen if nodes are overlapped with multiple groups._
|
||||
> </details>
|
||||
|
||||
## Fast Groups Bypasser
|
||||
> _Same as **Fast Groups Muter** above, but sets the connected nodes to "Bypass" instead of "Mute"_
|
||||
|
||||
|
||||
## Fast Muter
|
||||
> A powerful 'control panel' node to quickly toggle connected node allowing it to quickly be muted or enabled
|
||||
> A powerful 'control panel' node to quickly toggle connected nodes allowing them to quickly be muted or enabled
|
||||
> <details>
|
||||
> <summary>ℹ️ <i>More Information</i></summary>
|
||||
>
|
||||
@@ -137,7 +195,7 @@ Note, you can right-click on a bunch of the rgthree-comfy nodes and select `🛟
|
||||
> Same as Fast Muter but sets the connected nodes to "Bypass"
|
||||
|
||||
## Fast Actions Button
|
||||
> Oh boy, this node allows you to semi-automate connected nodes and/ror ConfyUI.
|
||||
> Oh boy, this node allows you to semi-automate connected nodes and/or ConfyUI.
|
||||
> <details>
|
||||
> <summary>ℹ️ <i>More Information</i></summary>
|
||||
>
|
||||
@@ -172,6 +230,14 @@ Note, you can right-click on a bunch of the rgthree-comfy nodes and select `🛟
|
||||
> </details>
|
||||
|
||||
|
||||
## Random Unmuter
|
||||
> An advanced node used to unmute one of its inputs randomly when the graph is queued (and, immediately mute it back).
|
||||
> <details>
|
||||
> <summary>ℹ️ <i>More Information</i></summary>
|
||||
>
|
||||
> - **Note:** All input nodes MUST be muted to start; if not this node will not randomly unmute another. (This is powerful, as the generated image can be dragged in and the chosen input will already by unmuted and work w/o any further action.)
|
||||
> - **Tip:** Connect a Repeater's output to this nodes input and place that Repeater on a group without any other inputs, and it will mute/unmute the entire group.
|
||||
> </details>
|
||||
|
||||
|
||||
# Advanced Techniques
|
||||
@@ -236,19 +302,28 @@ rgthree-comfy adds several improvements, features, and optimizations to ComfyUI
|
||||
> </details>
|
||||
|
||||
|
||||
## "Queue Node" in right-click menu for output nodes
|
||||
> Sometimes you want to just equeue a single node, without executing the entire workflow. Well, now you can do just that by right-clicking on an output node and selecting `Queue Node (rgthree)`.
|
||||
## "Queue Selected Output Nodes" in right-click menu
|
||||
> Sometimes you want to just queue one or two paths to specific output node(s) without executing the entire workflow. Well, now you can do just that by right-clicking on an output node and selecting `Queue Selected Output Nodes (rgthree)`.
|
||||
>
|
||||
> <details>
|
||||
> <summary>ℹ️ <i>More Information</i></summary>
|
||||
>
|
||||
> - Note that this can have side-effects on the entire workflow.
|
||||
> - Select the _output_ nodes you want to execute.
|
||||
>
|
||||
> - The whole workflow is serialized, and then we trim what we don't want for the backend. So things like all seed random/increment/decrement will run even if that node isn't being sent in the end, etc.
|
||||
> - Note: Only output nodes are captured and traversed, not all selected nodes. So if you select an output AND a node from a different path, only the path connected to the output will be executed and not non-output nodes, even if they were selected.
|
||||
>
|
||||
> - Note: The whole workflow is serialized, and then we trim what we don't want for the backend. So things like all seed random/increment/decrement will run even if that node isn't being sent in the end, etc.
|
||||
>
|
||||
> </details>
|
||||
|
||||
|
||||
## Auto-Nest Subdirectories in long Combos
|
||||
> _(Off by default while experimenting, turn on in rgthree-comfy settings)_.
|
||||
>
|
||||
> Automatically detect top-level subdirectories in long combo lists (like, Load Checkpoint) and break out into sub directories.
|
||||
|
||||
|
||||
|
||||
## "Copy Image" in right-click menu
|
||||
> Right clicking on a node that has an image should have a context-menu item of "Copy Image" will allow you to copy the image right to your clipboard
|
||||
>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
// COPY THIS FILE BEFORE MAKING CHANGES TO: rgthree_config.json
|
||||
{
|
||||
"log_level": "WARN",
|
||||
"features": {
|
||||
"patch_recursive_execution": true,
|
||||
"show_alerts_for_corrupt_workflows": false,
|
||||
"monitor_for_corrupt_links": false,
|
||||
"menu_queue_selected_nodes": true,
|
||||
"menu_auto_nest": {
|
||||
"subdirs": null,
|
||||
"threshold": 20
|
||||
},
|
||||
"progress_bar": {
|
||||
"enabled": true,
|
||||
"height": 16
|
||||
"height": 16,
|
||||
"position": "top"
|
||||
}
|
||||
},
|
||||
"nodes": {
|
||||
|
||||
@@ -25,6 +25,7 @@ type ConfigurationSchema = {
|
||||
key: string;
|
||||
type: ConfigType;
|
||||
label: string;
|
||||
options?: string[] | number[];
|
||||
description?: string;
|
||||
subconfig?: ConfigurationSchema[];
|
||||
};
|
||||
@@ -53,8 +54,30 @@ const CONFIGURABLE: { features: ConfigurationSchema[] } = {
|
||||
type: ConfigType.NUMBER,
|
||||
label: "Height of the bar",
|
||||
},
|
||||
{
|
||||
key: "features.progress_bar.position",
|
||||
type: ConfigType.STRING,
|
||||
label: "Position at top or bottom of window",
|
||||
options: ["top", "bottom"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "features.menu_queue_selected_nodes",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "(Menu) Show 'Queue Selected Output Nodes'",
|
||||
description:
|
||||
"Will show a menu item in the right-click context menus to queue (only) the selected " +
|
||||
"output nodes.",
|
||||
},
|
||||
{
|
||||
key: "features.menu_auto_nest.subdirs",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "(Menu) Auto Nest Subdirectories",
|
||||
description:
|
||||
"When a large, flat list of values contain sub-directories, auto nest them. (Like, for " +
|
||||
"a large list of checkpoints).",
|
||||
},
|
||||
{
|
||||
key: "features.show_alerts_for_corrupt_workflows",
|
||||
type: ConfigType.BOOLEAN,
|
||||
@@ -66,7 +89,6 @@ const CONFIGURABLE: { features: ConfigurationSchema[] } = {
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new fieldrow for main or sub configuration items.
|
||||
*/
|
||||
@@ -89,16 +111,26 @@ function fieldrow(item: ConfigurationSchema) {
|
||||
});
|
||||
|
||||
let input;
|
||||
if (item.type === ConfigType.BOOLEAN) {
|
||||
if (item.options?.length) {
|
||||
input = $el<HTMLSelectElement>(`select[id="${item.key}"]`, {
|
||||
parent: container,
|
||||
children: item.options.map((o) => {
|
||||
return $el<HTMLOptionElement>(`option[value="${String(o)}"]`, {
|
||||
text: String(o),
|
||||
selected: o === initialValue,
|
||||
});
|
||||
}),
|
||||
});
|
||||
} else if (item.type === ConfigType.BOOLEAN) {
|
||||
container.classList.toggle("-checked", initialValue);
|
||||
input = $el<HTMLInputElement>(`input[type="checkbox"][id="${item.key}"]`, {
|
||||
checked: initialValue,
|
||||
parent: container,
|
||||
checked: initialValue,
|
||||
});
|
||||
} else {
|
||||
input = $el(`input[id="${item.key}"]`, {
|
||||
value: initialValue,
|
||||
parent: container,
|
||||
value: initialValue,
|
||||
});
|
||||
}
|
||||
$el("div.fieldrow-value", { children: [input], parent: container });
|
||||
@@ -184,7 +216,7 @@ export class RgthreeConfigDialog extends RgthreeDialog {
|
||||
const name = el.dataset["name"]!;
|
||||
const type = el.dataset["type"]!;
|
||||
const initialValue = CONFIG_SERVICE.getConfigValue(name);
|
||||
let currentValueEl = $$("input, textarea", el)[0] as HTMLInputElement;
|
||||
let currentValueEl = $$("input, textarea, select", el)[0] as HTMLInputElement;
|
||||
let currentValue: any = null;
|
||||
if (type === String(ConfigType.BOOLEAN)) {
|
||||
currentValue = currentValueEl.checked;
|
||||
|
||||
+65
-19
@@ -1,8 +1,54 @@
|
||||
// @ts-ignore
|
||||
import { app } from "../../scripts/app.js";
|
||||
import type { LGraphCanvas, ContextMenuItem } from "typings/litegraph.js";
|
||||
import type {
|
||||
LGraphCanvas as TLGraphCanvas,
|
||||
ContextMenuItem,
|
||||
LGraphNode,
|
||||
LiteGraph as TLiteGraph,
|
||||
} from "typings/litegraph.js";
|
||||
import type { ComfyNodeConstructor, ComfyObjectInfo } from "typings/comfy.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { SERVICE as CONFIG_SERVICE } from "./config_service.js";
|
||||
|
||||
declare const LiteGraph: typeof TLiteGraph;
|
||||
declare const LGraphCanvas: typeof TLGraphCanvas;
|
||||
|
||||
function getOutputNodesFromSelected(canvas: TLGraphCanvas) {
|
||||
return (
|
||||
(canvas.selected_nodes &&
|
||||
Object.values(canvas.selected_nodes).filter((n) => {
|
||||
return (
|
||||
n.mode != LiteGraph.NEVER &&
|
||||
((n.constructor as any).nodeData as ComfyObjectInfo)?.output_node
|
||||
);
|
||||
})) ||
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
function showQueueNodesMenuIfOutputNodesAreSelected(
|
||||
existingOptions: ContextMenuItem[],
|
||||
node?: LGraphNode,
|
||||
) {
|
||||
if (CONFIG_SERVICE.getConfigValue("features.menu_queue_selected_nodes") != false) {
|
||||
const canvas = app.canvas as TLGraphCanvas;
|
||||
const outputNodes = getOutputNodesFromSelected(canvas);
|
||||
const menuItem = {
|
||||
content: `Queue Selected Output Nodes (rgthree) `,
|
||||
className: "rgthree-contextmenu-item",
|
||||
callback: () => {
|
||||
rgthree.queueOutputNodes(outputNodes.map((n) => n.id));
|
||||
},
|
||||
disabled: !outputNodes.length,
|
||||
};
|
||||
|
||||
let idx = existingOptions.findIndex((o) => o?.content === "Outputs") + 1;
|
||||
idx = idx || existingOptions.findIndex((o) => o?.content === "Align") + 1;
|
||||
idx = idx || 3;
|
||||
existingOptions.splice(idx, 0, menuItem);
|
||||
}
|
||||
return existingOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a "Queue Node" menu item to all output nodes, working with `rgthree.queueOutputNode` to
|
||||
@@ -11,24 +57,24 @@ import { rgthree } from "./rgthree.js";
|
||||
app.registerExtension({
|
||||
name: "rgthree.QueueNode",
|
||||
async beforeRegisterNodeDef(nodeType: ComfyNodeConstructor, nodeData: ComfyObjectInfo) {
|
||||
if (nodeData.output_node == true) {
|
||||
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
||||
nodeType.prototype.getExtraMenuOptions = function (
|
||||
canvas: LGraphCanvas,
|
||||
options: ContextMenuItem[],
|
||||
) {
|
||||
getExtraMenuOptions ? getExtraMenuOptions.apply(this, arguments) : undefined;
|
||||
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
||||
nodeType.prototype.getExtraMenuOptions = function (
|
||||
canvas: TLGraphCanvas,
|
||||
options: ContextMenuItem[],
|
||||
) {
|
||||
getExtraMenuOptions ? getExtraMenuOptions.apply(this, arguments) : undefined;
|
||||
showQueueNodesMenuIfOutputNodesAreSelected(options, this);
|
||||
};
|
||||
},
|
||||
|
||||
const menuItem: ContextMenuItem = {
|
||||
content: `Queue Node (rgthree)`,
|
||||
className: "rgthree-contextmenu-item",
|
||||
callback: () => {
|
||||
rgthree.queueOutputNode(this.id);
|
||||
},
|
||||
};
|
||||
const idx = options.findIndex(o => o?.content === "Outputs") + 1 || options.length - 1;
|
||||
options.splice(idx, 0, menuItem);
|
||||
};
|
||||
}
|
||||
async setup() {
|
||||
console.log("rgthree-menu-setup");
|
||||
|
||||
const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args: any[]) {
|
||||
const options = getCanvasMenuOptions.apply(this, [...args] as any);
|
||||
showQueueNodesMenuIfOutputNodesAreSelected(options);
|
||||
return options;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -221,10 +221,6 @@
|
||||
@import '../common/css/buttons.scss';
|
||||
@import '../common/css/dialog.scss';
|
||||
|
||||
body.rgthree-dialog-open > *:not(.rgthree-dialog):not(.rgthree-top-messages-container) {
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -271,7 +267,7 @@ body.rgthree-dialog-open > *:not(.rgthree-dialog):not(.rgthree-top-messages-cont
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow label {
|
||||
padding-left: 28px;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow:not(.-checked) ~ .fieldrow {
|
||||
.rgthree-dialog.-settings .fieldrow:first-child:not(.-checked) ~ .fieldrow {
|
||||
display: none;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow:hover {
|
||||
|
||||
@@ -32,6 +32,14 @@ export enum LogLevel {
|
||||
DEBUG,
|
||||
}
|
||||
|
||||
const LogLevelKeyToLogLevel: { [key: string]: LogLevel } = {
|
||||
IMPORTANT: LogLevel.IMPORTANT,
|
||||
ERROR: LogLevel.ERROR,
|
||||
WARN: LogLevel.WARN,
|
||||
INFO: LogLevel.INFO,
|
||||
DEBUG: LogLevel.DEBUG,
|
||||
};
|
||||
|
||||
type ConsoleLogFns = "log" | "error" | "warn" | "debug" | "info";
|
||||
const LogLevelToMethod: { [key in LogLevel]: ConsoleLogFns } = {
|
||||
[LogLevel.IMPORTANT]: "log",
|
||||
@@ -41,14 +49,14 @@ const LogLevelToMethod: { [key in LogLevel]: ConsoleLogFns } = {
|
||||
[LogLevel.DEBUG]: "debug",
|
||||
};
|
||||
const LogLevelToCSS: { [key in LogLevel]: string } = {
|
||||
[LogLevel.IMPORTANT]: "font-weight:bold; color:blue;",
|
||||
[LogLevel.IMPORTANT]: "font-weight: bold; color: blue;",
|
||||
[LogLevel.ERROR]: "",
|
||||
[LogLevel.WARN]: "",
|
||||
[LogLevel.INFO]: "",
|
||||
[LogLevel.DEBUG]: "font-style: italic;",
|
||||
[LogLevel.INFO]: "font-style: italic; color: blue;",
|
||||
[LogLevel.DEBUG]: "font-style: italic; color: #333;",
|
||||
};
|
||||
|
||||
let GLOBAL_LOG_LEVEL = LogLevel.DEBUG;
|
||||
let GLOBAL_LOG_LEVEL = LogLevel.ERROR;
|
||||
|
||||
/** A basic wrapper around logger. */
|
||||
class Logger {
|
||||
@@ -113,6 +121,10 @@ class LogSession {
|
||||
this.log(LogLevel.INFO, message, ...args);
|
||||
}
|
||||
|
||||
infoParts(message?: string, ...args: any[]) {
|
||||
return this.logParts(LogLevel.INFO, message, ...args);
|
||||
}
|
||||
|
||||
error(message?: string, ...args: any[]) {
|
||||
this.log(LogLevel.ERROR, message, ...args);
|
||||
}
|
||||
@@ -145,7 +157,7 @@ class Rgthree extends EventTarget {
|
||||
private progressBarEl: RgthreeProgressBar | null = null;
|
||||
|
||||
/** Stores a node id that we will use to queu only that output node (with `queueOutputNode`). */
|
||||
private queueNodeId: number | null = null;
|
||||
private queueNodeIds: number[] | null = null;
|
||||
|
||||
/** Are any functional keys pressed in this given moment? */
|
||||
ctrlKey = false;
|
||||
@@ -170,6 +182,11 @@ class Rgthree extends EventTarget {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const logLevel =
|
||||
LogLevelKeyToLogLevel[CONFIG_SERVICE.getConfigValue("log_level")] ?? GLOBAL_LOG_LEVEL;
|
||||
this.setLogLevel(logLevel);
|
||||
|
||||
window.addEventListener("keydown", (e) => {
|
||||
this.handleKeydown(e);
|
||||
});
|
||||
@@ -246,6 +263,13 @@ class Rgthree extends EventTarget {
|
||||
const fontSize = Math.max(10, Number(height) - 10);
|
||||
this.progressBarEl.style.fontSize = `${fontSize}px`;
|
||||
this.progressBarEl.style.fontWeight = fontSize <= 12 ? "bold" : "normal";
|
||||
if (CONFIG_SERVICE.getConfigValue("features.progress_bar.position") === "bottom") {
|
||||
this.progressBarEl.style.bottom = `0px`;
|
||||
this.progressBarEl.style.top = `auto`;
|
||||
} else {
|
||||
this.progressBarEl.style.top = `0px`;
|
||||
this.progressBarEl.style.bottom = `auto`;
|
||||
}
|
||||
} else {
|
||||
this.progressBarEl?.remove();
|
||||
}
|
||||
@@ -393,17 +417,21 @@ class Rgthree extends EventTarget {
|
||||
|
||||
/**
|
||||
* Wraps an `app.queuePrompt` call setting a specific node id that we will inspect and change the
|
||||
* serialized graph when set (below, in our `graphToPrompt` override).
|
||||
* serialized graph right before being sent (below, in our `api.queuePrompt` override).
|
||||
*/
|
||||
async queueOutputNode(nodeId: number) {
|
||||
async queueOutputNodes(nodeIds: number[]) {
|
||||
try {
|
||||
this.queueNodeId = nodeId;
|
||||
this.queueNodeIds = nodeIds;
|
||||
await app.queuePrompt();
|
||||
} catch(e) {
|
||||
const [n, v] = this.logParts(LogLevel.ERROR, `There was an error queuing node #${nodeId}`, e);
|
||||
} catch (e) {
|
||||
const [n, v] = this.logParts(
|
||||
LogLevel.ERROR,
|
||||
`There was an error queuing nodes ${nodeIds}`,
|
||||
e,
|
||||
);
|
||||
console[n]?.(...v);
|
||||
} finally {
|
||||
this.queueNodeId = null;
|
||||
this.queueNodeIds = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,7 +452,6 @@ class Rgthree extends EventTarget {
|
||||
return newOutput;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize a bunch of hooks into ComfyUI and/or LiteGraph itself so we can either keep state or
|
||||
* context on what's happening so nodes can respond appropriately. This is usually to fix broken
|
||||
@@ -464,18 +491,27 @@ class Rgthree extends EventTarget {
|
||||
app.graphToPrompt = async function () {
|
||||
rgthree.dispatchEvent(new CustomEvent("graph-to-prompt"));
|
||||
let promise = graphToPrompt.apply(app, [...arguments]);
|
||||
const result = (await promise as ComfyApiPrompt);
|
||||
// If queueNodeId is set, then we wonly want to queue one node. We'll capture that and rewrite
|
||||
// the api format, 'output' field.
|
||||
if (rgthree.queueNodeId != null) {
|
||||
const oldOutput = result.output;
|
||||
const newOutput = rgthree.recursiveAddNodes(String(rgthree.queueNodeId), oldOutput, {});
|
||||
result.output = newOutput;
|
||||
}
|
||||
await promise;
|
||||
rgthree.dispatchEvent(new CustomEvent("graph-to-prompt-end"));
|
||||
return promise;
|
||||
};
|
||||
|
||||
// Override the queuePrompt for api to intercept the prompt output and, if queueNodeIds is set,
|
||||
// then we only want to queue those nodes, by rewriting the api format (prompt 'output' field)
|
||||
// so only those are evaluated.
|
||||
const apiQueuePrompt = api.queuePrompt as Function;
|
||||
api.queuePrompt = async function (index: number, prompt: ComfyApiPrompt) {
|
||||
if (rgthree.queueNodeIds?.length && prompt.output) {
|
||||
const oldOutput = prompt.output;
|
||||
let newOutput = {};
|
||||
for (const queueNodeId of rgthree.queueNodeIds) {
|
||||
rgthree.recursiveAddNodes(String(queueNodeId), oldOutput, newOutput);
|
||||
}
|
||||
prompt.output = newOutput;
|
||||
}
|
||||
return apiQueuePrompt.apply(app, [index, prompt]);
|
||||
};
|
||||
|
||||
// Hook into a clean call; allow us to clear and rgthree messages.
|
||||
const clean = app.clean;
|
||||
app.clean = function () {
|
||||
|
||||
@@ -113,80 +113,3 @@ body.rgthree-dialog-open > *:not(.rgthree-dialog):not(.rgthree-top-messages-cont
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings {
|
||||
width: 100%;
|
||||
}
|
||||
.rgthree-dialog.-settings fieldset {
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
padding: 0 12px 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.rgthree-dialog.-settings fieldset > legend {
|
||||
margin-left: 8px;
|
||||
padding: 0 8px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.rgthree-dialog.-settings .formrow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.rgthree-dialog.-settings .formrow + .formrow {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow > label {
|
||||
flex: 1 1 auto;
|
||||
user-select: none;
|
||||
padding: 8px 12px 12px;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow > label span {
|
||||
font-weight: bold;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow > label small {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: calc(11rem / 16);
|
||||
opacity: 0.75;
|
||||
padding-left: 16px;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow {
|
||||
font-size: 0.9rem;
|
||||
border-top: 1px dotted rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow label {
|
||||
padding-left: 28px;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow:not(.-checked) ~ .fieldrow {
|
||||
display: none;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow span {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > .fieldrow-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
flex: 0 0 auto;
|
||||
width: 50%;
|
||||
max-width: 230px;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow.-type-boolean > .fieldrow-value {
|
||||
max-width: 64px;
|
||||
}
|
||||
.rgthree-dialog.-settings .fieldrow.-type-number input {
|
||||
width: 48px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow input[type="checkbox"] {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ function getChild(value: any) : HTMLElement|DocumentFragment|Text|null {
|
||||
|
||||
|
||||
export function setAttribute(element: HTMLElement, attribute: string, value: any) {
|
||||
const isRemoving = value == null;
|
||||
let isRemoving = value == null;
|
||||
|
||||
if (attribute === 'default') {
|
||||
attribute = RGX_DEFAULT_VALUE_PROP.test(element.nodeName) ? 'value' : 'text';
|
||||
@@ -227,8 +227,13 @@ export function setAttribute(element: HTMLElement, attribute: string, value: any
|
||||
}
|
||||
|
||||
} else if (['checked', 'disabled', 'readonly', 'required', 'selected'].includes(attribute)) {
|
||||
// Could be input, button, etc. We are not discriminate.
|
||||
(element as HTMLInputElement)[attribute as 'checked'] = !!value;
|
||||
// Could be input, button, etc. We are not discriminate.
|
||||
(element as HTMLInputElement)[attribute as 'checked'] = !!value;
|
||||
if (!value) {
|
||||
(element as HTMLInputElement).removeAttribute(attribute);
|
||||
} else {
|
||||
(element as HTMLInputElement).setAttribute(attribute, attribute);
|
||||
}
|
||||
|
||||
} else if (DIRECT_ATTRIBUTE_MAP.hasOwnProperty(attribute)) {
|
||||
if (isRemoving) {
|
||||
|
||||
@@ -37,8 +37,28 @@ const CONFIGURABLE = {
|
||||
type: ConfigType.NUMBER,
|
||||
label: "Height of the bar",
|
||||
},
|
||||
{
|
||||
key: "features.progress_bar.position",
|
||||
type: ConfigType.STRING,
|
||||
label: "Position at top or bottom of window",
|
||||
options: ["top", "bottom"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "features.menu_queue_selected_nodes",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "(Menu) Show 'Queue Selected Output Nodes'",
|
||||
description: "Will show a menu item in the right-click context menus to queue (only) the selected " +
|
||||
"output nodes.",
|
||||
},
|
||||
{
|
||||
key: "features.menu_auto_nest.subdirs",
|
||||
type: ConfigType.BOOLEAN,
|
||||
label: "(Menu) Auto Nest Subdirectories",
|
||||
description: "When a large, flat list of values contain sub-directories, auto nest them. (Like, for " +
|
||||
"a large list of checkpoints).",
|
||||
},
|
||||
{
|
||||
key: "features.show_alerts_for_corrupt_workflows",
|
||||
type: ConfigType.BOOLEAN,
|
||||
@@ -49,6 +69,7 @@ const CONFIGURABLE = {
|
||||
],
|
||||
};
|
||||
function fieldrow(item) {
|
||||
var _a;
|
||||
const initialValue = CONFIG_SERVICE.getConfigValue(item.key);
|
||||
const container = $el(`div.fieldrow.-type-${TYPE_TO_STRING[item.type]}`, {
|
||||
dataset: {
|
||||
@@ -65,17 +86,28 @@ function fieldrow(item) {
|
||||
parent: container,
|
||||
});
|
||||
let input;
|
||||
if (item.type === ConfigType.BOOLEAN) {
|
||||
if ((_a = item.options) === null || _a === void 0 ? void 0 : _a.length) {
|
||||
input = $el(`select[id="${item.key}"]`, {
|
||||
parent: container,
|
||||
children: item.options.map((o) => {
|
||||
return $el(`option[value="${String(o)}"]`, {
|
||||
text: String(o),
|
||||
selected: o === initialValue,
|
||||
});
|
||||
}),
|
||||
});
|
||||
}
|
||||
else if (item.type === ConfigType.BOOLEAN) {
|
||||
container.classList.toggle("-checked", initialValue);
|
||||
input = $el(`input[type="checkbox"][id="${item.key}"]`, {
|
||||
checked: initialValue,
|
||||
parent: container,
|
||||
checked: initialValue,
|
||||
});
|
||||
}
|
||||
else {
|
||||
input = $el(`input[id="${item.key}"]`, {
|
||||
value: initialValue,
|
||||
parent: container,
|
||||
value: initialValue,
|
||||
});
|
||||
}
|
||||
$el("div.fieldrow-value", { children: [input], parent: container });
|
||||
@@ -152,7 +184,7 @@ export class RgthreeConfigDialog extends RgthreeDialog {
|
||||
const name = el.dataset["name"];
|
||||
const type = el.dataset["type"];
|
||||
const initialValue = CONFIG_SERVICE.getConfigValue(name);
|
||||
let currentValueEl = $$("input, textarea", el)[0];
|
||||
let currentValueEl = $$("input, textarea, select", el)[0];
|
||||
let currentValue = null;
|
||||
if (type === String(ConfigType.BOOLEAN)) {
|
||||
currentValue = currentValueEl.checked;
|
||||
|
||||
@@ -1,22 +1,50 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { rgthree } from "./rgthree.js";
|
||||
import { SERVICE as CONFIG_SERVICE } from "./config_service.js";
|
||||
function getOutputNodesFromSelected(canvas) {
|
||||
return ((canvas.selected_nodes &&
|
||||
Object.values(canvas.selected_nodes).filter((n) => {
|
||||
var _a;
|
||||
return (n.mode != LiteGraph.NEVER &&
|
||||
((_a = n.constructor.nodeData) === null || _a === void 0 ? void 0 : _a.output_node));
|
||||
})) ||
|
||||
[]);
|
||||
}
|
||||
function showQueueNodesMenuIfOutputNodesAreSelected(existingOptions, node) {
|
||||
if (CONFIG_SERVICE.getConfigValue("features.menu_queue_selected_nodes") != false) {
|
||||
const canvas = app.canvas;
|
||||
const outputNodes = getOutputNodesFromSelected(canvas);
|
||||
const menuItem = {
|
||||
content: `Queue Selected Output Nodes (rgthree) `,
|
||||
className: "rgthree-contextmenu-item",
|
||||
callback: () => {
|
||||
rgthree.queueOutputNodes(outputNodes.map((n) => n.id));
|
||||
},
|
||||
disabled: !outputNodes.length,
|
||||
};
|
||||
let idx = existingOptions.findIndex((o) => (o === null || o === void 0 ? void 0 : o.content) === "Outputs") + 1;
|
||||
idx = idx || existingOptions.findIndex((o) => (o === null || o === void 0 ? void 0 : o.content) === "Align") + 1;
|
||||
idx = idx || 3;
|
||||
existingOptions.splice(idx, 0, menuItem);
|
||||
}
|
||||
return existingOptions;
|
||||
}
|
||||
app.registerExtension({
|
||||
name: "rgthree.QueueNode",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.output_node == true) {
|
||||
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
||||
nodeType.prototype.getExtraMenuOptions = function (canvas, options) {
|
||||
getExtraMenuOptions ? getExtraMenuOptions.apply(this, arguments) : undefined;
|
||||
const menuItem = {
|
||||
content: `Queue Node (rgthree)`,
|
||||
className: "rgthree-contextmenu-item",
|
||||
callback: () => {
|
||||
rgthree.queueOutputNode(this.id);
|
||||
},
|
||||
};
|
||||
const idx = options.findIndex(o => (o === null || o === void 0 ? void 0 : o.content) === "Outputs") + 1 || options.length - 1;
|
||||
options.splice(idx, 0, menuItem);
|
||||
};
|
||||
}
|
||||
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
||||
nodeType.prototype.getExtraMenuOptions = function (canvas, options) {
|
||||
getExtraMenuOptions ? getExtraMenuOptions.apply(this, arguments) : undefined;
|
||||
showQueueNodesMenuIfOutputNodesAreSelected(options, this);
|
||||
};
|
||||
},
|
||||
async setup() {
|
||||
console.log("rgthree-menu-setup");
|
||||
const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
|
||||
const options = getCanvasMenuOptions.apply(this, [...args]);
|
||||
showQueueNodesMenuIfOutputNodesAreSelected(options);
|
||||
return options;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -481,104 +481,7 @@ body.rgthree-dialog-open > *:not(.rgthree-dialog):not(.rgthree-top-messages-cont
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow:not(.-checked) ~ .fieldrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow span {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > .fieldrow-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
flex: 0 0 auto;
|
||||
width: 50%;
|
||||
max-width: 230px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow.-type-boolean > .fieldrow-value {
|
||||
max-width: 64px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow.-type-number input {
|
||||
width: 48px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow input[type=checkbox] {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
body.rgthree-dialog-open > *:not(.rgthree-dialog):not(.rgthree-top-messages-container) {
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings fieldset {
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
padding: 0 12px 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings fieldset > legend {
|
||||
margin-left: 8px;
|
||||
padding: 0 8px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .formrow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .formrow + .formrow {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > label {
|
||||
flex: 1 1 auto;
|
||||
user-select: none;
|
||||
padding: 8px 12px 12px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > label span {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > label small {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 0.6875rem;
|
||||
opacity: 0.75;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow {
|
||||
font-size: 0.9rem;
|
||||
border-top: 1px dotted rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow label {
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow:not(.-checked) ~ .fieldrow {
|
||||
.rgthree-dialog.-settings .fieldrow:first-child:not(.-checked) ~ .fieldrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,13 @@ export var LogLevel;
|
||||
LogLevel[LogLevel["INFO"] = 4] = "INFO";
|
||||
LogLevel[LogLevel["DEBUG"] = 5] = "DEBUG";
|
||||
})(LogLevel || (LogLevel = {}));
|
||||
const LogLevelKeyToLogLevel = {
|
||||
IMPORTANT: LogLevel.IMPORTANT,
|
||||
ERROR: LogLevel.ERROR,
|
||||
WARN: LogLevel.WARN,
|
||||
INFO: LogLevel.INFO,
|
||||
DEBUG: LogLevel.DEBUG,
|
||||
};
|
||||
const LogLevelToMethod = {
|
||||
[LogLevel.IMPORTANT]: "log",
|
||||
[LogLevel.ERROR]: "error",
|
||||
@@ -24,13 +31,13 @@ const LogLevelToMethod = {
|
||||
[LogLevel.DEBUG]: "debug",
|
||||
};
|
||||
const LogLevelToCSS = {
|
||||
[LogLevel.IMPORTANT]: "font-weight:bold; color:blue;",
|
||||
[LogLevel.IMPORTANT]: "font-weight: bold; color: blue;",
|
||||
[LogLevel.ERROR]: "",
|
||||
[LogLevel.WARN]: "",
|
||||
[LogLevel.INFO]: "",
|
||||
[LogLevel.DEBUG]: "font-style: italic;",
|
||||
[LogLevel.INFO]: "font-style: italic; color: blue;",
|
||||
[LogLevel.DEBUG]: "font-style: italic; color: #333;",
|
||||
};
|
||||
let GLOBAL_LOG_LEVEL = LogLevel.DEBUG;
|
||||
let GLOBAL_LOG_LEVEL = LogLevel.ERROR;
|
||||
class Logger {
|
||||
log(level, message, ...args) {
|
||||
var _a;
|
||||
@@ -69,6 +76,9 @@ class LogSession {
|
||||
info(message, ...args) {
|
||||
this.log(LogLevel.INFO, message, ...args);
|
||||
}
|
||||
infoParts(message, ...args) {
|
||||
return this.logParts(LogLevel.INFO, message, ...args);
|
||||
}
|
||||
error(message, ...args) {
|
||||
this.log(LogLevel.ERROR, message, ...args);
|
||||
}
|
||||
@@ -78,11 +88,12 @@ class LogSession {
|
||||
}
|
||||
class Rgthree extends EventTarget {
|
||||
constructor() {
|
||||
var _a;
|
||||
super();
|
||||
this.api = api;
|
||||
this.settingsDialog = null;
|
||||
this.progressBarEl = null;
|
||||
this.queueNodeId = null;
|
||||
this.queueNodeIds = null;
|
||||
this.ctrlKey = false;
|
||||
this.altKey = false;
|
||||
this.metaKey = false;
|
||||
@@ -97,6 +108,8 @@ class Rgthree extends EventTarget {
|
||||
this.canvasCurrentlyCopyingToClipboard = false;
|
||||
this.canvasCurrentlyCopyingToClipboardWithMultipleNodes = false;
|
||||
this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = null;
|
||||
const logLevel = (_a = LogLevelKeyToLogLevel[CONFIG_SERVICE.getConfigValue("log_level")]) !== null && _a !== void 0 ? _a : GLOBAL_LOG_LEVEL;
|
||||
this.setLogLevel(logLevel);
|
||||
window.addEventListener("keydown", (e) => {
|
||||
this.handleKeydown(e);
|
||||
});
|
||||
@@ -161,6 +174,14 @@ class Rgthree extends EventTarget {
|
||||
const fontSize = Math.max(10, Number(height) - 10);
|
||||
this.progressBarEl.style.fontSize = `${fontSize}px`;
|
||||
this.progressBarEl.style.fontWeight = fontSize <= 12 ? "bold" : "normal";
|
||||
if (CONFIG_SERVICE.getConfigValue("features.progress_bar.position") === "bottom") {
|
||||
this.progressBarEl.style.bottom = `0px`;
|
||||
this.progressBarEl.style.top = `auto`;
|
||||
}
|
||||
else {
|
||||
this.progressBarEl.style.top = `0px`;
|
||||
this.progressBarEl.style.bottom = `auto`;
|
||||
}
|
||||
}
|
||||
else {
|
||||
(_a = this.progressBarEl) === null || _a === void 0 ? void 0 : _a.remove();
|
||||
@@ -270,18 +291,18 @@ class Rgthree extends EventTarget {
|
||||
},
|
||||
];
|
||||
}
|
||||
async queueOutputNode(nodeId) {
|
||||
async queueOutputNodes(nodeIds) {
|
||||
var _a;
|
||||
try {
|
||||
this.queueNodeId = nodeId;
|
||||
this.queueNodeIds = nodeIds;
|
||||
await app.queuePrompt();
|
||||
}
|
||||
catch (e) {
|
||||
const [n, v] = this.logParts(LogLevel.ERROR, `There was an error queuing node #${nodeId}`, e);
|
||||
const [n, v] = this.logParts(LogLevel.ERROR, `There was an error queuing nodes ${nodeIds}`, e);
|
||||
(_a = console[n]) === null || _a === void 0 ? void 0 : _a.call(console, ...v);
|
||||
}
|
||||
finally {
|
||||
this.queueNodeId = null;
|
||||
this.queueNodeIds = null;
|
||||
}
|
||||
}
|
||||
recursiveAddNodes(nodeId, oldOutput, newOutput) {
|
||||
@@ -325,15 +346,23 @@ class Rgthree extends EventTarget {
|
||||
app.graphToPrompt = async function () {
|
||||
rgthree.dispatchEvent(new CustomEvent("graph-to-prompt"));
|
||||
let promise = graphToPrompt.apply(app, [...arguments]);
|
||||
const result = await promise;
|
||||
if (rgthree.queueNodeId != null) {
|
||||
const oldOutput = result.output;
|
||||
const newOutput = rgthree.recursiveAddNodes(String(rgthree.queueNodeId), oldOutput, {});
|
||||
result.output = newOutput;
|
||||
}
|
||||
await promise;
|
||||
rgthree.dispatchEvent(new CustomEvent("graph-to-prompt-end"));
|
||||
return promise;
|
||||
};
|
||||
const apiQueuePrompt = api.queuePrompt;
|
||||
api.queuePrompt = async function (index, prompt) {
|
||||
var _a;
|
||||
if (((_a = rgthree.queueNodeIds) === null || _a === void 0 ? void 0 : _a.length) && prompt.output) {
|
||||
const oldOutput = prompt.output;
|
||||
let newOutput = {};
|
||||
for (const queueNodeId of rgthree.queueNodeIds) {
|
||||
rgthree.recursiveAddNodes(String(queueNodeId), oldOutput, newOutput);
|
||||
}
|
||||
prompt.output = newOutput;
|
||||
}
|
||||
return apiQueuePrompt.apply(app, [index, prompt]);
|
||||
};
|
||||
const clean = app.clean;
|
||||
app.clean = function () {
|
||||
rgthree.clearAllMessages();
|
||||
|
||||
@@ -114,96 +114,3 @@
|
||||
body.rgthree-dialog-open > *:not(.rgthree-dialog):not(.rgthree-top-messages-container) {
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings fieldset {
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
padding: 0 12px 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings fieldset > legend {
|
||||
margin-left: 8px;
|
||||
padding: 0 8px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .formrow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .formrow + .formrow {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > label {
|
||||
flex: 1 1 auto;
|
||||
user-select: none;
|
||||
padding: 8px 12px 12px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > label span {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > label small {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 0.6875rem;
|
||||
opacity: 0.75;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow {
|
||||
font-size: 0.9rem;
|
||||
border-top: 1px dotted rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow label {
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow:not(.-checked) ~ .fieldrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow ~ .fieldrow span {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow > .fieldrow-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
flex: 0 0 auto;
|
||||
width: 50%;
|
||||
max-width: 230px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow.-type-boolean > .fieldrow-value {
|
||||
max-width: 64px;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow.-type-number input {
|
||||
width: 48px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.rgthree-dialog.-settings .fieldrow input[type=checkbox] {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ function getChild(value) {
|
||||
return null;
|
||||
}
|
||||
export function setAttribute(element, attribute, value) {
|
||||
const isRemoving = value == null;
|
||||
let isRemoving = value == null;
|
||||
if (attribute === 'default') {
|
||||
attribute = RGX_DEFAULT_VALUE_PROP.test(element.nodeName) ? 'value' : 'text';
|
||||
}
|
||||
@@ -205,6 +205,12 @@ export function setAttribute(element, attribute, value) {
|
||||
}
|
||||
else if (['checked', 'disabled', 'readonly', 'required', 'selected'].includes(attribute)) {
|
||||
element[attribute] = !!value;
|
||||
if (!value) {
|
||||
element.removeAttribute(attribute);
|
||||
}
|
||||
else {
|
||||
element.setAttribute(attribute, attribute);
|
||||
}
|
||||
}
|
||||
else if (DIRECT_ATTRIBUTE_MAP.hasOwnProperty(attribute)) {
|
||||
if (isRemoving) {
|
||||
|
||||
Reference in New Issue
Block a user