From 88dbfedf8629c503eec15dda4fa4958af6a8f392 Mon Sep 17 00:00:00 2001 From: malkuthro <166633396+malkuthro@users.noreply.github.com> Date: Fri, 19 Jun 2026 00:51:16 +0200 Subject: [PATCH] =?UTF-8?q?feat(nodes):=20Easy=20Image=20Batch=20keyframes?= =?UTF-8?q?=5Finsert=20+=20insert-over-source=20+=E2=80=A6=20(#246)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 --------- Co-authored-by: AI Machine Co-authored-by: Claude Opus 4.8 --- CHANGELOG.md | 84 ++- docs/designs/easy-image-batch-behavior.md | 141 ++++ .../img/Easy_Image_Batch_v0.1.5.png | Bin 178517 -> 0 bytes .../nodes/koolook_image/easy_image_batch.md | 215 ++++-- k_easy_image_batch.py | 687 ++++++++++++------ pyproject.toml | 6 +- tests/nodes/test_easy_image_batch.py | 488 ++++++++++++- 7 files changed, 1316 insertions(+), 305 deletions(-) create mode 100644 docs/designs/easy-image-batch-behavior.md delete mode 100644 docs/user_guide/img/Easy_Image_Batch_v0.1.5.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 86aacde..f974be8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,27 +7,76 @@ The format is inspired by Keep a Changelog and SemVer. ## [Unreleased] ### Added +- **Easy Image Batch — review hardening.** `keyframe_batch` is kept as a + **deprecated alias** of `keyframes_insert` (pre-rename workflows keep loading; + an explicit `keyframes_insert` wins if both are wired; removal deferred to a + release boundary). A non-empty `source_frames` with **no image source + connected** now logs a warning instead of silently returning a clean batch. + Frame-list **ranges are capped** — a single `N-M` token can't expand past + 8192 frames — guarding against typos like `1-99999999`. +- **Easy Image Batch — passthrough inpaint mask + frame-range syntax.** Two + refinements: (1) when `source_batch` is shorter than the output (empty list), + the covered source frames are kept as real content (`alpha` 0.0) while the + uncovered tail stays placeholder and becomes the `selected_image_batch` / + `selected_frames` output — an extend-a-clip inpaint mask (keep the first N, + generate the rest). A full-coverage passthrough has no gap, so `alpha` is + all-kept and the selection is empty. This makes `selected_*` the *gap* in + passthrough — the inverse of select mode, where it is the placed picks/slots. + (2) The `source_frames` field now 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 + modes (shared torch-free `parse_frame_tokens`, unit-tested in CI). +- **Easy Image Batch — `image1-4` super-overwrite + source passthrough.** The + four manual slots are now the top compositing layer in **every** mode: a + connected `imageN` overwrites output frame `imageN_frame` on top of the + sequence/inserts and the background, in select *and* insert modes (they were + previously ignored whenever `keyframes_insert` was connected). On a + collision the higher-numbered slot wins (`image4` > `image1`), and a slot + beats a sequence pick / insert at the same position. Two model fixes ride + along: an **unconnected** slot now contributes nothing — `imageN_frame` is + only a placement target for a wired image, never a `source_batch` pick (the + old "pull `source_batch[imageN_frame-1]`" fallback that surfaced + seemingly-random frames is removed); and **`source_batch` + an empty frame + list (no insert) now passes the source cut window straight through** instead + of emitting a placeholder batch, matching the empty-list insert passthrough + so "source in → source out" is consistent. Slot placement is a pure, + torch-free planner (`plan_slot_overwrites`) unit-tested in CI alongside the + existing insert planners. +- **Easy Image Batch — insert modes (`keyframes_insert`).** A new optional + `keyframes_insert` input (renamed from the unreleased `keyframe_batch`) + adds the inverse of the node's select behaviour. When connected, the node + switches from *selecting* sparse keyframes to *scattering* an + already-packed short sequence onto the `source_frames` positions — placing + the *i*-th incoming frame at the *i*-th listed number (ascending). The + **background depends on `source_batch`**: + - **not connected → offset / reconstruct mode** — gaps filled with the + placeholder colour, at the original sequence length. Closes the + select → process → reconstruct loop: feed a processed copy of a previous + `selected_image_batch` plus the same `selected_frames` list back in to + rebuild the original spacing. + - **connected → insert-over-source mode** — the `source_batch` cut window + is laid down as the background and the listed positions are overwritten + with the insert frames (composite N>1 processed frames back into the + source video at chosen indexes). Cut frames beyond the source fall back + to the placeholder, reported in the summary. + + Connected `image1…image4` still composite on top of the inserts in this mode + (see the super-overwrite note above); `alpha_batch` marks the inserted + positions and any slot overwrites. An empty + `source_frames` list inserts nothing — a clean placeholder batch (no + `source_batch`) or a clean `source_batch` passthrough. Placement and the + source-base mapping are pure, torch-free planners (`plan_offset_placements`, + `plan_source_base_fill`) so both are unit-tested in CI. +- **Easy Image Batch — clean batch instead of erroring + `width`/`height` + fallback.** With no image source connected at all, the node now emits a + clean placeholder batch (sized by the new `width`/`height` widgets, default + 512×512) rather than raising. `width`/`height` are used **only** as the + fallback when no connected image provides dimensions. - **Bundled Kforge Labs demo workflows.** Fresh sidebar installs now seed a **Kforge Labs Workflows** folder with `Easy Image Batch - Select and Rebuild` and `LOOP Demo`, and the source workflow JSONs are tracked under `docs/workflows/kforge-labs-workflows/` for release review and future demo additions. The loop demo ships with auto-queue disabled until the user sets a writable output folder. -- **Easy Image Batch — offset mode (`keyframe_batch`).** A new optional - `keyframe_batch` input adds the inverse of the node's select behaviour. - When connected, the node switches from *selecting* sparse keyframes out of - a dense `source_batch` to *scattering* an already-packed short sequence - back onto the `source_frames` positions — placing the *i*-th incoming - frame at the *i*-th listed number (ascending), placeholder colour in - between, at the original sequence length. This closes the - select → process → reconstruct loop: feed a processed copy of a previous - `selected_image_batch` plus the same `selected_frames` list back in to - rebuild the original spacing. The switch is wire-driven (`source_batch` - and `image1…image4` are ignored while `keyframe_batch` is connected, with - a console note); frame-count vs list-length mismatches and outside-cut - positions are reported in the end-of-run summary. Placement logic is a - pure, torch-free planner (`plan_offset_placements`) so it is unit-tested - in CI. - **Publish setup success feedback (#227).** Publishing a saved sidebar workflow no longer closes the dialog silently — a confirmation card now shows where the setup was saved (the registry `setups.json` path) and @@ -170,7 +219,10 @@ The format is inspired by Keep a Changelog and SemVer. scripts upgrade `setuptools` alongside `pip` before audit. The test lock was regenerated to move `aiohttp` from 3.14.0 to the fixed 3.14.1 line, and the relock path now avoids editable Git metadata inspection so Windows - cross-drive worktrees can regenerate the lock cleanly. + cross-drive worktrees can regenerate the lock cleanly. The `[test]` extra's + `aiohttp` floor was also raised from `>=3.9` to `>=3.14.1` so the declared + dependency range can no longer resolve a vulnerable version, clearing the + eight range-based Dependabot alerts the lock bump alone left open. - **`Koolook_LoopStatus` auto-queue failed on non-default ports.** The node's `server_url` defaulted to ComfyUI's standard port (`http://127.0.0.1:8188`), so installs launched with `--port` (or diff --git a/docs/designs/easy-image-batch-behavior.md b/docs/designs/easy-image-batch-behavior.md new file mode 100644 index 0000000..56700b9 --- /dev/null +++ b/docs/designs/easy-image-batch-behavior.md @@ -0,0 +1,141 @@ + +# Easy Image Batch — behavior matrix + +Status: **Canonical** (approved 2026-06-18). Implemented in +`k_easy_image_batch.py` and mirrored in +[`docs/user_guide/nodes/koolook_image/easy_image_batch.md`](../user_guide/nodes/koolook_image/easy_image_batch.md). + +Runnable demo: the **Easy Image Batch — Select and Rebuild** workflow bundled in +the Kforge Labs Workflows sidebar folder +([`docs/workflows/kforge-labs-workflows/`](../workflows/kforge-labs-workflows/), +from [#248](https://github.com/malkuthro/ComfyUI-Koolook/pull/248)). It +supersedes the generated demo removed here. + +This captures the agreed changes: + +1. `keyframe_batch` → **`keyframes_insert`** (rename; the old name is kept as a + deprecated alias input so pre-rename workflows keep loading). +2. **No image source connected → clean placeholder batch** sized by new + `width`/`height` widgets (no error). +3. **Insert-over-source mode** — `keyframes_insert` + `source_batch` composite + the inserts over the source cut window. +4. **`image1`–`image4` are "super-overwrites"** — when connected they replace + whatever is at their index in **every** mode (top priority). When **not** + connected they do **nothing** (the old "pull from `source_batch` via + `imageN_frame`" fallback — Mode 2 — is removed). + +--- + +## The layered model (single source of truth) + +The output is built in three layers, lowest first. A later layer overwrites an +earlier one at the same output index. + +``` +output index i ↔ VFX frame v = cut_start_frame + i (window: i in [0, total_frames)) + +┌─ Layer 3 — SUPER-OVERWRITE (image1–image4) ────────────────┐ highest priority +│ Each CONNECTED imageN → placed at index (imageN_frame │ +│ − cut_start_frame). Wins over everything. Unconnected │ +│ slots contribute NOTHING. (imageN_frame matters only │ +│ when imageN is connected.) │ +├─ Layer 2 — SEQUENCE ───────────────────────────────────────┤ +│ • keyframes_insert connected (INSERT): i-th incoming frame │ +│ → i-th source_frames position (ascending, deduped). │ +│ • else (SELECT): each source_frames number N picks │ +│ source_batch[N−1] → index (N − cut_start_frame). │ +├─ Layer 1 — BACKGROUND ─────────────────────────────────────┤ lowest priority +│ • insert-over-source (keyframes_insert AND source_batch): │ +│ source_batch cut window (index i ← source_batch[v−1]; │ +│ frames beyond the source fall back to placeholder). │ +│ • otherwise: placeholder_color everywhere. │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Background rule.** Layer 1 is the source cut window whenever `source_batch` +is connected **and** either an insert is active (`keyframes_insert`) **or** the +`source_frames` list is empty; otherwise it is `placeholder_color`. + +**What counts as "placed"** (drives `alpha_batch`, `selected_image_batch`, +`selected_frames`): normally the Layer 2 placements **and** Layer 3 overwrites; +the Layer 1 `source_batch` backdrop is *not* placed (just the background). So +`alpha_batch` is `0.0` at sequence inserts/picks and connected-slot overwrites, +`1.0` everywhere else (flipped by `invert_alpha`). + +**Select passthrough is the exception (row 5).** When the source passes through +with an empty list, the covered source frames *are* kept content (`alpha` +`0.0`), and the **uncovered tail becomes the selection** (`alpha` `1.0`, +emitted in `selected_*`) — an extend-a-clip inpaint mask. The selection here is +the *gap*, the inverse of every other row. Insert-mode passthrough (row 9) +keeps the plain backdrop semantics (nothing placed). + +**Resolution / dtype** come from the first connected image (insert: from +`keyframes_insert`; select: `image1` → `source_batch` → `image2…4`). With **no** +image connected at all, the output is sized by the `width`/`height` widgets +(channels = 3), on CPU/float — a clean placeholder batch. + +**Tie-break:** if two connected slots target the same index, the +higher-numbered slot wins (`image4` > … > `image1`). Sequence + slot at the +same index → the slot wins (Layer 3 > Layer 2). + +--- + +## Mode matrix + +Legend — `keyframes_insert` = **KI**, `source_batch` = **SB**, `source_frames` +list = **list**, connected `image1–4` = **slots**. + +| # | KI | SB | list | slots | Background | Placed (alpha 0.0) | `selected_*` | Mode label | +|---|----|----|------|-------|-----------|--------------------|--------------|-----------| +| 1 | ✗ | ✗ | — | none | placeholder (`width`×`height`) | nothing | empty | **clean batch** | +| 2 | ✗ | ✓ | has | none | placeholder | source picks at listed positions | the picks | select | +| 3 | ✗ | ✗ | — | some | placeholder | connected slots | the slots | select (slots) | +| 4 | ✗ | ✓ | has | some | placeholder | source picks **+** slots (slots win on overlap) | picks + slots | select | +| 5 | ✗ | ✓ | empty | none | **source cut window** | covered source (kept) | the uncovered **gap** | source passthrough / extend | +| 6 | ✓ | ✗ | has | none | placeholder | scattered inserts | the inserts | offset / reconstruct | +| 7 | ✓ | ✗ | empty | none | placeholder | nothing | empty | **clean** (nothing inserted) | +| 8 | ✓ | ✓ | has | none | **source cut window** | scattered inserts (overwrite source) | the inserts | insert-over-source | +| 9 | ✓ | ✓ | empty | none | **source cut window** | nothing | empty | source passthrough | +| 10 | ✓ | ✓ | has | some | **source cut window** | inserts **+** slots (slots win) | inserts + slots | insert-over-source | +| 11 | any | any | any | **set but NOT connected** | (per above) | **slots contribute nothing** | (per above) | (per above) | + +Row **11** was the bug fixed here: a slot with a non-default `imageN_frame` +that is **not wired** has zero effect on the output. The old behaviour (an +unconnected slot pulling from `source_batch`) is gone. + +--- + +## Edge cases & logging + +- **List ranges.** `N-M` (e.g. `14-17`) expands inclusively; a descending + `M-N`, an over-wide range (more than 8192 frames), or any non-integer token + warns and is skipped. Applies in both modes. +- **List set but no image source** (select): the list is ignored and a clean + placeholder batch is returned, with a console warning (not a silent drop). +- **List token not in `source_batch`** (select): warn + skip that number. +- **List/insert position outside the cut window**: dropped, summarized once. +- **More inserts than list positions** / **more positions than inserts**: + reported as "extra insert frames" / "positions with no insert frame". +- **`source_batch` shorter than the cut window** (insert-over-source): the + uncovered tail frames stay placeholder; count reported. +- **Empty list** in an insert mode: clean placeholder (no SB) or clean source + passthrough (with SB) — console note "frame list is empty". +- **Mismatched H/W/C** among connected inputs: hard `ValueError` (unchanged). +- Every run still prints one summary line: mode, cut window, placed count, and + any of the notes above. + +--- + +## Decisions (resolved) + +1. **Tie-break direction** — higher-numbered slot wins (`image4` > `image1`); + a slot beats a sequence pick / insert at the same index (Layer 3 > Layer 2). +2. **Empty-list passthrough (select)** — `source_batch` + an empty list passes + the source cut window through (row 5). Covered source frames are kept content + (`alpha` 0.0); a source shorter than the output leaves a placeholder tail + that becomes the selection (`selected_*`) — an extend/inpaint mask. The + selection here is the *gap*, the inverse of other rows. +3. **Mode-2 fallback removed** — an unconnected slot never pulls from + `source_batch`; `imageN_frame` is only a placement target for a wired image. +4. **Range syntax** — the frame list accepts inclusive ranges (`14-17` → + 14,15,16,17) in both select and insert; descending/non-integer tokens warn. diff --git a/docs/user_guide/img/Easy_Image_Batch_v0.1.5.png b/docs/user_guide/img/Easy_Image_Batch_v0.1.5.png deleted file mode 100644 index 873ec801cddd686e08b10f0188a0c87a43c184f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178517 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 diff --git a/docs/user_guide/nodes/koolook_image/easy_image_batch.md b/docs/user_guide/nodes/koolook_image/easy_image_batch.md index 6040f4e..c7288b3 100644 --- a/docs/user_guide/nodes/koolook_image/easy_image_batch.md +++ b/docs/user_guide/nodes/koolook_image/easy_image_batch.md @@ -23,14 +23,31 @@ 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. -**Offset mode (the inverse).** Connect a packed sequence to -`keyframe_batch` and the node flips around: instead of *selecting* sparse +**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 back onto the keyframe positions from the frame list — placing -frame *i* at the *i*-th listed number, placeholder in between. 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). +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/`](../../../workflows/kforge-labs-workflows/)). +> Load it to walk the select → process → rebuild loop and the layer / +> passthrough behaviour end to end. ## Inputs @@ -42,29 +59,42 @@ select → process → reconstruct workflow). | `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]`). Each token 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 frame number (1-based) for the slot's keyframe. In Mode 2 also used as the `source_batch` pick index. | +| `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 | |---|---|---| -| `keyframe_batch` | `IMAGE` | Packed sequence for **offset mode**. When connected, the node switches from *select* to *offset*: frame *i* is placed at the *i*-th position in `source_frames` (ascending), placeholder elsewhere. `source_batch` and `image1…image4` are ignored while this is connected. | -| `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`. | +| `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. | -At least one of `image1` or `source_batch` must be connected; otherwise -the node raises a clear `ValueError`. All connected inputs must share -the same `H × W × C`. (In **offset mode** — `keyframe_batch` connected — -that input alone is sufficient and the others are ignored.) +### 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 convention: selected = `0.0`, empty = `1.0`. Toggle `invert_alpha` to flip. | -| `selected_image_batch` | `IMAGE` | **Only** the keyframes that actually landed inside the cut window, packed back-to-back in ascending order. Length = number of placed keyframes; empty (length 0) if nothing was placed. | +| `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 @@ -85,68 +115,91 @@ 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. -## Modes +## Select mode — the three layers -The node supports three input modes that can be freely mixed. +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. -### Mode 1 — Per-slot images (up to 4 keyframes) +### Layer 1 — Background -Connect `imageN` and set `imageN_frame` to its VFX timeline position. -The image lands at output index `imageN_frame - cut_start_frame` if -that's inside the cut. No `source_batch` needed. Up to 4 keyframes -total. +- **`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. -### Mode 2 — `source_batch` + the 4 slots (up to 4 keyframes from a video) +In select mode the placeholder background is never counted as *placed* — +`alpha_batch` and the `selected_*` outputs describe only the picks and slots. -Connect a pre-batched stack to `source_batch` (e.g. from `Load Video`) -and leave `imageN` unconnected. Each unconnected slot pulls -`source_batch[imageN_frame - 1]` and places it at output index -`imageN_frame - cut_start_frame` — collapsing the typical "Load Video → -Get Images From Batch In Range × N → Easy Image Batch" helper chain -into a single node. +### Layer 2 — `source_frames` picks -This mode is active when `source_frames` is empty. +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. -### Mode 3 — `source_batch` + `source_frames` list (unlimited keyframes) +### Layer 3 — Manual slots (`image1`-`image4`), top priority -Connect `source_batch` and fill `source_frames` with as many VFX frame -numbers as you need. Removes the 4-keyframe ceiling. +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. -**Important interaction rule:** when `source_frames` is non-empty, the -4 manual slots contribute ONLY where `imageN` is explicitly connected — -the `imageN_frame` defaults do **not** pick from `source_batch` in this -mode. The list fully controls the selection, with explicit `imageN` -connections used solely to override individual positions. +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.) -### Priority order +The set of placed positions is deduped automatically (`selected_*` reflect the +final, deduped state). -When more than one mode contributes: - -1. `source_frames` list runs first. -2. Manual slots (`image1` … `image4` with their `imageN_frame`) run - after, so explicit `imageN` inputs always **override** list entries - at the same cut-output position. -3. The set of placed keyframes is deduped automatically (`selected_*` - outputs reflect the final, deduped state). - -### Mode 4 — Offset / reconstruct (`keyframe_batch`) +### 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 `keyframe_batch`. The node switches to **offset mode** and +re-loaded) to `keyframes_insert`. The node switches to **insert mode** and reads `source_frames` as *destination positions*: the *i*-th frame of -`keyframe_batch` is placed at output index `position[i] - cut_start_frame`, -everything else filled by `placeholder_color`. 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. +`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. -- **Wire-driven switch.** Offset mode is active whenever `keyframe_batch` - is connected; `source_batch` and `image1…image4` are ignored (a one-line - console note fires if they're connected, so nothing is silently dropped). +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 are dropped, same as select mode. -- **Empty list ⇒ nothing placed** (all placeholder), with a console note. + 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 @@ -195,7 +248,7 @@ VFX frame 41 for 81 frames. | `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.` +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)` @@ -209,7 +262,7 @@ the original length: | Field | Value | |---|---| -| `keyframe_batch` | the 5 processed frames (e.g. `Load EXR` / image batch) | +| `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` | @@ -224,18 +277,40 @@ the original length: 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. -- **Picking from a video without writing the list manually.** If you - already have helper nodes producing single frames, plug them into - `image1`…`image4` (Mode 1) and leave `source_frames` empty. That's - the legacy 4-keyframe pattern, kept fully working. -- **Going past 4 keyframes.** Switch to Mode 3: connect `source_batch`, - fill `source_frames`, leave `imageN` unconnected. Use `image1`… - `image4` only for *overrides* (e.g. patching one frame manually). +- **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. diff --git a/k_easy_image_batch.py b/k_easy_image_batch.py index 5898980..cc705b8 100644 --- a/k_easy_image_batch.py +++ b/k_easy_image_batch.py @@ -14,6 +14,11 @@ _PLACEHOLDER_FILL = { "White": 1.0, } +# Upper bound on how many frames a single ``N-M`` range token may expand to, so +# a typo like ``1-99999999`` can't balloon the frame list into millions of +# entries. Comfortably above the node's max cut window (total_frames <= 1024). +_MAX_RANGE_SPAN = 8192 + @dataclass(frozen=True) class OffsetPlan: @@ -21,7 +26,7 @@ class OffsetPlan: - ``placements`` — ``(source_index, output_index, vfx_frame)`` for each incoming frame that lands inside the cut window, ascending by - position. ``source_index`` is the frame's ordinal in ``keyframe_batch``. + position. ``source_index`` is the frame's ordinal in ``keyframes_insert``. - ``outside_cut`` — VFX frame numbers paired to a frame but whose output index fell outside ``[0, total_frames)`` (dropped, like select mode). @@ -71,6 +76,103 @@ def plan_offset_placements( return OffsetPlan(placements, outside_cut, unused_source, missing_positions) +def plan_source_base_fill( + total_frames: int, cut_start_frame: int, base_len: int +) -> tuple[list[tuple[int, int]], int]: + """Map each output index in the cut window onto a ``source_batch`` index. + + Used by insert-over-source mode to lay down the source video as the + background before the inserts overwrite it. Output index ``i`` represents + VFX frame ``cut_start_frame + i``, and ``source_batch[0]`` is VFX frame 1, + so ``source_index = i + cut_start_frame - 1``. + + Returns ``(placements, fallback_count)`` where ``placements`` is the list + of ``(output_index, source_index)`` the source actually covers and + ``fallback_count`` is how many cut frames fell beyond the source (and stay + placeholder). Pure Python — no torch — so it is unit-testable in CI. + """ + placements: list[tuple[int, int]] = [] + fallback = 0 + for output_index in range(total_frames): + source_index = output_index + cut_start_frame - 1 + if 0 <= source_index < base_len: + placements.append((output_index, source_index)) + else: + fallback += 1 + return placements, fallback + + +def plan_slot_overwrites( + slot_frames: list[tuple[int, int]], + total_frames: int, + cut_start_frame: int, +) -> tuple[list[tuple[int, int, int]], list[int]]: + """Map connected manual slots (image1-4) onto output indices — Layer 3. + + ``slot_frames`` is ``(slot_index, vfx_frame)`` for each CONNECTED slot, in + ascending slot order (image1 = 0 … image4 = 3). Manual slots are the top + compositing layer: they overwrite whatever the sequence or background put + at the same output index. On a collision between two slots the + higher-numbered slot wins (image4 > image1). A slot lands iff its output + index ``vfx - cut_start_frame`` is inside ``[0, total_frames)``; otherwise + the frame is reported in ``outside_cut``. + + Returns ``(placements, outside_cut)`` where ``placements`` is + ``(slot_index, output_index, vfx)`` ascending by output index, one winner + per index. Pure Python — no torch — so it is unit-testable in CI. + """ + winner: dict[int, tuple[int, int]] = {} # output_index -> (slot_index, vfx) + outside_cut: list[int] = [] + for slot_index, vfx in slot_frames: + output_index = vfx - cut_start_frame + if 0 <= output_index < total_frames: + # Higher slot wins on a collision (image4 > image1), independent of + # the order slots are supplied in. + prev = winner.get(output_index) + if prev is None or slot_index > prev[0]: + winner[output_index] = (slot_index, vfx) + else: + outside_cut.append(vfx) + placements = [ + (slot_index, output_index, vfx) + for output_index, (slot_index, vfx) in sorted(winner.items()) + ] + return placements, outside_cut + + +def parse_frame_tokens(source_frames: str) -> tuple[list[int], list[str]]: + """Parse a frame-list string into 1-based VFX frame numbers. + + Tokens are separated by commas, spaces, tabs, or newlines. A token is + either a single integer (``7``) or an inclusive ascending range + (``14-17`` → 14, 15, 16, 17). Returns ``(values, bad_tokens)`` where + ``bad_tokens`` are the tokens that could not be parsed (non-integer, a + descending range like ``5-1``, or a range wider than ``_MAX_RANGE_SPAN``) + so the caller can warn. Pure Python — no torch — so it is unit-testable in + CI. Shared by select picks and insert positions. + """ + values: list[int] = [] + bad_tokens: list[str] = [] + for tok in re.split(r"[,\s]+", (source_frames or "").strip()): + if not tok: + continue + span = re.fullmatch(r"(\d+)-(\d+)", tok) + if span: + lo, hi = int(span.group(1)), int(span.group(2)) + # Reject descending or absurdly large ranges (a typo like + # "1-99999999" would otherwise expand into millions of entries). + if lo <= hi <= lo + _MAX_RANGE_SPAN - 1: + values.extend(range(lo, hi + 1)) + else: + bad_tokens.append(tok) + continue + try: + values.append(int(tok)) + except ValueError: + bad_tokens.append(tok) + return values, bad_tokens + + class easy_ImageBatch: """ Place up to N keyframes on a 1-based VFX timeline, then output a @@ -116,45 +218,54 @@ class easy_ImageBatch: `cut_start_frame = 1` means the cut starts at VFX frame 1, so the full timeline shows up — equivalent to "no cut". - Three input modes, freely mixable - --------------------------------- - 1. Per-slot images (up to 4): connect `imageN` and set `imageN_frame` - to its VFX timeline position. The image lands at output index - `imageN_frame - cut_start_frame` if that's inside the cut. - 2. `source_batch` (optional pre-batched IMAGE, e.g. straight from - Load Video) + the 4 slots: when a slot's `imageN` is *not* - connected, the node pulls VFX frame `imageN_frame` from - `source_batch` (i.e. `source_batch[imageN_frame - 1]`) and - places it at output index `imageN_frame - cut_start_frame`. - Collapses the "Load Video → Get Images From Batch In Range × N" - helper chain into a single node. - 3. `source_batch` + `source_frames` (optional comma/newline/whitespace - separated string like `"1, 27, 41, 63, 85, 120"`): for keyframe - counts beyond 4. Each number picks - `source_batch[N - 1]` and places it at output index - `N - cut_start_frame` (if inside the cut). Bad tokens warn and - are skipped; frames not present in `source_batch` warn; frames - outside the cut window are dropped silently and summarised at - the end. + Select mode — the layers (lowest first; a later layer overwrites an + earlier one at the same output index) + ----------------------------------------------------------------------- + 1. Background: with `source_batch` connected and an EMPTY `source_frames` + list, the source cut window passes straight through (source in → + source out) as kept content (`alpha` 0.0). Any tail beyond the source + stays placeholder, and that uncovered gap becomes the `selected_*` + output — the frames to inpaint/generate (the inverse of the usual + "selected = picks/slots"). Otherwise the background is + `placeholder_color` and is not counted as "placed". + 2. `source_frames` (optional comma/newline/whitespace string like + `"1, 27, 41, 63"`, with inclusive ranges `"14-17"` → 14,15,16,17): each + number picks `source_batch[N - 1]` and places it at output index + `N - cut_start_frame` (if inside the cut), onto the placeholder. Bad + tokens warn and are skipped; frames not present in `source_batch` warn; + frames outside the cut window are dropped and summarised at the end. + 3. Manual slots (`image1`-`image4`): connect `imageN` and set + `imageN_frame` to its VFX position; the image overwrites that output + frame on TOP of everything. A higher-numbered slot wins a slot-vs-slot + collision (`image4` > `image1`). An UNconnected slot does nothing — + `imageN_frame` is only a placement target for a wired `imageN`, never a + `source_batch` pick. - Important: when `source_frames` is non-empty, the 4 manual slots - contribute ONLY where `imageN` is explicitly connected — the - `imageN_frame` defaults do NOT pick from `source_batch` in this - mode. The list fully controls the selection, with explicit - `imageN` connections used solely to override individual positions. + Output dedup is automatic (set-based). All inputs must share the same + H/W/C. When no image source is connected at all, the node emits a clean + placeholder batch sized by the `width`/`height` widgets instead of + erroring. - Priority order (later wins): `source_frames` list → 4 manual slots. - Explicit `imageN` inputs always override list entries at the same - cut-output position. Output dedup is automatic (set-based). At least - one of `image1` or `source_batch` must be provided. All keyframes - (and `source_batch` frames) must share the same H/W/C. + Insert modes (the inverse) — connect a packed sequence to + `keyframes_insert` and the node instead *scatters* those frames onto the + `source_frames` positions (i-th frame → i-th position, ascending). The + background depends on `source_batch`: - Offset mode (the inverse) — connect a packed sequence to - `keyframe_batch` and the node instead *scatters* those frames back onto - the `source_frames` positions (i-th frame → i-th position, ascending), - filling the rest with the placeholder. `source_batch` and the manual - slots are ignored while `keyframe_batch` is connected. Round-trips with - `selected_image_batch` + `selected_frames`. See `create_batch_from_frames`. + - `source_batch` NOT connected → gaps filled with the placeholder + (offset / reconstruct mode). Round-trips with `selected_image_batch` + + `selected_frames`. + - `source_batch` connected → the inserts are composited *over* the + `source_batch` cut window (insert-over-source mode): the source video + is the background and the listed positions are overwritten with the + insert frames. + + Connected manual slots (`image1`-`image4`) still apply here — they + composite on TOP of the inserts (a slot beats an insert at the same + position; the higher slot wins a slot collision). With an empty + `source_frames` list and no slots nothing is placed: a clean placeholder + batch (no `source_batch`) or a clean `source_batch` cut-window + passthrough. `alpha_batch` marks the inserted positions and slot + overwrites as placed. See `create_batch_from_frames`. Useful for preparing sparse control sequences for video models like Wan 2.2, where placeholder frames indicate no latent update. @@ -223,8 +334,8 @@ class easy_ImageBatch: "source_frames": ("STRING", { "default": "", "multiline": True, - "placeholder": "extra frames from source_batch, e.g. 1, 27, 41, 63, 85 (commas and/or newlines)", - "tooltip": "Optional 1-based VFX frame list to pick from source_batch. Separators can be commas, spaces, tabs, or newlines.", + "placeholder": "frames from source_batch, e.g. 1, 27, 41-50, 63 (commas/newlines; ranges with -)", + "tooltip": "Optional 1-based VFX frame list to pick from source_batch (in insert mode, the destination positions). Separators: commas, spaces, tabs, or newlines. Inclusive ranges like 14-17 expand to 14,15,16,17.", }), "image1_frame": ("INT", { "default": 5, @@ -232,22 +343,32 @@ class easy_ImageBatch: "max": 999999, "step": 1, "display": "number", - "tooltip": "1-based VFX frame number for image1, or source_batch pick when source_frames is empty.", + "tooltip": "1-based VFX frame number for image1; the connected image is placed here on top (no effect when the slot is unwired).", }), }, "optional": { + "keyframes_insert": ("IMAGE", { + "tooltip": ( + "Connect a packed sequence of frames to INSERT them onto the " + "timeline at the positions from the frame list (source_frames): " + "1st frame -> 1st number, 2nd -> 2nd, ... Connected image1-4 " + "still composite on top of the inserts. The background depends on " + "source_batch: if source_batch is NOT connected, the gaps are " + "filled with placeholder_color (offset/reconstruct mode); if " + "source_batch IS connected, the inserts are composited over the " + "source frames in the cut window (insert-over-source mode). With " + "an empty frame list and no slots nothing is placed: a clean " + "placeholder batch (no source_batch) or a clean source-batch " + "passthrough (with source_batch)." + ), + }), "keyframe_batch": ("IMAGE", { "tooltip": ( - "Connect a packed sequence of already-selected/processed " - "frames to switch the node into OFFSET mode: each incoming " - "frame is placed onto the timeline at the matching number " - "from the frame list (1st frame -> 1st number, 2nd -> 2nd, " - "...), with every other position filled by " - "placeholder_color. The frame list (source_frames) supplies " - "the positions; source_batch and image1-4 are ignored while " - "this is connected. Use it to rebuild a full-length sequence " - "from frames you exported, processed, and want back in their " - "original positions." + "Deprecated alias for keyframes_insert (the input was " + "renamed before release). Use keyframes_insert; this alias " + "keeps pre-rename workflows loading and will be removed in " + "a future release. Ignored when keyframes_insert is also " + "connected." ), }), "source_batch": ("IMAGE", ), @@ -259,7 +380,7 @@ class easy_ImageBatch: "max": 999999, "step": 1, "display": "number", - "tooltip": "1-based VFX frame number for image2, or source_batch pick when source_frames is empty.", + "tooltip": "1-based VFX frame number for image2; the connected image is placed here on top (no effect when the slot is unwired).", }), "image3": ("IMAGE", ), "image3_frame": ("INT", { @@ -268,7 +389,7 @@ class easy_ImageBatch: "max": 999999, "step": 1, "display": "number", - "tooltip": "1-based VFX frame number for image3, or source_batch pick when source_frames is empty.", + "tooltip": "1-based VFX frame number for image3; the connected image is placed here on top (no effect when the slot is unwired).", }), "image4": ("IMAGE", ), "image4_frame": ("INT", { @@ -277,7 +398,26 @@ class easy_ImageBatch: "max": 999999, "step": 1, "display": "number", - "tooltip": "1-based VFX frame number for image4, or source_batch pick when source_frames is empty.", + "tooltip": "1-based VFX frame number for image4; the connected image is placed here on top (no effect when the slot is unwired).", + }), + # width/height are intentionally LAST so they append to the end + # of the widget list — inserting them earlier would shift the + # positional widgets_values of every previously-saved workflow. + "width": ("INT", { + "default": 512, + "min": 1, + "max": 8192, + "step": 8, + "display": "number", + "tooltip": "Fallback output width. Used ONLY when no image input provides dimensions (e.g. a fully empty node producing a clean placeholder batch). Ignored whenever an image source is connected.", + }), + "height": ("INT", { + "default": 512, + "min": 1, + "max": 8192, + "step": 8, + "display": "number", + "tooltip": "Fallback output height. Used ONLY when no image input provides dimensions (e.g. a fully empty node producing a clean placeholder batch). Ignored whenever an image source is connected.", }), } } @@ -304,27 +444,25 @@ class easy_ImageBatch: image3_frame=None, image4=None, image4_frame=None, + keyframes_insert=None, keyframe_batch=None, + width=512, + height=512, ): - # Offset mode: when keyframe_batch is connected, switch behaviour from - # "select frames out of source_batch" to "scatter this packed sequence - # back onto the frame-list positions". See create_batch_from_frames. - if keyframe_batch is not None: - ignored = source_batch is not None or any( - img is not None for img in (image1, image2, image3, image4) - ) - return self.create_batch_from_frames( - keyframe_batch, - source_frames, - total_frames, - cut_start_frame, - placeholder_color, - invert_alpha, - ignored_inputs_connected=ignored, + # Deprecated alias: `keyframe_batch` was renamed to `keyframes_insert` + # before release. Honour a pre-rename workflow's connection so it keeps + # loading; an explicit `keyframes_insert` wins if both are wired. + if keyframes_insert is None and keyframe_batch is not None: + keyframes_insert = keyframe_batch + print( + "[easy_ImageBatch] 'keyframe_batch' is deprecated; use " + "'keyframes_insert'. Routing the connected batch to insert mode." ) - # Validate per-slot image inputs (must be single-frame tensors, - # not pre-batched). source_batch is the only accepted multi-frame input. + # Validate per-slot image inputs (must be single-frame tensors, not + # pre-batched) for BOTH modes — manual slots are now composited as the + # top layer in insert mode too, not just select. source_batch is the + # only accepted multi-frame input. for slot_name, slot_img in ( ("image1", image1), ("image2", image2), @@ -337,16 +475,54 @@ class easy_ImageBatch: "tensor. Use the 'source_batch' input for pre-batched stacks." ) + # Manual slots (image1-4) are the top compositing layer (Layer 3) in + # every mode: a connected imageN overwrites output frame imageN_frame on + # top of the sequence/background. Higher slot wins on a collision. + slots = ( + (image1, image1_frame), + (image2, image2_frame), + (image3, image3_frame), + (image4, image4_frame), + ) + + # Insert modes: when keyframes_insert is connected, switch from "select + # frames out of source_batch" to "scatter this packed sequence onto the + # frame-list positions", then composite the slots over the result. The + # background depends on source_batch: + # - no source_batch -> placeholder gaps (offset / reconstruct mode) + # - with source_batch -> the inserts sit over the source cut window + # (insert-over-source mode) + # See create_batch_from_frames. + if keyframes_insert is not None: + return self.create_batch_from_frames( + keyframes_insert, + source_frames, + total_frames, + cut_start_frame, + placeholder_color, + invert_alpha, + base_batch=source_batch, + slots=slots, + ) + # Determine reference H/W/C/device/dtype from the first available source. - # Priority: image1 → source_batch → image2 → image3 → image4. + # Priority: image1 → source_batch → image2 → image3 → image4. With no + # image source connected at all, fall back to the width/height widgets + # and emit a clean placeholder batch instead of erroring. reference = None for candidate in (image1, source_batch, image2, image3, image4): if candidate is not None: reference = candidate break if reference is None: - raise ValueError( - "easy_ImageBatch needs at least one of: image1, source_batch, image2, image3, image4." + if (source_frames or "").strip(): + print( + "[easy_ImageBatch] source_frames is set but no image source " + "(source_batch / image1-4) is connected; the list is ignored " + "and a clean placeholder batch is returned." + ) + return self._clean_placeholder_batch( + total_frames, height, width, 3, placeholder_color, invert_alpha ) h, w, c = reference.shape[1:] @@ -367,31 +543,53 @@ class easy_ImageBatch: f"({h}, {w}, {c}). All inputs must share the same H/W/C." ) - fill_value = _PLACEHOLDER_FILL[placeholder_color] - image_batch = torch.full((total_frames, h, w, c), fill_value, device=device, dtype=dtype) - # Alpha (default convention): 1.0 (white) = empty, set to 0.0 on occupied frames below. - # `invert_alpha` flips the result to compositing-style at the end. - alpha_batch = torch.ones((total_frames, h, w), device=device, dtype=torch.float32) - - # Track timeline indices that actually got a keyframe placed. Used at the - # end to build `selected_image_batch` (just the placed frames, packed) - # and `selected_frames` (their VFX numbers as a comma-separated string). - placed_indices: set[int] = set() - # VFX frame numbers that were referenced but landed outside the cut - # window. Collected once and printed as a single summary line at the - # end so each frame doesn't generate its own console warning. - outside_cut: list[int] = [] - + # Compute the frame list early — it decides the background. + source_frames_str = (source_frames or "").strip() source_len = source_batch.shape[0] if source_batch is not None else 0 - # 1) source_frames list (optional, additive). Each token is a VFX-numbered - # frame: it picks source_batch[N - 1] (VFX 1-based, hardcoded) AND - # places it at output index N - cut_start_frame. The 4 manual slots - # run after this and override per-position when needed. + # Background (Layer 1): with source_batch connected AND an empty frame + # list, pass the source cut window straight through (the same backdrop + # insert-over-source lays down) — "source in, source out" with nothing + # else to do. Otherwise fill with the placeholder; a non-empty list + # means select mode (pull the named frames onto a neutral background). + # The background is NOT counted as "placed": only list picks and slots + # are, so alpha / selected_* describe just those. + fill_value = _PLACEHOLDER_FILL[placeholder_color] + image_batch = torch.full((total_frames, h, w, c), fill_value, device=device, dtype=dtype) + # Alpha (default convention): 1.0 (white) = empty/to-fill; set to 0.0 on + # frames that carry real content. `invert_alpha` flips at the end. + alpha_batch = torch.ones((total_frames, h, w), device=device, dtype=torch.float32) + + # Timeline indices with a keyframe placed (picks + slots), used to build + # the selected_* outputs. `outside_cut` collects referenced VFX frames + # that fell outside the window (summarised once at the end). + placed_indices: set[int] = set() + outside_cut: list[int] = [] + + # Passthrough (empty list + source): the source cut window is the kept + # background — covered frames carry real content (alpha 0.0), the + # uncovered tail stays placeholder. `covered_indices` lets the selection + # below resolve to the GAP (the to-inpaint frames) rather than to the + # picks/slots that drive select mode. + passthrough = source_batch is not None and not source_frames_str + covered_indices: set[int] = set() + if passthrough: + base_placements, _ = plan_source_base_fill( + total_frames, cut_start_frame, source_len + ) + for output_index, source_index in base_placements: + image_batch[output_index] = source_batch[source_index] + alpha_batch[output_index] = 0.0 + covered_indices.add(output_index) + + # 1) source_frames list (optional). Each token is a VFX-numbered frame: + # it picks source_batch[N - 1] (VFX 1-based) AND places it at output + # index N - cut_start_frame, onto the placeholder. Runs only when the + # list is non-empty (an empty list is the passthrough case above); + # the manual slots run after and override per-position. # # The field is multiline: tokens may be separated by commas, newlines, # spaces, tabs, or any mix of them ("1, 27\n41 63" → [1, 27, 41, 63]). - source_frames_str = (source_frames or "").strip() if source_frames_str: if source_batch is None: print( @@ -399,15 +597,12 @@ class easy_ImageBatch: "ignoring the list (it only picks frames from source_batch)." ) else: - tokens = [t for t in re.split(r"[,\s]+", source_frames_str) if t] - for tok in tokens: - try: - f = int(tok) - except ValueError: - print( - f"[easy_ImageBatch] source_frames: ignoring non-integer token '{tok}'." - ) - continue + values, bad_tokens = parse_frame_tokens(source_frames_str) + for tok in bad_tokens: + print( + f"[easy_ImageBatch] source_frames: ignoring invalid token '{tok}'." + ) + for f in values: source_index = f - 1 output_index = f - cut_start_frame if not (0 <= source_index < source_len): @@ -424,57 +619,26 @@ class easy_ImageBatch: alpha_batch[output_index] = 0.0 placed_indices.add(output_index) - # 2) Resolve each manual slot to (frame_image, timeline_frame). A slot - # contributes iff (a) its imageN is connected, or (b) source_batch - # is connected (in which case imageN_frame doubles as the source - # pick index). Manual slots run AFTER the source_frames list so an - # explicit imageN always wins at its target position. - slots = ( - (image1, image1_frame, "image1"), - (image2, image2_frame, "image2"), - (image3, image3_frame, "image3"), - (image4, image4_frame, "image4"), + # 2) Manual slots (Layer 3, top priority): a connected image1-4 + # overwrites its output frame on top of the list picks / passthrough + # background. Higher slot wins on a collision (image4 > image1). + # Unconnected slots contribute nothing — imageN_frame is only a + # placement target for a wired imageN, never a source_batch pick. + self._apply_slot_overwrites( + image_batch, alpha_batch, placed_indices, slots, + total_frames, cut_start_frame, outside_cut, (h, w, c), ) - # When the user is driving the node from `source_frames`, the manual - # `imageN_frame` defaults shouldn't sneak in extra picks - # from source_batch — only the explicit list (and any explicitly - # connected imageN inputs) should contribute. So the source_batch - # fallback for an UN-connected slot is only active when the list is - # empty (the original 4-keyframe workflow). - slot_source_fallback = source_batch is not None and not source_frames_str - - for slot_img, slot_frame, slot_label in slots: - if slot_frame is None: - continue # Optional slot with no frame value — skip. - - output_index = slot_frame - cut_start_frame - - if slot_img is not None: - frame_tensor = slot_img[0] - elif slot_source_fallback: - # Pull VFX frame `slot_frame` from source_batch (1-based). - source_index = slot_frame - 1 - if not (0 <= source_index < source_len): - print( - f"[easy_ImageBatch] {slot_label}: VFX frame {slot_frame} doesn't " - f"exist in source_batch (size {source_len}, covers frames " - f"1..{source_len}); skipping." - ) - continue - frame_tensor = source_batch[source_index] - else: - # Either source_batch isn't connected, or source_frames is - # being used and no explicit imageN was wired — leave this - # slot empty so it doesn't pollute the selection. - continue - - if 0 <= output_index < total_frames: - image_batch[output_index] = frame_tensor - alpha_batch[output_index] = 0.0 - placed_indices.add(output_index) - else: - outside_cut.append(slot_frame) + # Passthrough selection: the "selected" frames are the GAP — placeholder + # frames with no source coverage and no slot, i.e. the ones to inpaint / + # generate. This is the inverse of select mode (where selected = the + # placed picks/slots); covered source and slot overwrites are kept + # content and excluded from the selection. + if passthrough: + content = covered_indices | placed_indices + placed_indices = { + i for i in range(total_frames) if i not in content + } if invert_alpha: alpha_batch = 1.0 - alpha_batch @@ -493,86 +657,183 @@ class easy_ImageBatch: ) # One-line summary of the cut window. Always logged so it's easy to - # confirm what the node produced; mentions outside-cut drops when any. + # confirm what the node produced; surfaces outside-cut drops, the + # passthrough background, and any source-shorter-than-cut fill. cut_end = cut_start_frame + total_frames - 1 + notes: list[str] = [] if outside_cut: unique_outside = sorted(set(outside_cut)) - outside_str = ", ".join(str(f) for f in unique_outside) - print( - f"[easy_ImageBatch] cut window: frames {cut_start_frame}..{cut_end} " - f"({total_frames} frames). {len(sorted_indices)} placed; " - f"{len(unique_outside)} outside cut: {outside_str}." + notes.append( + f"{len(unique_outside)} outside cut: " + + ", ".join(str(f) for f in unique_outside) ) - else: - print( - f"[easy_ImageBatch] cut window: frames {cut_start_frame}..{cut_end} " - f"({total_frames} frames). {len(sorted_indices)} placed." + if passthrough: + # kept = everything not in the gap (covered source + any slot fills). + notes.append( + f"source passthrough: {total_frames - len(sorted_indices)} kept, " + f"{len(sorted_indices)} gap frame(s) selected to inpaint" ) + suffix = (" " + "; ".join(notes) + ".") if notes else "" + print( + f"[easy_ImageBatch] cut window: frames {cut_start_frame}..{cut_end} " + f"({total_frames} frames). {len(sorted_indices)} placed.{suffix}" + ) return (image_batch, alpha_batch, selected_image_batch, selected_frames_out, ) + def _clean_placeholder_batch( + self, total_frames, h, w, c, placeholder_color, invert_alpha, device=None, dtype=None + ): + """Build a fully-empty batch (no placed frames) of the placeholder color. + + Used when nothing drives placement: a totally empty node (sized by the + width/height widgets, so ``device``/``dtype`` are absent → CPU/float). + ``image_batch`` is uniform placeholder; ``alpha_batch`` is all-empty + (1.0, flipped by ``invert_alpha``); ``selected_*`` outputs are + zero-length. Returns the standard 4-tuple. + """ + fill_value = _PLACEHOLDER_FILL[placeholder_color] + if device is None: + image_batch = torch.full((total_frames, h, w, c), fill_value) + alpha_batch = torch.ones((total_frames, h, w), dtype=torch.float32) + else: + image_batch = torch.full( + (total_frames, h, w, c), fill_value, device=device, dtype=dtype + ) + alpha_batch = torch.ones( + (total_frames, h, w), device=device, dtype=torch.float32 + ) + if invert_alpha: + alpha_batch = 1.0 - alpha_batch + print( + f"[easy_ImageBatch] clean batch: {total_frames} frames of " + f"{placeholder_color} placeholder; nothing placed." + ) + return (image_batch, alpha_batch, image_batch[:0], "") + + def _apply_slot_overwrites( + self, + image_batch, + alpha_batch, + placed_indices, + slots, + total_frames, + cut_start_frame, + outside_cut, + ref_hwc, + ): + """Composite connected manual slots on top of the batch (Layer 3). + + ``slots`` is ``((image, frame), …)`` for image1..image4 in ascending + order. Only connected slots with a frame value participate; each must + match ``ref_hwc`` (H/W/C). Higher slot wins on a collision (see + ``plan_slot_overwrites``). Mutates ``image_batch`` / ``alpha_batch`` / + ``placed_indices`` and extends ``outside_cut`` in place. + """ + h, w, c = ref_hwc + connected: list[tuple[int, int]] = [] + for slot_index, (img, frame) in enumerate(slots): + if img is None or frame is None: + continue + if tuple(img.shape[1:]) != (h, w, c): + raise ValueError( + f"image{slot_index + 1} shape {tuple(img.shape[1:])} does not match " + f"reference ({h}, {w}, {c}). All inputs must share the same H/W/C." + ) + connected.append((slot_index, frame)) + placements, slot_outside = plan_slot_overwrites( + connected, total_frames, cut_start_frame + ) + for slot_index, output_index, _vfx in placements: + image_batch[output_index] = slots[slot_index][0][0] + alpha_batch[output_index] = 0.0 + placed_indices.add(output_index) + outside_cut.extend(slot_outside) + def create_batch_from_frames( self, - keyframe_batch, + keyframes_insert, source_frames, total_frames, cut_start_frame, placeholder_color, invert_alpha, - ignored_inputs_connected=False, + base_batch=None, + slots=((None, None), (None, None), (None, None), (None, None)), ): - """Offset mode — active when ``keyframe_batch`` is connected. + """Insert mode — active when ``keyframes_insert`` is connected. - Scatter the packed ``keyframe_batch`` sequence back onto the timeline - at the positions listed in ``source_frames`` (i-th frame → i-th - position, ascending), filling the rest with ``placeholder_color``. This - is the inverse of the node's select behaviour: feed back a processed - copy of a previous ``selected_image_batch`` together with the same - ``selected_frames`` list to rebuild a full-length sequence in the - original spacing. ``source_batch`` and the 4 manual slots are ignored - in this mode (the wire-driven switch keeps the behaviour unambiguous). + Scatter the packed ``keyframes_insert`` sequence onto the timeline at + the positions listed in ``source_frames`` (i-th frame → i-th position, + ascending). The background depends on ``base_batch`` (the connected + ``source_batch``, or ``None``): - Outputs match the select path: ``image_batch`` (cut window), - ``alpha_batch`` (placed = 0.0 / empty = 1.0, flipped by - ``invert_alpha``), ``selected_image_batch`` (the placed frames packed - ascending), and ``selected_frames`` (their VFX numbers, round-trippable). + - **No ``base_batch`` (offset / reconstruct mode).** Gaps are filled + with ``placeholder_color``. Feed back a processed copy of a previous + ``selected_image_batch`` plus the same ``selected_frames`` list to + rebuild a full-length sequence in the original spacing. + - **With ``base_batch`` (insert-over-source mode).** The cut window of + ``base_batch`` is laid down first (VFX frame ``cut_start_frame + i`` + → ``base_batch[cut_start_frame + i - 1]``; frames beyond the source + fall back to the placeholder), then the inserts overwrite at the + listed positions. The inserts are composited *over* the source video. + + Connected manual slots (``image1``-``image4``) are then composited on + top as the highest layer — a slot overwrites an insert at the same + position, and the higher-numbered slot wins a slot-vs-slot collision. + + ``alpha_batch`` marks the inserted positions and any slot overwrites as + placed (0.0), regardless of the background — so ``selected_image_batch`` + / ``selected_frames`` describe just those. With an empty frame list and + no slots nothing is placed: a clean placeholder batch (no ``base_batch``) + or a clean ``base_batch`` cut-window passthrough. """ - if ignored_inputs_connected: - print( - "[easy_ImageBatch] offset mode (keyframe_batch connected): " - "source_batch and image1-4 are ignored; the frame list controls " - "placement." - ) - - h, w, c = keyframe_batch.shape[1:] - device = keyframe_batch.device - dtype = keyframe_batch.dtype + h, w, c = keyframes_insert.shape[1:] + device = keyframes_insert.device + dtype = keyframes_insert.dtype + # Background: source cut window (insert-over-source) or placeholder. fill_value = _PLACEHOLDER_FILL[placeholder_color] image_batch = torch.full((total_frames, h, w, c), fill_value, device=device, dtype=dtype) - # Same convention as the select path: 1.0 (white/empty), 0.0 on placed. + base_short_fallback = 0 + if base_batch is not None: + if tuple(base_batch.shape[1:]) != (h, w, c): + raise ValueError( + f"source_batch shape {tuple(base_batch.shape[1:])} does not match " + f"keyframes_insert ({h}, {w}, {c}). Both must share the same H/W/C." + ) + base_placements, base_short_fallback = plan_source_base_fill( + total_frames, cut_start_frame, base_batch.shape[0] + ) + for output_index, source_index in base_placements: + image_batch[output_index] = base_batch[source_index] + # Convention: 1.0 (white/empty), 0.0 on inserted positions only. alpha_batch = torch.ones((total_frames, h, w), device=device, dtype=torch.float32) - # Parse the position list (mirrors the select-mode tokenizer: commas, - # spaces, tabs, newlines, or any mix). - positions: list[int] = [] + # Parse the position list (commas, spaces, tabs, newlines, or any mix; + # inclusive ranges like "5-7" expand via parse_frame_tokens). source_frames_str = (source_frames or "").strip() - for tok in re.split(r"[,\s]+", source_frames_str): - if not tok: - continue - try: - positions.append(int(tok)) - except ValueError: - print(f"[easy_ImageBatch] source_frames: ignoring non-integer token '{tok}'.") + positions, bad_tokens = parse_frame_tokens(source_frames_str) + for tok in bad_tokens: + print(f"[easy_ImageBatch] source_frames: ignoring invalid token '{tok}'.") - source_count = keyframe_batch.shape[0] + source_count = keyframes_insert.shape[0] plan = plan_offset_placements(positions, source_count, total_frames, cut_start_frame) - placed_indices: list[int] = [] + placed_indices: set[int] = set() for source_index, output_index, _vfx in plan.placements: - image_batch[output_index] = keyframe_batch[source_index] + image_batch[output_index] = keyframes_insert[source_index] alpha_batch[output_index] = 0.0 - placed_indices.append(output_index) + placed_indices.add(output_index) + + # Manual slots (Layer 3): composite connected image1-4 on top of the + # inserts and the source background. A slot beats an insert at the same + # position; higher slot wins a slot-vs-slot collision. + slot_outside: list[int] = [] + self._apply_slot_overwrites( + image_batch, alpha_batch, placed_indices, slots, + total_frames, cut_start_frame, slot_outside, (h, w, c), + ) if invert_alpha: alpha_batch = 1.0 - alpha_batch @@ -586,9 +847,10 @@ class easy_ImageBatch: str(idx + cut_start_frame) for idx in sorted_indices ) - # One-line summary (mirrors select mode) with offset-specific notes so - # silent surprises (extra frames, short lists, outside-cut, empty list) - # are visible without scrolling per-frame logs. + # One-line summary (mirrors select mode) with insert-specific notes so + # silent surprises (extra frames, short lists, outside-cut, empty list, + # source shorter than the cut) are visible without per-frame logs. + mode_label = "insert-over-source mode" if base_batch is not None else "offset mode" cut_end = cut_start_frame + total_frames - 1 notes: list[str] = [] if plan.outside_cut: @@ -598,18 +860,27 @@ class easy_ImageBatch: ) if plan.unused_source: notes.append( - f"{plan.unused_source} extra source frame(s) had no listed position" + f"{plan.unused_source} extra insert frame(s) had no listed position" ) if plan.missing_positions: notes.append( - f"{len(plan.missing_positions)} position(s) had no source frame: " + f"{len(plan.missing_positions)} position(s) had no insert frame: " + ", ".join(str(f) for f in plan.missing_positions) ) + if base_short_fallback: + notes.append( + f"{base_short_fallback} cut frame(s) beyond source_batch filled with placeholder" + ) + if slot_outside: + notes.append( + f"{len(set(slot_outside))} slot frame(s) outside cut: " + + ", ".join(str(f) for f in sorted(set(slot_outside))) + ) if not source_frames_str: - notes.append("frame list is empty — nothing placed") + notes.append("frame list is empty — nothing inserted") suffix = (" " + "; ".join(notes) + ".") if notes else "" print( - f"[easy_ImageBatch] offset mode: cut window frames {cut_start_frame}.." + f"[easy_ImageBatch] {mode_label}: cut window frames {cut_start_frame}.." f"{cut_end} ({total_frames} frames). {len(sorted_indices)} placed.{suffix}" ) diff --git a/pyproject.toml b/pyproject.toml index fbce1a8..94787cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,5 +71,7 @@ addopts = ["--import-mode=importlib"] # ComfyUI custom node is not installed as a package in the workflow. # Locally use ``.venv/bin/pip install -e '.[test]'`` from the repo root. # ``aiohttp`` is a runtime dep of ``koolook_routes`` and must be importable -# even when only a pure helper is under test. -test = ["pytest>=8", "aiohttp>=3.9", "pip-audit>=2.7", "Pillow>=10"] +# even when only a pure helper is under test. The lower bound is pinned to +# the CVE-2026-54273..54280 fix line (>=3.14.1) so the declared range can't +# resolve a vulnerable aiohttp; the lock (constraints-test.txt) matches. +test = ["pytest>=8", "aiohttp>=3.14.1", "pip-audit>=2.7", "Pillow>=10"] diff --git a/tests/nodes/test_easy_image_batch.py b/tests/nodes/test_easy_image_batch.py index 3858c06..d3fd233 100644 --- a/tests/nodes/test_easy_image_batch.py +++ b/tests/nodes/test_easy_image_batch.py @@ -1,9 +1,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later -"""Tests for ``easy_ImageBatch`` offset mode. +"""Tests for ``easy_ImageBatch`` insert modes and schema. -The node imports ``torch`` at module load, but the offset *planner* -(``plan_offset_placements``) is pure Python and never touches torch — it -just computes which incoming frame lands at which output index. CI installs +The node imports ``torch`` at module load, but its *planners* +(``plan_offset_placements`` for insert placement, ``plan_source_base_fill`` +for the insert-over-source background) are pure Python and never touch torch +— they just compute which frame lands at which output index. CI installs only ``pytest`` / ``aiohttp`` / ``Pillow`` (no torch), so we stub the module to import the node torch-free and test the placement logic directly. """ @@ -12,13 +13,84 @@ from __future__ import annotations import sys import types +import pytest + # Must run before importing the node module (its top-level ``import torch``). # ``setdefault`` keeps a real torch if one happens to be installed. sys.modules.setdefault("torch", types.ModuleType("torch")) +# Enrich the stub with the handful of constructors ``create_batch`` uses so the +# *behaviour* (layer compositing, tie-break, passthrough) can be exercised +# torch-free — not just the pure planners. ``_FakeTensor`` wraps a flat list of +# frame-identity tags, so a test can assert *which* frame landed at each output +# index. Skipped automatically if a real torch is installed. +_torch = sys.modules["torch"] +_FAKE_TORCH = not hasattr(_torch, "__version__") +if _FAKE_TORCH: + class _FakeTensor: + """Minimal stand-in: a list of opaque per-frame tags + an H/W/C shape.""" + + def __init__(self, data, hwc=(2, 2, 3)): + self._data = list(data) + self._hwc = tuple(hwc) + self.device = "cpu" + self.dtype = "float32" + + @property + def shape(self): + return (len(self._data),) + self._hwc + + def __len__(self): + return len(self._data) + + def __getitem__(self, key): + if isinstance(key, int): + return self._data[key] + if isinstance(key, slice): + return _FakeTensor(self._data[key], self._hwc) + if isinstance(key, (list, tuple)): + return _FakeTensor([self._data[i] for i in key], self._hwc) + raise TypeError(key) + + def __setitem__(self, key, value): + if isinstance(key, int): + self._data[key] = value + else: + raise TypeError(key) + + def __rsub__(self, other): # 1.0 - alpha; identity is fine for these tests + return self + + def _fake_full(shape, fill_value, device=None, dtype=None): + return _FakeTensor([("placeholder", fill_value)] * shape[0], shape[1:]) + + def _fake_ones(shape, device=None, dtype=None): + return _FakeTensor([1.0] * shape[0], shape[1:]) + + _torch.float32 = "float32" + _torch.full = _fake_full + _torch.ones = _fake_ones +else: # pragma: no cover - real torch present, skip the integration tests + _FakeTensor = None + +_GRAY = ("placeholder", 0.5) # what _fake_full tags an unfilled frame with + + +def _source(n): + """A fake source_batch whose frame k (0-based) is the tag ``f"src{k+1}"``.""" + return _FakeTensor([f"src{k + 1}" for k in range(n)]) + + +requires_fake_torch = pytest.mark.skipif( + not _FAKE_TORCH, reason="behaviour tests rely on the fake-tensor stub" +) + from k_easy_image_batch import ( # noqa: E402 (import after stub) easy_ImageBatch, + parse_frame_tokens, plan_offset_placements, + plan_slot_overwrites, + plan_source_base_fill, ) @@ -89,14 +161,115 @@ def test_cut_start_offsets_output_indices(): assert plan.placements == [(0, 0, 41), (1, 22, 63)] -def test_input_types_exposes_keyframe_batch_with_tooltip(): +def test_source_base_fill_full_window_maps_one_to_one(): + # cut_start=1: output i -> source i (source_batch[0] is VFX frame 1). + placements, fallback = plan_source_base_fill(total_frames=5, cut_start_frame=1, base_len=10) + assert placements == [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)] + assert fallback == 0 + + +def test_source_base_fill_offsets_with_cut_start(): + # cut_start=41: output 0 -> source 40 (VFX frame 41), etc. + placements, fallback = plan_source_base_fill(total_frames=3, cut_start_frame=41, base_len=121) + assert placements == [(0, 40), (1, 41), (2, 42)] + assert fallback == 0 + + +def test_source_base_fill_beyond_source_falls_back_to_placeholder(): + # base only covers 2 frames; the remaining 3 cut frames stay placeholder. + placements, fallback = plan_source_base_fill(total_frames=5, cut_start_frame=1, base_len=2) + assert placements == [(0, 0), (1, 1)] + assert fallback == 3 + + +def test_source_base_fill_cut_starts_past_source_end(): + # Cut window starts beyond the source entirely → all placeholder. + placements, fallback = plan_source_base_fill(total_frames=4, cut_start_frame=50, base_len=10) + assert placements == [] + assert fallback == 4 + + +def test_slot_overwrites_basic_placement(): + # image1 @ VFX frame 4, cut_start 1 -> output index 3. + placements, outside = plan_slot_overwrites([(0, 4)], total_frames=24, cut_start_frame=1) + assert placements == [(0, 3, 4)] + assert outside == [] + + +def test_slot_overwrites_higher_slot_wins_collision(): + # image1 (idx 0) and image3 (idx 2) both target VFX frame 10 -> the + # higher-numbered slot wins, one winner per output index. + placements, outside = plan_slot_overwrites( + [(0, 10), (2, 10)], total_frames=24, cut_start_frame=1 + ) + assert placements == [(2, 9, 10)] + assert outside == [] + + +def test_slot_overwrites_higher_slot_wins_regardless_of_input_order(): + # Same collision, supplied highest-first: result must still pick image3. + placements, _ = plan_slot_overwrites( + [(2, 10), (0, 10)], total_frames=24, cut_start_frame=1 + ) + assert placements == [(2, 9, 10)] + + +def test_slot_overwrites_outside_cut_reported(): + # cut_start 10: frame 2 -> output -8 (outside); frame 15 -> 5 (in). + placements, outside = plan_slot_overwrites( + [(0, 2), (1, 15)], total_frames=20, cut_start_frame=10 + ) + assert placements == [(1, 5, 15)] + assert outside == [2] + + +def test_slot_overwrites_cut_start_offsets_output_indices(): + placements, _ = plan_slot_overwrites( + [(0, 41), (1, 63)], total_frames=81, cut_start_frame=41 + ) + assert placements == [(0, 0, 41), (1, 22, 63)] + + +def test_slot_overwrites_sorted_by_output_index(): + # Distinct frames, ascending by output index regardless of slot order. + placements, _ = plan_slot_overwrites( + [(0, 20), (1, 5)], total_frames=24, cut_start_frame=1 + ) + assert [out for _s, out, _v in placements] == [4, 19] + + +def test_slot_overwrites_empty_input_places_nothing(): + placements, outside = plan_slot_overwrites([], total_frames=10, cut_start_frame=1) + assert placements == [] + assert outside == [] + + +def test_input_types_exposes_keyframes_insert_with_tooltip(): spec = easy_ImageBatch.INPUT_TYPES() - assert "keyframe_batch" in spec["optional"], "new offset-mode input must be declared" - entry = spec["optional"]["keyframe_batch"] + assert "keyframes_insert" in spec["optional"], "insert-mode input must be declared" + # `keyframe_batch` is retained as a deprecated alias so pre-rename workflows + # keep loading (unreleased, but origin/main exposed it). + assert "keyframe_batch" in spec["optional"] + assert "deprecated" in spec["optional"]["keyframe_batch"][1]["tooltip"].lower() + entry = spec["optional"]["keyframes_insert"] assert entry[0] == "IMAGE" tooltip = entry[1]["tooltip"].lower() - assert "offset" in tooltip - assert "frame list" in tooltip + assert "insert" in tooltip + assert "source_frames" in tooltip + + +def test_input_types_exposes_width_height_fallback_at_end(): + spec = easy_ImageBatch.INPUT_TYPES() + # Fallback-only and declared OPTIONAL so they append to the END of the + # widget list — keeping positional widgets_values of older saved workflows + # aligned (inserting them earlier shifts placeholder_color/source_frames). + for key in ("width", "height"): + assert key in spec["optional"], f"{key} fallback widget must be optional" + assert key not in spec["required"] + assert spec["optional"][key][0] == "INT" + assert spec["optional"][key][1]["default"] == 512 + # They must be the last two optional keys (= last two widgets). + assert list(spec["optional"])[-2:] == ["width", "height"] def test_registration_exports_unchanged(): @@ -104,3 +277,300 @@ def test_registration_exports_unchanged(): assert NODE_CLASS_MAPPINGS["easy_ImageBatch"] is easy_ImageBatch assert NODE_DISPLAY_NAME_MAPPINGS["easy_ImageBatch"] == "Easy Image Batch (Koolook)" + + +# --- Behaviour (layer compositing) tests, run against the fake-tensor stub --- + + +@requires_fake_torch +def test_select_slot_overwrites_list_pick_at_same_frame(): + # source pick at frame 5 AND image1 wired at frame 5 -> the slot wins. + node = easy_ImageBatch() + image_batch, _alpha, selected, frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="5", + image1_frame=5, source_batch=_source(24), image1=_FakeTensor(["slotA"]), + ) + assert image_batch[4] == "slotA" # output index 4 == VFX frame 5 + assert frames == "5" + assert len(selected) == 1 + + +@requires_fake_torch +def test_select_higher_slot_wins_at_same_frame(): + # image1 and image2 both target frame 10 -> the higher-numbered slot wins. + node = easy_ImageBatch() + image_batch, _alpha, _sel, _frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="", + image1_frame=10, image1=_FakeTensor(["slot1"]), + image2=_FakeTensor(["slot2"]), image2_frame=10, + ) + assert image_batch[9] == "slot2" + + +@requires_fake_torch +def test_insert_slot_composites_on_top_of_inserts(): + # Inserts ins1/ins2/ins3 land at frames 5/7/20; image1 @ frame 5 wins there. + node = easy_ImageBatch() + image_batch, _alpha, _sel, _frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="5 7 20", + image1_frame=5, keyframes_insert=_FakeTensor(["ins1", "ins2", "ins3"]), + image1=_FakeTensor(["slotA"]), + ) + assert image_batch[4] == "slotA" # slot beats the insert at frame 5 + assert image_batch[6] == "ins2" # other inserts untouched + assert image_batch[19] == "ins3" + + +@requires_fake_torch +def test_select_empty_list_passes_source_through_with_no_phantom_picks(): + # EXE 2: source connected, empty list, no slots wired (defaults 5/9/13/17). + # Whole batch is the source passthrough; nothing is "placed". + node = easy_ImageBatch() + image_batch, _alpha, selected, frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="", + image1_frame=5, source_batch=_source(24), + image2_frame=9, image3_frame=13, image4_frame=17, + ) + assert [image_batch[i] for i in range(24)] == [f"src{k + 1}" for k in range(24)] + assert frames == "" # background is never counted as placed + assert len(selected) == 0 + + +@requires_fake_torch +def test_select_nonempty_list_ignores_unwired_slot_defaults(): + # EXE 3: only the listed picks are placed; the unwired slots' default + # imageN_frame values (9/13/17) do NOT pull phantom frames from source. + node = easy_ImageBatch() + image_batch, _alpha, _sel, frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="5 8 20", + image1_frame=5, source_batch=_source(24), + image2_frame=9, image3_frame=13, image4_frame=17, + ) + assert frames == "5, 8, 20" + assert image_batch[4] == "src5" + assert image_batch[7] == "src8" + assert image_batch[19] == "src20" + assert image_batch[8] == _GRAY # frame 9 stays placeholder, no phantom pick + + +@requires_fake_torch +def test_selected_image_batch_is_picks_plus_slots_slots_win_overlap(): + # selected_image_batch = union of multiline picks AND slot overwrites, + # ascending by frame, with the slot winning where they overlap. + node = easy_ImageBatch() + _img, _alpha, selected, frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="5 8", + image1_frame=5, source_batch=_source(24), + image1=_FakeTensor(["slotA"]), # overlaps the pick at frame 5 + image2=_FakeTensor(["slotB"]), image2_frame=12, # slot-only position + ) + assert frames == "5, 8, 12" + assert [selected[i] for i in range(len(selected))] == ["slotA", "src8", "slotB"] + + +@requires_fake_torch +def test_selected_image_batch_includes_inserts_and_slots_in_insert_mode(): + # Same rule under insert mode: selected = inserts + slot overwrites. + node = easy_ImageBatch() + _img, _alpha, selected, frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="5 7 20", + image1_frame=4, keyframes_insert=_FakeTensor(["ins1", "ins2", "ins3"]), + image1=_FakeTensor(["slotA"]), # slot-only position at frame 4 + ) + assert frames == "4, 5, 7, 20" + assert [selected[i] for i in range(len(selected))] == ["slotA", "ins1", "ins2", "ins3"] + + +# --- Feature: range syntax in the frame list (parse_frame_tokens) --- + + +def test_parse_frame_tokens_expands_inclusive_ranges(): + values, bad = parse_frame_tokens("1-5, 7, 9, 14-17") + assert values == [1, 2, 3, 4, 5, 7, 9, 14, 15, 16, 17] + assert bad == [] + + +def test_parse_frame_tokens_mixed_separators(): + values, bad = parse_frame_tokens("1-3 7\n10") + assert values == [1, 2, 3, 7, 10] + assert bad == [] + + +def test_parse_frame_tokens_single_value_range(): + values, bad = parse_frame_tokens("7-7") + assert values == [7] + assert bad == [] + + +def test_parse_frame_tokens_descending_range_is_bad(): + values, bad = parse_frame_tokens("5-1, 8") + assert values == [8] + assert bad == ["5-1"] + + +def test_parse_frame_tokens_non_integer_tokens_are_bad(): + values, bad = parse_frame_tokens("3, foo, 5-x, 9") + assert values == [3, 9] + assert bad == ["foo", "5-x"] + + +@requires_fake_torch +def test_select_accepts_range_syntax_in_list(): + node = easy_ImageBatch() + _img, _alpha, _sel, frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="1-3, 5", + image1_frame=4, source_batch=_source(24), + ) + assert frames == "1, 2, 3, 5" + + +@requires_fake_torch +def test_insert_accepts_range_syntax_in_positions(): + node = easy_ImageBatch() + _img, _alpha, _sel, frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="5-7", + image1_frame=4, keyframes_insert=_FakeTensor(["a", "b", "c"]), + ) + assert frames == "5, 6, 7" + + +# --- Feature: passthrough shorter than output -> keep + gap-as-selection --- + + +@requires_fake_torch +def test_passthrough_shorter_source_keeps_covered_and_selects_gap(): + # source = 17 frames, output = 24: frames 1-17 are kept (alpha 0/black), + # frames 18-24 are the gap (alpha 1/white) and become the selection. + node = easy_ImageBatch() + image_batch, alpha, selected, frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="", + image1_frame=4, source_batch=_source(17), + ) + # image: 1-17 source, 18-24 placeholder + assert image_batch[16] == "src17" + assert image_batch[17] == _GRAY + # alpha (inpaint): covered black (0.0), gap white (1.0) + assert alpha[0] == 0.0 and alpha[16] == 0.0 + assert alpha[17] == 1.0 and alpha[23] == 1.0 + # selection = the gap frames 18..24 (to inpaint) + assert frames == "18, 19, 20, 21, 22, 23, 24" + assert len(selected) == 7 + assert selected[0] == _GRAY + + +@requires_fake_torch +def test_passthrough_full_coverage_selects_nothing(): + # source >= output: no gap -> alpha all black (kept), selection empty. + node = easy_ImageBatch() + image_batch, alpha, selected, frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="", + image1_frame=4, source_batch=_source(24), + ) + assert [image_batch[i] for i in range(24)] == [f"src{k + 1}" for k in range(24)] + assert alpha[0] == 0.0 and alpha[23] == 0.0 + assert frames == "" + assert len(selected) == 0 + + +@requires_fake_torch +def test_passthrough_slot_in_gap_counts_as_kept_not_gap(): + # A slot filling a gap frame becomes kept content, excluded from the + # inpaint selection. + node = easy_ImageBatch() + _img, alpha, _sel, frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="", + image1_frame=20, source_batch=_source(17), image1=_FakeTensor(["slotA"]), + ) + assert alpha[19] == 0.0 # frame 20 filled by slot -> kept + assert frames == "18, 19, 21, 22, 23, 24" # gap excludes frame 20 + + +@requires_fake_torch +def test_passthrough_gap_with_cut_start_offset(): + # cut_start=41, total=10 -> output is VFX 41..50. source has 45 frames, so + # VFX 41..45 are covered (output 0..4) and 46..50 are the gap. + node = easy_ImageBatch() + image_batch, alpha, _sel, frames = node.create_batch( + total_frames=10, cut_start_frame=41, placeholder_color="Gray", + invert_alpha=False, source_frames="", + image1_frame=4, source_batch=_source(45), + ) + assert image_batch[0] == "src41" and image_batch[4] == "src45" + assert image_batch[5] == _GRAY # VFX 46 = gap + assert alpha[0] == 0.0 and alpha[4] == 0.0 # covered kept + assert alpha[5] == 1.0 and alpha[9] == 1.0 # gap to inpaint + assert frames == "46, 47, 48, 49, 50" # gap selection + + +# --- Review-round fixes: deprecated alias + input guards --- + + +def test_parse_frame_tokens_rejects_overlong_range(): + values, bad = parse_frame_tokens("1-99999999, 7") + assert values == [7] + assert bad == ["1-99999999"] + + +def test_parse_frame_tokens_allows_large_bounded_range(): + values, bad = parse_frame_tokens("1-100") + assert len(values) == 100 and bad == [] + + +def test_parse_frame_tokens_range_span_boundary(): + # exactly _MAX_RANGE_SPAN (8192) frames is allowed; one more is rejected. + ok, bad = parse_frame_tokens("1-8192") + assert len(ok) == 8192 and bad == [] + over, bad_over = parse_frame_tokens("1-8193") + assert over == [] and bad_over == ["1-8193"] + + +@requires_fake_torch +def test_keyframe_batch_alias_routes_to_insert_mode(): + # A pre-rename workflow wires `keyframe_batch` (not `keyframes_insert`); it + # must still behave like an insert. + node = easy_ImageBatch() + _img, _alpha, _sel, frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="5 7 20", + image1_frame=4, keyframe_batch=_FakeTensor(["a", "b", "c"]), + ) + assert frames == "5, 7, 20" + + +@requires_fake_torch +def test_keyframes_insert_wins_over_deprecated_alias(): + node = easy_ImageBatch() + _img, _alpha, selected, _frames = node.create_batch( + total_frames=24, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="5", + image1_frame=4, + keyframes_insert=_FakeTensor(["new"]), keyframe_batch=_FakeTensor(["old"]), + ) + assert selected[0] == "new" + + +@requires_fake_torch +def test_nonempty_list_without_image_source_warns_and_returns_clean(capsys): + # source_frames set but no source_batch / imageN -> clean placeholder batch, + # with a warning that the list was ignored (no silent drop). + node = easy_ImageBatch() + image_batch, _alpha, selected, frames = node.create_batch( + total_frames=5, cut_start_frame=1, placeholder_color="Gray", + invert_alpha=False, source_frames="2 3", + image1_frame=4, width=8, height=8, + ) + assert len(image_batch) == 5 + assert frames == "" and len(selected) == 0 + assert "source_frames is set but no image source" in capsys.readouterr().out