Files

711 lines
31 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import logging
import math
import torch
logger = logging.getLogger(__name__)
import comfy.latent_formats
import comfy.model_base
import comfy.model_management as mm
from comfy.utils import common_upscale
from .diffusers_helper.memory import move_model_to_device_with_memory_preservation
from .diffusers_helper.pipelines.k_diffusion_hunyuan import sample_hunyuan
from .diffusers_helper.utils import crop_or_pad_yield_mask
from .nodes import HyVideoModel, HyVideoModelConfig
vae_scaling_factor = 0.476986
class FramePackSingleFrameSampler:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("FramePackMODEL",),
"positive": ("CONDITIONING",),
"negative": ("CONDITIONING",),
"start_latent": (
"LATENT",
{"tooltip": "init Latents to use for image2image"},
),
"steps": ("INT", {"default": 20, "min": 1, "tooltip": "Sampling steps (10=fast preview, 20=balanced, 30=high quality)"}),
"use_teacache": (
"BOOLEAN",
{"default": False, "tooltip": "Use TeaCache for faster sampling (recommended for 15+ steps)."},
),
"teacache_rel_l1_thresh": (
"FLOAT",
{
"default": 0.15,
"min": 0.0,
"max": 1.0,
"step": 0.01,
"tooltip": "The threshold for the relative L1 loss.",
},
),
"cfg": (
"FLOAT",
{"default": 1.0, "min": 0.0, "max": 30.0, "step": 0.01},
),
"guidance_scale": (
"FLOAT",
{"default": 10.0, "min": 0.0, "max": 32.0, "step": 0.01},
),
"shift": (
"FLOAT",
{"default": 0.0, "min": 0.0, "max": 1000.0, "step": 0.01},
),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF}),
"latent_window_size": (
"INT",
{
"default": 9,
"min": 1,
"max": 33,
"step": 1,
"tooltip": "The size of the latent window to use for sampling.",
},
),
"gpu_memory_preservation": (
"FLOAT",
{
"default": 6.0,
"min": 0.0,
"max": 128.0,
"step": 0.1,
"tooltip": "The amount of GPU memory to preserve.",
},
),
"sampler": (["unipc_bh1", "unipc_bh2"], {"default": "unipc_bh1"}),
},
"optional": {
"image_embeds": ("CLIP_VISION_OUTPUT",),
"initial_samples": (
"LATENT",
{"tooltip": "init Latents to use for image2image variation"},
),
"denoise_strength": (
"FLOAT",
{"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01},
),
"reference_latents": (
"REFERENCE_LATENT_LIST",
{"tooltip": "List of reference image latents for kisekaeichi mode (up to 8)"},
),
"reference_image_embeds": (
"REFERENCE_EMBEDS_LIST",
{"tooltip": "List of reference image CLIP embeds for kisekaeichi mode (up to 8)"},
),
"reference_masks": (
"REFERENCE_MASK_LIST",
{"tooltip": "List of reference masks for selective features (up to 8, optional)"},
),
"target_index": (
"INT",
{
"default": 5,
"min": 0,
"max": 16,
"step": 1,
"tooltip": "Target index for kisekaeichi mode (recommended: 1)",
},
),
"control_index": (
"STRING",
{
"default": "0;10",
"tooltip": "Control indices separated by semicolon (preferred) or comma: 0;7;8;9;10 or 0,7,8,9,10 (default: 0;10)",
},
),
"input_mask": (
"MASK",
{"tooltip": "Input mask for selective application"},
),
},
}
RETURN_TYPES = ("LATENT",)
RETURN_NAMES = ("samples",)
FUNCTION = "process"
CATEGORY = "FramePackWrapper"
DESCRIPTION = "Single frame sampler with auto-enabled Kisekaeichi (style transfer) support for multiple references"
def process(
self,
model,
shift,
positive,
negative,
latent_window_size,
use_teacache,
teacache_rel_l1_thresh,
steps,
cfg,
guidance_scale,
seed,
sampler,
gpu_memory_preservation,
start_latent=None,
image_embeds=None,
initial_samples=None,
denoise_strength=1.0,
reference_latents=None,
reference_image_embeds=None,
reference_masks=None,
target_index=1,
control_index="0;10",
input_mask=None,
):
print("=== 1フレーム推論モード(musubi-tuner完全互換) ===")
# Use control_index parameter directly (no more one_frame_inference)
# Auto-enable kisekaeichi when reference inputs are provided
use_kisekaeichi = reference_latents is not None and len(reference_latents) > 0
if use_kisekaeichi:
print("Kisekaeichi(着せ替え)モード有効 (自動検出)")
print(f"target_index: {target_index}")
print(f"control_index raw value: '{control_index}'")
print(f"control_index type: {type(control_index)}")
# Parse control indices from parameter (support both semicolon and comma for backward compatibility)
if ';' in control_index:
# New format (musubi-tuner compatible): semicolon-separated
control_indices_list = [int(x.strip()) for x in control_index.split(';') if x.strip()]
else:
# Old format: comma-separated for backward compatibility
control_indices_list = [int(x.strip()) for x in control_index.split(',') if x.strip()]
if len(control_indices_list) == 0:
control_indices_list = [0, 10] # default
print(f"parsed control_index: {control_indices_list}")
transformer = model["transformer"]
base_dtype = model["dtype"]
device = mm.get_torch_device()
offload_device = mm.unet_offload_device()
mm.unload_all_models()
mm.cleanup_models()
mm.soft_empty_cache()
# Latent処理
if start_latent is not None:
start_latent = start_latent["samples"] * vae_scaling_factor
if initial_samples is not None:
initial_samples = initial_samples["samples"] * vae_scaling_factor
# Process reference latents if provided
processed_reference_latents = []
if use_kisekaeichi and reference_latents is not None:
for i, ref_latent in enumerate(reference_latents):
processed_latent = ref_latent["samples"] * vae_scaling_factor
processed_reference_latents.append(processed_latent)
print(f"参照画像latent {i+1}: {processed_latent.shape}")
print("start_latent", start_latent.shape)
B, C, T, H, W = start_latent.shape
# 画像エンベッディング処理
if image_embeds is not None:
start_image_encoder_last_hidden_state = image_embeds[
"last_hidden_state"
].to(device, base_dtype)
else:
start_image_encoder_last_hidden_state = None
# Process reference image embeds if provided
processed_reference_embeds = []
if use_kisekaeichi and reference_image_embeds is not None:
for i, ref_embeds in enumerate(reference_image_embeds):
embed_state = ref_embeds["last_hidden_state"].to(device, base_dtype)
processed_reference_embeds.append(embed_state)
print(f"参照画像 {i+1} のCLIP embeddingを設定しました")
# Validate that latents and embeds have same count
if use_kisekaeichi:
if len(processed_reference_latents) != len(processed_reference_embeds):
raise ValueError(f"Reference latents count ({len(processed_reference_latents)}) must match embeds count ({len(processed_reference_embeds)})")
if len(processed_reference_latents) == 0:
raise ValueError("No reference latents provided for kisekaeichi mode")
# テキストエンベッディング処理
llama_vec = positive[0][0].to(device, base_dtype)
clip_l_pooler = positive[0][1]["pooled_output"].to(device, base_dtype)
if not math.isclose(cfg, 1.0):
llama_vec_n = negative[0][0].to(device, base_dtype)
clip_l_pooler_n = negative[0][1]["pooled_output"].to(device, base_dtype)
else:
llama_vec_n = torch.zeros_like(llama_vec, device=device)
clip_l_pooler_n = torch.zeros_like(clip_l_pooler, device=device)
llama_vec, llama_attention_mask = crop_or_pad_yield_mask(llama_vec, length=512)
llama_vec_n, llama_attention_mask_n = crop_or_pad_yield_mask(
llama_vec_n, length=512
)
# シード設定
rnd = torch.Generator("cpu").manual_seed(seed)
# === 一つ目のコードに完全準拠した設定 ===
# 1フレームモード固定設定
sample_num_frames = 1
total_latent_sections = 1
latent_padding = 0
latent_padding_size = latent_padding * latent_window_size # 0
# 一つ目のコードと同じインデックス構造
indices = torch.arange(
0, sum([1, latent_padding_size, latent_window_size, 1, 2, 16])
).unsqueeze(0)
split_sizes = [1, latent_padding_size, latent_window_size, 1, 2, 16]
# latent_padding_sizeが0の場合の分割(一つ目のコードと完全同一)
if latent_padding_size == 0:
clean_latent_indices_pre = indices[:, 0:1]
latent_indices = indices[:, 1 : 1 + latent_window_size]
clean_latent_indices_post = indices[
:, 1 + latent_window_size : 2 + latent_window_size
]
clean_latent_2x_indices = indices[
:, 2 + latent_window_size : 4 + latent_window_size
]
clean_latent_4x_indices = indices[
:, 4 + latent_window_size : 20 + latent_window_size
]
blank_indices = torch.empty((1, 0), dtype=torch.long)
else:
(
clean_latent_indices_pre,
blank_indices,
latent_indices,
clean_latent_indices_post,
clean_latent_2x_indices,
clean_latent_4x_indices,
) = indices.split(split_sizes, dim=1)
# 一つ目のコードの重要な処理:1フレームモード時のone_frame_inference処理
if sample_num_frames == 1:
if use_kisekaeichi and len(processed_reference_latents) > 0:
print("=== Kisekaeichi モード設定(完全版) ===")
# 一つ目のコードのone_frame_inference処理を完全再現
one_frame_inference = set()
one_frame_inference.add(f"target_index={target_index}")
# control_indexは後でcontrol_index_listから設定されるため、ここでは追加しない
# musubi-tuner仕様:latent_indicesを初期化
latent_indices = torch.zeros((1, 1), dtype=torch.int64) # 1x1 latent index for target image
latent_indices[:, 0] = latent_window_size # musubi-tunerのデフォルト値
# Apply target_index parameter (musubi-tuner仕様)
latent_indices[:, 0] = target_index
print(f"target_index設定 (musubi-tuner仕様): {target_index}")
# この部分は削除(後でまとめて処理するため)
# control_latentsのダミーを作成(一つ目のコードと同じ構造)
control_latents = torch.zeros(
size=(1, 16, 1 + 2 + 16, H, W), dtype=torch.float32, device="cpu"
)
# musubi-tuner仕様:control_latentsを直接結合(マスク適用は後で実行)
control_latents = []
# 入力画像をcontrol_latentsに追加
clean_latents_pre = start_latent.to(torch.float32).cpu()
if len(clean_latents_pre.shape) < 5:
clean_latents_pre = clean_latents_pre.unsqueeze(2)
control_latents.append(clean_latents_pre)
# 参照画像をcontrol_latentsに追加
for i, ref_latent in enumerate(processed_reference_latents):
clean_latent_post = ref_latent[:, :, 0:1, :, :].to(torch.float32).cpu()
control_latents.append(clean_latent_post)
# musubi-tuner仕様:ゼロlatentをclean_latents_postとして追加
control_latents.append(torch.zeros((1, 16, 1, H, W), dtype=torch.float32))
print(f"Add zero latents as clean latents post for one frame inference.")
# musubi-tuner仕様:control_latentsを直接結合
clean_latents = torch.cat(control_latents, dim=2) # (1, 16, num_control_images, H//8, W//8)
# musubi-tuner仕様:clean_latent_indicesを設定
clean_latent_indices = torch.zeros((1, len(control_latents)), dtype=torch.int64)
if "no_post" not in []: # one_frame_inferenceは空なので常にtrue
clean_latent_indices[:, -1] = 1 + latent_window_size # default index for clean latents post
# musubi-tuner仕様:control_indices_listをclean_latent_indicesに動的適用
if control_indices_list:
print(f"control_indices動的適用開始: {control_indices_list}")
i = 0
while i < len(control_indices_list) and i < clean_latent_indices.shape[1]:
control_index = int(control_indices_list[i])
clean_latent_indices[:, i] = control_index
print(f"clean_latent_indices[:, {i}] = {control_index}")
i += 1
print(f"最終的なclean_latent_indices: {clean_latent_indices}")
else:
print("control_indices_listが空のため、デフォルト設定を使用")
# musubi-tuner仕様:マスク適用(clean_latents生成後)
for i in range(len(control_latents)):
mask_image = None
# 入力画像のマスク適用
if i == 0 and input_mask is not None:
print("入力画像マスクを適用中(musubi-tuner仕様)...")
try:
height_latent, width_latent = clean_latents.shape[-2:]
if isinstance(input_mask, torch.Tensor):
input_mask_tensor = input_mask
else:
input_mask_tensor = torch.from_numpy(input_mask)
input_mask_resized = (
common_upscale(
input_mask_tensor.unsqueeze(0).unsqueeze(0),
width_latent,
height_latent,
"bilinear",
"center",
)
.squeeze(0)
.squeeze(0)
)
mask_image = input_mask_resized.to(clean_latents.device)[None, None, :, :]
print("入力画像マスクを適用しました")
except Exception as e:
print(f"入力マスク適用エラー: {e}")
# 参照画像のマスク適用
elif i > 0 and i < len(control_latents) - 1: # -1はゼロlatentなので除外
ref_index = i - 1 # 最初が入力画像なので-1
if reference_masks is not None and ref_index < len(reference_masks):
print(f"参照画像 {ref_index+1} マスクを適用中(musubi-tuner仕様)...")
try:
height_latent, width_latent = clean_latents.shape[-2:]
reference_mask = reference_masks[ref_index]
if isinstance(reference_mask, torch.Tensor):
reference_mask_tensor = reference_mask
else:
reference_mask_tensor = torch.from_numpy(reference_mask)
reference_mask_resized = (
common_upscale(
reference_mask_tensor.unsqueeze(0).unsqueeze(0),
width_latent,
height_latent,
"bilinear",
"center",
)
.squeeze(0)
.squeeze(0)
)
mask_image = reference_mask_resized.to(clean_latents.device)[None, None, :, :]
print(f"参照画像 {ref_index+1} マスクを適用しました")
except Exception as e:
print(f"参照マスク {ref_index+1} 適用エラー: {e}")
# マスクを適用
if mask_image is not None:
clean_latents[:, :, i : i + 1, :, :] = clean_latents[:, :, i : i + 1, :, :] * mask_image
# Always disable 2x/4x processing for kisekaeichi mode
clean_latents_2x_param = None
clean_latent_2x_indices = None
clean_latents_2x = None
clean_latents_4x_param = None
clean_latent_4x_indices = None
clean_latents_4x = None
print("Kisekaeichi: 2x/4xインデックスを無効化しました")
# 画像エンベッディングの処理(musubi-tuner仕様:セクション毎の個別処理)
# musubi-tunerではセクション毎に個別のimage_encoder_last_hidden_stateを使用
# 複数リファレンス時は最初の参照画像のエンベッディングを使用(musubi-tuner互換)
if len(processed_reference_embeds) > 0:
# musubi-tuner互換:最初の参照画像エンベッディングを使用
image_encoder_last_hidden_state = processed_reference_embeds[0]
print(f"musubi-tuner互換:最初の参照画像エンベッディングを使用 (参照画像数: {len(processed_reference_embeds)})")
else:
image_encoder_last_hidden_state = start_image_encoder_last_hidden_state
print(f"Kisekaeichi設定完了(musubi-tuner完全互換):")
print(f" - clean_latents.shape: {clean_latents.shape} (入力+参照{len(processed_reference_latents)}個+ゼロlatent)")
print(f" - latent_indices: {latent_indices} (初期値: {latent_window_size} -> target: {target_index})")
print(f" - clean_latent_indices: {clean_latent_indices} (control_indices適用済み)")
print(f" - sample_num_frames: {sample_num_frames}")
print(f" - control_indices適用: {control_indices_list}")
print(f" - control_latents数: {len(control_latents)}")
print(f" - マスク適用: clean_latents生成後")
print(f" - 2x/4x無効化: True")
else:
# 通常モード(参照画像なし)
# control_indicesは通常モードでは使用しない
all_indices = torch.arange(0, latent_window_size).unsqueeze(0)
latent_indices = all_indices[:, -1:]
clean_latents_pre = start_latent.to(torch.float32).cpu()
if len(clean_latents_pre.shape) < 5:
clean_latents_pre = clean_latents_pre.unsqueeze(2)
clean_latents_post = torch.zeros_like(clean_latents_pre)
clean_latents = torch.cat(
[clean_latents_pre, clean_latents_post], dim=2
)
clean_latent_indices = torch.cat(
[clean_latent_indices_pre, clean_latent_indices_post], dim=1
)
# 通常モードでのインデックス調整
clean_latent_indices = torch.tensor(
[[0]],
dtype=clean_latent_indices.dtype,
device=clean_latent_indices.device,
)
clean_latents = clean_latents[:, :, :1, :, :]
clean_latents_2x_param = None
clean_latents_4x_param = None
clean_latent_2x_indices = None
clean_latent_4x_indices = None
# 2x, 4x latentsの設定も無効化
clean_latents_2x = None
clean_latents_4x = None
print("Kisekaeichi: 2x/4xインデックスを無効化しました")
image_encoder_last_hidden_state = start_image_encoder_last_hidden_state
print("通常モード設定:")
print(f" - clean_latents.shape: {clean_latents.shape}")
print(f" - latent_indices: {latent_indices}")
print(f" - clean_latent_indices: {clean_latent_indices}")
# 初期サンプルの処理
input_init_latents = None
if initial_samples is not None:
input_init_latents = initial_samples[:, :, 0:1, :, :].to(device)
print("初期サンプルを設定しました")
# ComfyUI用のセットアップ
comfy_model = HyVideoModel(
HyVideoModelConfig(base_dtype),
model_type=comfy.model_base.ModelType.FLOW,
device=device,
)
patcher = comfy.model_patcher.ModelPatcher(
comfy_model, device, torch.device("cpu")
)
from latent_preview import prepare_callback
callback = prepare_callback(patcher, steps)
# モデルをGPUに移動
move_model_to_device_with_memory_preservation(
transformer,
target_device=device,
preserved_memory_gb=gpu_memory_preservation,
)
# TeaCacheの設定
if use_teacache:
transformer.initialize_teacache(
enable_teacache=True,
num_steps=steps,
rel_l1_thresh=teacache_rel_l1_thresh,
)
else:
transformer.initialize_teacache(enable_teacache=False)
print("=== サンプリング開始 ===")
print(f"sample_num_frames: {sample_num_frames}")
print(f"clean_latents使用フレーム数: {clean_latents.shape[2]}")
print(f"clean_latent_2x_indices: {clean_latent_2x_indices}")
print(f"clean_latent_4x_indices: {clean_latent_4x_indices}")
with torch.autocast(
device_type=mm.get_autocast_device(device), dtype=base_dtype, enabled=True
):
generated_latents = sample_hunyuan(
transformer=transformer,
sampler=sampler,
initial_latent=input_init_latents,
strength=denoise_strength,
width=W * 8,
height=H * 8,
frames=sample_num_frames, # 1フレーム固定
real_guidance_scale=cfg,
distilled_guidance_scale=guidance_scale,
guidance_rescale=0,
shift=shift if shift != 0 else None,
num_inference_steps=steps,
generator=rnd,
prompt_embeds=llama_vec,
prompt_embeds_mask=llama_attention_mask,
prompt_poolers=clip_l_pooler,
negative_prompt_embeds=llama_vec_n,
negative_prompt_embeds_mask=llama_attention_mask_n,
negative_prompt_poolers=clip_l_pooler_n,
device=device,
dtype=base_dtype,
image_embeddings=image_encoder_last_hidden_state,
latent_indices=latent_indices,
clean_latents=clean_latents,
clean_latent_indices=clean_latent_indices,
clean_latents_2x=clean_latents_2x,
clean_latent_2x_indices=clean_latent_2x_indices,
clean_latents_4x=clean_latents_4x,
clean_latent_4x_indices=clean_latent_4x_indices,
callback=callback,
)
# クリーンアップ
transformer.to(offload_device)
mm.soft_empty_cache()
# 出力処理
if use_kisekaeichi:
mode_info = f"Kisekaeichi(musubi-tuner完全互換、参照{len(processed_reference_latents)}個)"
else:
mode_info = "通常"
print(
f"=== 1フレーム生成完了 ({mode_info}モード): {generated_latents.shape} ==="
)
return ({"samples": generated_latents / vae_scaling_factor},)
class ReferenceLatentList:
@classmethod
def INPUT_TYPES(s):
return {
"required": {},
"optional": {
"reference_1": ("LATENT", {"tooltip": "Reference latent 1"}),
"reference_2": ("LATENT", {"tooltip": "Reference latent 2"}),
"reference_3": ("LATENT", {"tooltip": "Reference latent 3"}),
"reference_4": ("LATENT", {"tooltip": "Reference latent 4"}),
"reference_5": ("LATENT", {"tooltip": "Reference latent 5"}),
"reference_6": ("LATENT", {"tooltip": "Reference latent 6"}),
"reference_7": ("LATENT", {"tooltip": "Reference latent 7"}),
"reference_8": ("LATENT", {"tooltip": "Reference latent 8"}),
},
}
RETURN_TYPES = ("REFERENCE_LATENT_LIST",)
RETURN_NAMES = ("reference_latents",)
FUNCTION = "create_list"
CATEGORY = "FramePackWrapper/References"
DESCRIPTION = "Combine up to 8 reference latents into a list for multi-reference Kisekaeichi mode"
def create_list(self, **kwargs):
reference_list = []
for i in range(1, 9):
ref_key = f"reference_{i}"
if ref_key in kwargs and kwargs[ref_key] is not None:
reference_list.append(kwargs[ref_key])
if len(reference_list) == 0:
return (None,)
print(f"Created reference latent list with {len(reference_list)} items")
return (reference_list,)
class ReferenceEmbedsList:
@classmethod
def INPUT_TYPES(s):
return {
"required": {},
"optional": {
"embeds_1": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 1"}),
"embeds_2": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 2"}),
"embeds_3": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 3"}),
"embeds_4": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 4"}),
"embeds_5": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 5"}),
"embeds_6": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 6"}),
"embeds_7": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 7"}),
"embeds_8": ("CLIP_VISION_OUTPUT", {"tooltip": "Reference embeds 8"}),
},
}
RETURN_TYPES = ("REFERENCE_EMBEDS_LIST",)
RETURN_NAMES = ("reference_embeds",)
FUNCTION = "create_list"
CATEGORY = "FramePackWrapper/References"
DESCRIPTION = "Combine up to 8 reference CLIP vision embeddings into a list for multi-reference Kisekaeichi mode"
def create_list(self, **kwargs):
embeds_list = []
for i in range(1, 9):
embeds_key = f"embeds_{i}"
if embeds_key in kwargs and kwargs[embeds_key] is not None:
embeds_list.append(kwargs[embeds_key])
if len(embeds_list) == 0:
return (None,)
print(f"Created reference embeds list with {len(embeds_list)} items")
return (embeds_list,)
class ReferenceMaskList:
@classmethod
def INPUT_TYPES(s):
return {
"required": {},
"optional": {
"mask_1": ("MASK", {"tooltip": "Reference mask 1"}),
"mask_2": ("MASK", {"tooltip": "Reference mask 2"}),
"mask_3": ("MASK", {"tooltip": "Reference mask 3"}),
"mask_4": ("MASK", {"tooltip": "Reference mask 4"}),
"mask_5": ("MASK", {"tooltip": "Reference mask 5"}),
"mask_6": ("MASK", {"tooltip": "Reference mask 6"}),
"mask_7": ("MASK", {"tooltip": "Reference mask 7"}),
"mask_8": ("MASK", {"tooltip": "Reference mask 8"}),
},
}
RETURN_TYPES = ("REFERENCE_MASK_LIST",)
RETURN_NAMES = ("reference_masks",)
FUNCTION = "create_list"
CATEGORY = "FramePackWrapper/References"
DESCRIPTION = "Combine up to 8 reference masks into a list for multi-reference Kisekaeichi mode (optional)"
def create_list(self, **kwargs):
mask_list = []
for i in range(1, 9):
mask_key = f"mask_{i}"
if mask_key in kwargs and kwargs[mask_key] is not None:
mask_list.append(kwargs[mask_key])
if len(mask_list) == 0:
return (None,)
print(f"Created reference mask list with {len(mask_list)} items")
return (mask_list,)
NODE_CLASS_MAPPINGS = {
"FramePackSingleFrameSampler": FramePackSingleFrameSampler,
"ReferenceLatentList": ReferenceLatentList,
"ReferenceEmbedsList": ReferenceEmbedsList,
"ReferenceMaskList": ReferenceMaskList,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FramePackSingleFrameSampler": "FramePack Single Frame Sampler",
"ReferenceLatentList": "Reference Latent List",
"ReferenceEmbedsList": "Reference Embeds List",
"ReferenceMaskList": "Reference Mask List",
}