Files
malkuthro-ComfyUI-Koolook/docs/user_guide/nodes/koolook_image/easy_image_batch.md
T
88dbfedf86 feat(nodes): Easy Image Batch keyframes_insert + insert-over-source +… (#246)
* feat(nodes): Easy Image Batch keyframes_insert + insert-over-source + clean batch

- Rename optional input keyframe_batch -> keyframes_insert (node ID unchanged).
- Insert-over-source mode: when keyframes_insert AND source_batch are both
  connected, composite the inserts over the source cut window (instead of the
  placeholder background). keyframes_insert alone keeps the offset/reconstruct
  behaviour over placeholder.
- No image source connected -> clean placeholder batch sized by new
  width/height fallback widgets, instead of raising. width/height are appended
  LAST so older saved workflows keep their positional widgets_values alignment
  (fixes a mid-list insertion that shifted placeholder_color into source_frames).
- alpha_batch marks only inserted/placed positions; source background is not
  counted as placed.
- New pure planner plan_source_base_fill (torch-free) alongside
  plan_offset_placements; both unit-tested in CI.
- Docs: behavior matrix spec, interactive HTML demo, and a generated
  "all versions" ComfyUI demo workflow under docs/designs/.

Not yet implemented (specced as DRAFT in docs/designs, pending maintainer
approval + tie-break decision): image1-4 "super-overwrite" layer across all
modes, and removal of the Mode 2 unconnected-slot source_batch fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(deps): raise aiohttp [test] floor to >=3.14.1

The constraints lock already pins aiohttp 3.14.1 (the CVE-2026-54273..54280
fix line), but pyproject's [test] extra still declared aiohttp>=3.9.
Dependabot keys off pyproject, not the lock, so the open declared range kept
eight range-based alerts open even though the installed version is patched.
Raising the floor to >=3.14.1 aligns the declared range with the lock and
clears the alerts once this reaches the default branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(nodes): Easy Image Batch image1-4 super-overwrite + source passthrough

Make the four manual slots the top compositing layer in every mode (select
and insert): a connected imageN overwrites output frame imageN_frame on top
of the sequence/inserts and the background. Higher slot wins a collision
(image4 > image1); a slot beats a sequence pick / insert at the same position.
Extracted as the torch-free plan_slot_overwrites planner + a shared
_apply_slot_overwrites helper used by both code paths.

Two model fixes ride along, per the finalised behaviour matrix:
- An unconnected slot now contributes nothing -- imageN_frame is only a
  placement target for a wired image, never a source_batch pick. The old
  unwired-slot fallback (which surfaced seemingly-random frames) is removed.
- source_batch + an empty frame list (no insert) now passes the source cut
  window straight through instead of a placeholder batch, matching the
  insert-mode empty-list passthrough.

Tests: new plan_slot_overwrites planner cases + fake-tensor behaviour tests
covering slot-beats-pick/insert, higher-slot-wins, passthrough-not-placed, and
the removed phantom fallback. Docstring, tooltips, user guide, design spec
(promoted DRAFT -> canonical), and CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(nodes): assert selected_image_batch = picks/inserts + slots; drop matrix HTML demo

selected_image_batch already packs every placed position -- both the
source_frames picks / inserts and the manual-slot overwrites, with slots
winning on overlap. Add explicit select- and insert-mode tests asserting the
composition and ascending order, locking the behaviour in.

Also delete docs/designs/easy-image-batch-matrix.html (stale interactive demo
built against an earlier draft) and drop its companion reference from the
design spec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(nodes): Easy Image Batch passthrough inpaint mask + frame-range syntax

Two refinements to the Easy Image Batch passthrough/list behaviour:

- Short-source passthrough is now an extend/inpaint mask. When source_batch is
  connected and the frame list is empty, the covered source frames are kept
  content (alpha 0.0) and the uncovered tail becomes the selection
  (selected_image_batch / selected_frames) with alpha 1.0 -- "keep the first N,
  generate the rest". Full coverage -> no gap, empty selection. The selection
  is the gap here (inverse of select mode); insert mode is unchanged.
- The source_frames field accepts inclusive ranges: "1-5, 7, 9, 14-17" expands
  to 1,2,3,4,5,7,9,14,15,16,17, in both select and insert, via a shared
  torch-free parse_frame_tokens (descending/non-integer tokens warn).

Tests cover the gap selection (short/full/cut-start-offset/slot-in-gap), the
parser (ranges, bad tokens), and range syntax end-to-end. Docstring, tooltip,
user guide, design spec, and CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(designs): remove draft-era Easy Image Batch demo workflow + builder

Delete easy-image-batch-demo-setup.json and build_easy_image_batch_demo.py
(the leftover draft-era demo, sibling to the already-removed matrix HTML) and
drop the companion reference from the behavior spec. A purpose-built demo will
replace them later; the spec matrix + the node remain the canonical reference.
Also drop the now-stale "(proposed)" from the spec title (it is canonical).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(user-guide): remove stale v0.1.5 Easy Image Batch screenshot

The screenshot predates the node's keyframes_insert / super-overwrite /
passthrough rework and is unreferenced. Removing it to be re-created fresh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: point Easy Image Batch docs at the bundled Select-and-Rebuild demo

Note the runnable demo workflow ("Easy Image Batch - Select and Rebuild",
bundled into the Kforge Labs Workflows sidebar folder via #248) in both the
user guide and the design spec, replacing the generated demo removed earlier.
Referenced by path (not a clickable link) since the workflow lands via #248.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(nodes): address PR review — keyframe_batch alias + input guards

- Restore keyframe_batch as a deprecated alias of keyframes_insert so
  pre-rename workflows (origin/main exposes the input) keep loading; an explicit
  keyframes_insert wins. Follows docs/maintainers/node-versioning.md
  (alias-then-deprecate); removal deferred to a release boundary.
- Warn instead of silently returning a clean batch when source_frames is
  non-empty but no image source is connected.
- Cap frame-list range expansion at 8192 frames/token to guard against typos
  like 1-99999999.
- Docs: reframe the bundled demo-workflow note as forthcoming (the JSON lands
  via #248, not on this branch); document the alias, cap, and no-source guard in
  the user guide / design spec / CHANGELOG.

Tests: alias routing + precedence, no-source warning, range cap + boundary
(46 node tests). Fresh-eyes review: no correctness/compat issues (the IMAGE
alias input adds no widget-position shift).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: point Easy Image Batch docs at the now-merged Select-and-Rebuild demo

#248 merged to main and is now in this branch (via the main merge), so the
demo workflow exists at docs/workflows/kforge-labs-workflows/. Repoint the user
guide and design spec at it directly (present tense, real folder link) instead
of the earlier "coming soon / pending" framing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: AI Machine <ai.machine@x.baconcph.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 00:51:16 +02:00

17 KiB
Raw Blame History

Easy Image Batch (Koolook)

Aspect Value
Display name Easy Image Batch (Koolook)
Node ID easy_ImageBatch
ComfyUI category Koolook/Image
Source Koolook-native
Source file k_easy_image_batch.py

What it does

Places up to N keyframes on a 1-based VFX timeline, then outputs a cut window of that timeline as a fixed-length IMAGE batch. Empty positions are filled with a chosen placeholder colour (Black / 50 % Gray / White). Pairs the image batch with an alpha_batch mask marking selected vs empty frames, and also returns just the in-cut keyframes packed back-to-back as a separate short-batch output.

Designed for sparse control sequences — e.g. feeding a video model like Wan 2.2 a 81-frame guide where only frames 41, 63, 78, 88, 98 and 121 carry real image data and the rest tell the model "no latent update here". The cut window lets you grab any sub-range of a longer sequence without needing an external ImageFromBatch slicer.

Insert modes (the inverse). Connect a packed sequence to keyframes_insert and the node flips around: instead of selecting sparse keyframes out of a dense source, it scatters an already-dense short sequence onto the keyframe positions from the frame list — placing frame i at the i-th listed number. The background depends on whether source_batch is connected:

  • source_batch not connected (offset / reconstruct mode): gaps are filled with the placeholder. Use it to rebuild a full-length sequence from frames you selected, exported, processed, and want back in their original spacing (step 2 of a select → process → reconstruct workflow).
  • source_batch connected (insert-over-source mode): the source cut window is the background and the listed positions are overwritten with the insert frames — composite more-than-one processed frames back into the source video at chosen indexes.

No inputs ⇒ clean batch. With no image source connected at all, the node emits a clean placeholder batch sized by the width/height widgets instead of raising an error.

Demo workflow. A runnable Easy Image Batch — Select and Rebuild demo ships in the Kforge Labs Workflows sidebar folder (source: docs/workflows/kforge-labs-workflows/). Load it to walk the select → process → rebuild loop and the layer / passthrough behaviour end to end.

Inputs

Required widgets

Input Type Default Description
total_frames INT 81 Length of the cut window (= length of the output). Range: 1..1024.
cut_start_frame INT 1 First VFX frame in the output. The output represents [cut_start_frame .. cut_start_frame + total_frames - 1]. Frames placed outside this window are dropped from the cut and listed in a single end-of-run summary log.
placeholder_color enum Black Fill colour for unoccupied frames in image_batch. Black = 0.0, Gray = 0.5, White = 1.0. Independent of alpha_batch.
invert_alpha BOOLEAN false (inpaint) Off = inpaint convention (selected = 0.0 black, empty = 1.0 white). On = compositing alpha (selected = 1.0, empty = 0.0).
source_frames multiline STRING "" Optional list of VFX frame numbers to pick from source_batch. Tokens may be separated by commas, newlines, spaces, tabs, or any mix ("1, 27\n41 63" → [1, 27, 41, 63]); inclusive ranges expand ("14-17" → 14, 15, 16, 17). Each number picks source_batch[N - 1] and places it at output index N - cut_start_frame. Bad tokens warn and skip. Frames not present in source_batch warn; frames outside the cut window are dropped silently and reported in the end-of-run summary.
image1_frame … image4_frame INT 5, 9, 13, 17 VFX timeline position (1-based) for the slot's keyframe. Used only when the matching imageN is connected — it's the output frame that image overwrites (the top layer). No longer a source_batch pick index.

Optional input slots

Input Type Description
keyframes_insert IMAGE Packed sequence for insert modes. When connected, the node switches from select to insert: frame i is placed at the i-th position in source_frames (ascending). Connected image1…image4 still composite on top of the inserts. The background is placeholder (no source_batch) or the source_batch cut window (insert-over-source).
source_batch IMAGE Pre-batched image stack, e.g. straight from Load Video. source_batch[0] is treated as VFX frame 1; source_batch[k] is VFX frame k + 1. In insert mode it becomes the composite background.
image1 … image4 IMAGE Per-slot keyframes. Each must be a single-frame IMAGE (batch size 1). Pre-batched stacks must come through source_batch instead.

Fallback widgets (last two)

Input Type Default Description
width INT 512 Fallback output width. Used only when no image input provides dimensions (a fully empty node producing a clean placeholder batch). Ignored whenever an image source is connected. Range: 1..8192.
height INT 512 Fallback output height. Same fallback-only semantics as width. Range: 1..8192.

These two are deliberately the last widgets on the node so they append to the end of the saved widget list — keeping older saved workflows (which predate them) correctly aligned.

With no image source connected at all, the node emits a clean placeholder batch sized by the width/height widgets (no error). When a source is connected, all connected inputs must share the same H × W × C. (In insert modes — keyframes_insert connected — that input drives the sequence placement; source_batch, if present, supplies the composite background; and connected image1…image4 overwrite on top.)

Outputs

Output Type Description
image_batch IMAGE The cut window as a total_frames-long batch starting at VFX frame cut_start_frame. Selected positions carry the picked images; everything else is the chosen placeholder colour.
alpha_batch MASK Same length as image_batch. Default (inpaint): content = 0.0, empty/to-fill = 1.0. Toggle invert_alpha to flip. In a short-source passthrough the covered frames are 0.0 (kept) and the uncovered tail 1.0 (to inpaint).
selected_image_batch IMAGE Only the keyframes that landed inside the cut, packed back-to-back ascending; empty if nothing was placed. In passthrough this is instead the uncovered gap frames — the inverse: the frames to generate.
selected_frames STRING Comma-separated VFX frame numbers of the placed-inside-cut keyframes, sorted ascending and deduped (e.g. "41, 63, 78, 88, 98, 121"). Same format as the source_frames input — paste it (or wire it) into another easy_ImageBatch to reuse the same selection.

Frame-numbering model

Two indices are computed per VFX frame number N:

source pick index   = N - 1                  # source_batch[0] is VFX frame 1
output (cut) index  = N - cut_start_frame    # cut window

These are independent. A frame is in the cut iff its output index is in [0, total_frames). The two are equal only when cut_start_frame = 1 (the default), so the previously-shipped behaviour is preserved by default.

There is no exposed knob to renumber source_batch (i.e. say "my video starts at VFX frame 100"). If your stack represents a different VFX range, renumber externally before connecting.

Select mode — the three layers

When keyframes_insert is not connected, the output is built in three layers, lowest first. A higher layer overwrites a lower one at the same output index.

Layer 1 — Background

  • source_batch connected and source_frames empty → passthrough. The source cut window is laid down as the output (output index i ← source_batch[cut_start_frame + i - 1]). Those covered frames are kept content (alpha 0.0). If the source is shorter than the output, the uncovered tail stays placeholder and becomes the selection — alpha 1.0, plus the selected_image_batch / selected_frames outputs — i.e. an extend-a-clip inpaint mask: keep the first N real frames, generate the rest. (This is the inverse of select mode, where the selection is the picks/slots.) A full-coverage passthrough has no gap → all kept, empty selection.
  • Otherwise → placeholder. A non-empty source_frames list means select mode: the named frames are pulled onto a neutral placeholder_color background, and only those picks/slots are placed. With no source_batch at all the background is placeholder too.

In select mode the placeholder background is never counted as placed — alpha_batch and the selected_* outputs describe only the picks and slots.

Layer 2 — source_frames picks

Each VFX number in source_frames picks source_batch[N - 1] and places it at output index N - cut_start_frame (if inside the cut), on top of the background. Unlimited keyframes — removes the old 4-slot ceiling. Bad tokens warn and skip; frames absent from source_batch warn; outside-cut frames are dropped and summarised.

Layer 3 — Manual slots (image1-image4), top priority

Connect imageN and set imageN_frame to its VFX position; the image overwrites that output frame on top of everything — background and picks alike. On a collision the higher-numbered slot wins (image4 > image1), and a slot beats a Layer-2 pick at the same position.

An unconnected slot does nothing. imageN_frame is only a placement target for a wired imageN — it is no longer a source_batch pick index. (The old behaviour, where an unwired slot pulled source_batch[imageN_frame - 1] whenever the list was empty, is removed: it surfaced seemingly-random source frames. To pick frames from a video, use the source_frames list.)

The set of placed positions is deduped automatically (selected_* reflect the final, deduped state).

Mode 4 — Insert (keyframes_insert)

The inverse of Modes 1–3. Connect a packed IMAGE sequence (e.g. frames you previously pulled via selected_image_batch, processed, and re-loaded) to keyframes_insert. The node switches to insert mode and reads source_frames as destination positions: the i-th frame of keyframes_insert is placed at output index position[i] - cut_start_frame. Positions are taken ascending and de-duplicated, so it round-trips cleanly with the ascending selected_image_batch + selected_frames a select-mode node produces.

The background depends on source_batch:

  • Offset / reconstruct (no source_batch). Gaps are filled with placeholder_color, at the original sequence length.

  • Insert-over-source (source_batch connected). The source_batch cut window is laid down first (output index i ← `source_batch[cut_start_frame

    • i - 1]`; cut frames beyond the source fall back to the placeholder and are reported), then the listed positions are overwritten with the insert frames. This composites several processed frames back into the source video at chosen indexes.
  • Wire-driven switch. Insert mode is active whenever keyframes_insert is connected. Connected image1…image4 still apply — they composite on top of the inserts (Layer 3): a slot beats an insert at the same position, and the higher-numbered slot wins a slot-vs-slot collision.

  • Alpha marks the placed frames. Regardless of background, alpha_batch is 0.0 at the inserted positions and any slot overwrites, and selected_image_batch / selected_frames describe just those.

  • Counts needn't match. Extra incoming frames beyond the list are reported as unused; positions beyond the available frames are reported as missing. Outside-cut positions (inserts or slots) are dropped, same as select mode.

  • Empty list ⇒ nothing inserted — a clean placeholder batch (no source_batch) or a clean source_batch cut-window passthrough — with a console note. (Connected slots still composite on top.)

Conventions

  • 1-based VFX numbering. imageN_frame, source_frames, cut_start_frame are all 1-based. source_batch[0] is VFX frame 1. Frame 0 is invalid and out-of-source-range; frame 1 is the first real frame.
  • Cut-window drops. Frames placed outside the cut window aren't errors — they're intentionally absent. The console prints one summary line per run noting the cut range, the placed count, and any outside-cut drops.
  • Alpha vs mask vocabulary. alpha_batch is a MASK-typed output but follows whichever convention invert_alpha selects. The default (inpaint) matches the previous mask_batch semantics for backward compatibility (selected = black, empty = white).

Example A — cut_start_frame = 1 (no cut, full timeline)

Field Value
source_batch Load Video (121 frames)
source_frames "1, 27, 41, 63, 78, 88, 98, 121"
cut_start_frame 1
total_frames 121
placeholder_color Gray
invert_alpha false (inpaint)
Output What you get
image_batch 121 frames; the 8 picks land at indices 0 / 26 / 40 / 62 / 77 / 87 / 97 / 120. Rest is gray.
alpha_batch 121-frame mask; those 8 indices are 0.0, the other 113 are 1.0.
selected_image_batch 8 frames, packed back-to-back in timeline order.
selected_frames "1, 27, 41, 63, 78, 88, 98, 121"

Console summary: cut window: frames 1..121 (121 frames). 8 placed.

Example B — cut_start_frame = 41, total_frames = 81 (cut)

Same inputs as Example A, but with the cut window changed to start at VFX frame 41 for 81 frames.

Output What you get
image_batch 81 frames representing VFX frames 41..121. Frames 41 / 63 / 78 / 88 / 98 / 121 land at output indices 0 / 22 / 37 / 47 / 57 / 80. Rest is gray.
alpha_batch 81-frame mask, 6 indices selected.
selected_image_batch 6 frames, packed back-to-back.
selected_frames "41, 63, 78, 88, 98, 121"

Console summary: cut window: frames 41..121 (81 frames). 6 placed. 2 outside cut: 1, 27.

This replaces the workflow pattern of feeding the Easy Image Batch output through an external ImageFromBatch (batch_index=40, length=81) node — the cut is built in.

Example C — Offset mode (reconstruct a selection)

Step 1 produced 5 enhanced frames (a 5-frame sequence) that originally came from VFX frames 1, 10, 19, 30, 40 of a 41-frame shot. Step 2 rebuilds the original length:

Field Value
keyframes_insert the 5 processed frames (e.g. Load EXR / image batch)
source_frames "1, 10, 19, 30, 40"
cut_start_frame 1
total_frames 41
placeholder_color Black
Output What you get
image_batch 41 frames; the 5 processed frames land at indices 0 / 9 / 18 / 29 / 39. Rest is black.
alpha_batch 41-frame mask; those 5 indices are 0.0, the other 36 are 1.0 — ready as an inpaint mask for the gaps.
selected_image_batch the 5 frames, packed back-to-back.
selected_frames "1, 10, 19, 30, 40"

Console summary: offset mode: cut window frames 1..41 (41 frames). 5 placed.

Example D — Insert-over-source (composite into a video)

Same 5 processed frames as Example C, but now you want them dropped back into the original 41-frame video at their positions rather than onto a black background — the untouched frames keep the source footage.

Field Value
keyframes_insert the 5 processed frames
source_batch the original 41-frame Load Video stack
source_frames "1, 10, 19, 30, 40"
cut_start_frame 1
total_frames 41
Output What you get
image_batch 41 frames; the 5 processed frames overwrite indices 0 / 9 / 18 / 29 / 39, every other frame is the original source frame.
alpha_batch 41-frame mask; only those 5 indices are 0.0 (the inserts), the rest 1.0.
selected_image_batch the 5 inserted frames, packed back-to-back.
selected_frames "1, 10, 19, 30, 40"

Console summary: insert-over-source mode: cut window frames 1..41 (41 frames). 5 placed.

Tips

  • Round-tripping selections. Wire selected_frames of one node into source_frames of another to chain or fan out the same picks across workflows.
  • A few explicit keyframes. Plug single-frame images into image1… image4 and set each imageN_frame. They overwrite those positions on top of whatever else the node produces — no source_batch needed.
  • Picking many frames from a video. Connect source_batch and list the VFX numbers in source_frames (no 4-keyframe ceiling). Reach for image1… image4 only to override individual positions on top of the picks.
  • Cutting an output. Set cut_start_frame to the first VFX frame you want and total_frames to the length of the cut. No external slicer node needed.
  • Inverted alpha for compositing. Toggle invert_alpha on when you want to use the alpha output as a true compositing alpha (selected = white = visible).