Three small UX fixes from the v0.1.7 post-release inspection:
1. Categories: the catch-all `Koolook/VFX` subfolder is replaced with
three more granular ones:
- Easy_Version, EasyAIPipeline -> Koolook/Pipeline
- easy_ImageBatch -> Koolook/Image (joins EasyResize)
- Easy_hdr_VAE_encode/decode -> Koolook/VAE
2. Easy_hdr_VAE_encode / Easy_hdr_VAE_decode display names: previously
the display equalled the node ID (`Easy_hdr_VAE_encode`) so the user
couldn't find them by typing `koolook` in the node-add search filter
— they were registered correctly but invisible to the filter. Now:
"Easy_hdr_VAE_encode" -> "Easy HDR VAE Encode (Koolook)"
"Easy_hdr_VAE_decode" -> "Easy HDR VAE Decode (Koolook)"
Matches the title-case " (Koolook)"-suffixed convention used by the
rest of the pack since v0.1.7.
3. README node tables + categories list updated to match.
Class IDs unchanged everywhere -- saved workflows that reference any of
these IDs continue to load and run unchanged. CATEGORY is purely a UI
hint and only affects the node-add menu hierarchy.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
125 lines
5.0 KiB
Python
125 lines
5.0 KiB
Python
import torch
|
|
|
|
class easy_ImageBatch:
|
|
"""
|
|
A custom ComfyUI node for VFX workflows that takes 1-4 keyframe images with specified frame positions
|
|
and creates an IMAGE batch sequence of a given total length, filling undefined frames with selected placeholder color (black or 50% gray).
|
|
Also outputs a MASK batch where occupied frames are black (0.0) and empty are white (1.0), directly compatible with standard mask nodes.
|
|
Supports VFX-style frame numbering with a start_frame offset (internal indices are 0-based).
|
|
Useful for preparing sparse control sequences for video models like Wan 2.2, where placeholder frames indicate no latent update.
|
|
Assumes all input images have identical dimensions.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"total_frames": ("INT", {
|
|
"default": 16,
|
|
"min": 1,
|
|
"max": 1024,
|
|
"step": 1,
|
|
"display": "number"
|
|
}),
|
|
"start_frame": ("INT", {
|
|
"default": 0,
|
|
"min": 0,
|
|
"max": 999999,
|
|
"step": 1,
|
|
"display": "number"
|
|
}),
|
|
"placeholder_color": (["Black", "Gray"], {
|
|
"default": "Black"
|
|
}),
|
|
"image1": ("IMAGE", ),
|
|
"image1_frame": ("INT", {
|
|
"default": 0,
|
|
"min": 0,
|
|
"max": 999999,
|
|
"step": 1,
|
|
"display": "number"
|
|
}),
|
|
},
|
|
"optional": {
|
|
"image2": ("IMAGE", ),
|
|
"image2_frame": ("INT", {
|
|
"default": 4,
|
|
"min": 0,
|
|
"max": 999999,
|
|
"step": 1,
|
|
"display": "number"
|
|
}),
|
|
"image3": ("IMAGE", ),
|
|
"image3_frame": ("INT", {
|
|
"default": 8,
|
|
"min": 0,
|
|
"max": 999999,
|
|
"step": 1,
|
|
"display": "number"
|
|
}),
|
|
"image4": ("IMAGE", ),
|
|
"image4_frame": ("INT", {
|
|
"default": 12,
|
|
"min": 0,
|
|
"max": 999999,
|
|
"step": 1,
|
|
"display": "number"
|
|
}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE", "MASK", )
|
|
RETURN_NAMES = ("image_batch", "mask_batch", )
|
|
FUNCTION = "create_batch"
|
|
CATEGORY = "Koolook/Image"
|
|
OUTPUT_NODE = False # Not necessarily an output node, but can be chained
|
|
|
|
def create_batch(self, total_frames, start_frame, placeholder_color, image1, image1_frame, image2=None, image2_frame=None, image3=None, image3_frame=None, image4=None, image4_frame=None):
|
|
if image1.shape[0] != 1:
|
|
raise ValueError("Each input should be a single IMAGE (batch size 1), not a pre-batched tensor.")
|
|
|
|
h, w, c = image1.shape[1:]
|
|
device = image1.device
|
|
dtype = image1.dtype
|
|
|
|
# Vectorized creation of image_batch
|
|
fill_value = 0.0 if placeholder_color == "Black" else 0.5
|
|
image_batch = torch.full((total_frames, h, w, c), fill_value, device=device, dtype=dtype)
|
|
|
|
# Vectorized creation of mask_batch (single channel), initialized to 1.0 (white for empty)
|
|
mask_batch = torch.ones((total_frames, h, w), device=device, dtype=torch.float32)
|
|
|
|
# Collect keyframes and compute indices
|
|
keyframes = [(image1[0], image1_frame)]
|
|
if image2 is not None and image2_frame is not None:
|
|
if image2.shape[1:] != (h, w, c):
|
|
raise ValueError("All images must have the same height, width, and channels.")
|
|
keyframes.append((image2[0], image2_frame))
|
|
if image3 is not None and image3_frame is not None:
|
|
if image3.shape[1:] != (h, w, c):
|
|
raise ValueError("All images must have the same height, width, and channels.")
|
|
keyframes.append((image3[0], image3_frame))
|
|
if image4 is not None and image4_frame is not None:
|
|
if image4.shape[1:] != (h, w, c):
|
|
raise ValueError("All images must have the same height, width, and channels.")
|
|
keyframes.append((image4[0], image4_frame))
|
|
|
|
# Place keyframes
|
|
for img, f in keyframes:
|
|
index = f - start_frame
|
|
if 0 <= index < total_frames:
|
|
image_batch[index] = img
|
|
mask_batch[index] = 0.0 # Black (0.0) for occupied
|
|
else:
|
|
print(f"Warning: Frame {f} maps to index {index} which is out of range (0 to {total_frames-1}); ignoring this keyframe.")
|
|
|
|
return (image_batch, mask_batch, )
|
|
|
|
# Node mappings for ComfyUI registration
|
|
NODE_CLASS_MAPPINGS = {
|
|
"easy_ImageBatch": easy_ImageBatch
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"easy_ImageBatch": "Easy Image Batch (Koolook)"
|
|
} |