dependabot/github_actions/github-actions-30f7f1d3b7
75
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2babbafb6b |
chore(security): pin Actions and harden test lock (#273)
* chore(security): pin Actions and update aiohttp * fix(security): generate universal test lock * fix(security): harden relock safeguards |
||
|
|
7b00a551d9 |
feat(publish): independent output type for published setups; fix(pipeline): auto-versioning re-runs each queue (#267)
Publish: Koolook_PublishOutput output_mode/input_switch/switch; setupSurface.app.outputSwitch; routers auto-bound as the output selector; unwired output types hidden; 'Same as input' offered only when every input type has a wired writer; result path follows the output switch. Pipeline: EasyAIPipeline IS_CHANGED keeps version=auto/next perpetually dirty so the vNNN disk scan re-runs every queue; literal versions stay cacheable. |
||
|
|
040b30733d |
feat(matte): first-party mask-guided one-step video matting nodes (#269)
* feat(matte): first-party mask-guided one-step video matting nodes
Adds matte/ — an independent implementation of the published VideoMaMa
one-step SVD method (matte/pipeline.py) plus Koolook's temporal windowing,
subject-focused crop/stitch, resolution control (/64, shortest-side) and
auto OOM resolution-backoff. Nodes: KoolookMatteLoader, KoolookMatteSampler,
KoolookMatteFocusCrop, KoolookMatteFocusStitch.
Registered via the existing guarded _merge_node_group("Koolook Matte",
".matte"), so a missing diffusers disables only these nodes. diffusers is
already pulled in by the radiance/whatdreamscost forks, so no new dependency.
Loads (never bundles) the VideoMaMa UNet (CC-BY-NC) + SVD VAE at runtime;
matte/NOTICE records the model licenses and user responsibility.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(dev-sync): include matte/ in RUNTIME_PATHS
So `sync_to_dev` copies the new matte node package into the live ComfyUI
install alongside the rest of the koolook pack.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(matte): raw cond latents; fp16 default; preserve checkpoints on dev-sync
pipeline.py: _encode_latents must NOT divide by vae scaling_factor. The
VideoMaMa UNet is trained on raw VAE latents (matching okdalto's
VideoInferencePipeline, which multiplies by scaling_factor then divides it
back out -> net x1). Dividing over-scaled the conditioning ~5.5x and produced
a systematically blurry matte on every frame. Verified 5/5 vs the desktop /
vanilla reference on identical single-frame inputs.
nodes.py: harden the loader precision fallback so fp16 is the true default -
only an explicit "bf16" opts out; a stale "auto" widget now resolves to fp16
instead of bf16.
sync_to_dev.py: the rmtree+copytree mirror deleted matte/checkpoints/ (the
gitignored ~10GB SVD+VideoMaMa weights) on every matte sync, forcing a
re-download. Add PRESERVE_IN_DEST + selective _clean_dest so runtime-
downloaded model data survives a sync.
docs: add docs/maintainers/matte-pipeline.md (latent-scaling invariant,
black-output causes, fidelity levers, single-frame + vanilla-oracle debug
loop, checkpoint preservation).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(matte): address PR #269 review
- license: align matte to the repo's GPL-3.0 (was self-contradictory: claimed
"MIT licensed - see LICENSE" while LICENSE is GPL-3.0). pipeline.py header,
NOTICE, and the maintainer doc updated. NOTICE still credits VideoMaMa
(CC BY-NC) + SVD (Stability Community License).
- nodes.py _ensure_2d_mask: squeeze first, then handle [H,W,C]/[C,H,W]
explicitly and RAISE on ambiguous shapes -- previously [H,W,3] silently
became [W,3] (wrong geometry).
- nodes.py OOM backoff: catch RuntimeError (OutOfMemoryError subclasses it, but
cuBLAS/cuDNN/older paths raise plain "RuntimeError: CUDA out of memory"); the
existing message guard still filters non-OOM errors.
- nodes.py: warn when the guide mask is empty instead of silently falling back
to full-frame.
- sync_to_dev.py: PRESERVE_IN_DEST is now an exact relative path
("matte/checkpoints"), not a bare name matched at any depth; handle symlinks
before is_dir() in _clean_dest so a symlinked dir isn't recursed/rmtree'd.
- CHANGELOG: add the Koolook Matte nodes under [Unreleased] with the
non-commercial model-license caveat.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(matte): address PR #269 re-review
- sync_to_dev.py [BLOCKING]: a top-level symlinked dest (e.g. target/matte -> X)
was passed straight into _clean_dest()/copytree(), which follow the link and
can mutate files outside the target. Now unlink a symlinked dest and own the
path as a real dir (checked before exists(), which follows symlinks). Verified
with a repro: the symlink target's file survives.
- nodes.py: force float32 on a CPU fallback (CUDA unavailable). Half precision
is unreliable on CPU; fp16 kernels would error. Warn that CPU is very slow.
- docs: correct the preserved-path guidance to the target-relative
"matte/checkpoints" (was a bare "checkpoints" name); reword "near-verbatim
reimplementation" -> "independent implementation targeting behavioural parity"
to match the NOTICE/pipeline framing.
- nodes.py: also warn on a degenerate (<8px) guide-mask region before the
full-frame fallback (was silent).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(matte): address PR #269 re-review (CI red + FocusCrop frame check)
- nodes.py: KoolookMatteFocusCrop now validates images vs coarse_mask frame
counts (raises like KoolookMatteSampler.run_inference). A mismatch previously
made cropped_images/cropped_mask diverge and silently drop/misalign frames at
FocusStitch.
- pipeline.py: split "vae.to('cpu'); _clear_cache(...)" onto two lines (Ruff
E702 was failing Lint & Smoke).
- constraints-test.txt: bump pillow 12.2.0 -> 12.3.0 (pip-audit CVE, fixed in
12.3.0). Pillow is a leaf dep so the closure is unaffected; CI re-audits.
- README: note the Koolook Matte nodes need diffusers (already via the LTX /
Radiance forks) and huggingface_hub for runtime weight downloads.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: AI Machine <ai.machine@x.baconcph.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
ba37ec3d41 |
feat: LTX Director 2.0.2 fork, keyframe grid-snap, and audio→video bind schedule (#259)
* feat(forks): LTX Director v2_0_2 fork + keyframe latent-grid snap (#258) Bump the WhatDreamsCost Koolook fork to upstream 2.0.2 (pinned fe09f73) in a new versions/v2_0_2 namespace, and add `snap_keyframes_to_grid` (default on) to fix hard-pinned image keyframes "jumping" at latent-bucket edges. Why: a Director image keyframe is injected as a single latent frame at the segment's left edge, quantized to the model temporal stride s (L(f)=0 if f==0 else 1+(f-1)//s). Pins near a bucket boundary round ambiguously and two pins in one bucket collide. Snapping each pin to its bucket center removes the ambiguity without changing which latent frame it lands on; colliding pins are bumped to the next free bucket with a warning. - forks/whatdreamscost_koolook/versions/v2_0_2/: vendored 2.0.2 sources with the v1.3.9 Koolook deltas carried forward (relay_overrides, audio_transcript_json, per-segment sigma — build_segments is byte-identical 1.3.9->2.0.2 so the sigma patch drops in verbatim), plus new pure keyframe_grid.py snapper wired into the insert_frames build. - Canonical LTXDirector__koolook rolls to 2.0.2 (entrypoint loads v2_0_2 last); LTXDirector__koolook_v1_3_2 alias preserved; v1.3.9 kept on disk. - LICENSE re-verified GPL-3.0 (2026-06-22). Manifest + THIRD_PARTY + CHANGELOG + audio-lipsync findings/CHEATSHEET updated. - 37 new unit tests for the snapper; 107 fork tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(forks): append Koolook LTX Director widgets last for clean load Move the v2_0_2 Koolook widgets (relay_overrides, audio_transcript_json, snap_keyframes_to_grid) to the end of the schema, after override_audio. They were inserted after img_compression, which shifted the last upstream widget when an upstream-2.0.x-saved workflow is loaded onto LTXDirector__koolook. With them last, an upstream graph's positional widgets_values map onto the upstream widgets untouched and only the Koolook widgets append with defaults (snap_keyframes_to_grid=on). Execution is unaffected (kwargs by name). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(forks): make v2_0_2 a clean upstream-2.0.2 replica + grid-snap only Per maintainer direction: the v2_0_2 Koolook LTX Director should faithfully replicate upstream 2.0.2 (so all latest features work) rather than carry the older customizations, which are unused. - Drop relay_overrides, audio_transcript_json, and the per-segment sigma patch. prompt_relay.py and patches.py are now vendored VERBATIM from upstream 2.0.2; _relay_overrides.py removed. - ltx_director.py is upstream 2.0.2 verbatim except two additive changes: the namespaced node ID LTXDirector__koolook, and the snap_keyframes_to_grid latent-bucket keyframe snap (issue #258), appended last in the schema. - v2_0_2 registers only LTXDirector__koolook; the legacy LTXDirector__koolook_v1_3_2 ID stays backed by the v1_3_9 package. v1.3.9 (with the older customizations) remains on disk for rollback. - Manifest / THIRD_PARTY / UPSTREAM_PIN / CHANGELOG updated to the clean scope. grid-snap unit tests unchanged (107 fork tests pass). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(forks): vendor upstream 2.0.2 timeline editor JS for LTXDirector__koolook The v2_0_2 Python was upstream-exact but the node still rendered with the old 1.3.9-era timeline editor (fewer toolbar actions, no IC-LoRA track, leaked *_second widgets, frame_rate NaN). Replace web/whatdreamscost_koolook/ ltx_director.js with upstream 2.0.2's js/ltx_director.js vendored verbatim, retargeted with only two edits: unique extension name 'LTXDirector_Koolook' and node guard nodeData.name === 'LTXDirector__koolook'. UX now matches upstream 2.0.2 exactly; only the Python differs (grid-snap). The old koolook base64-scrub deltas are dropped as obsolete — upstream 2.0.2 persists timeline media via imageFile/audioFile + /api/view URLs (saved timelines are already lean), and the localStorage quota guard stays in the global web/koolook_draft_guard.js (upstream does not override localStorage). Rewrote tests/sidebar/test_ltx_director_draft_guard.py to the new contract (no embedded guard, retargeted to the koolook node, pinned-upstream provenance). 110 fork+contract tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(scripts): resolve .env from main repo when dev-sync runs in a worktree Unrelated to the LTX work: plain `dev-sync` failed with "KOLOOK_COMFYUI_DEV_PATH not set" from any worktree because scripts/sync_to_dev.py only loaded `.env` from the worktree root, where the committed `.env` does not exist (it lives in the main checkout). sync_to_dev_audio.py already had the worktree->main fallback, which is why dev-sync-audio worked. Move that fallback into a shared sync_to_dev.find_dotenv() and use it in both scripts so they can't drift. Adds regression tests for the worktree fallback, the worktree-own-.env preference, and the not-a-worktree case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: gitignore root-level generated workflow JSONs Node-swapped/converted workflow copies (e.g. *.koolook.json) are dropped at the repo root during dev iteration; they're scratch deliverables, not package content. Ignore root-level *.json only (leading `/`), with a `!config.json` exception, so the 30+ tracked JSON in subdirs (test fixtures, web/, .claude/, docs/runs/*/workflow.json) stay tracked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: LTX A/V Bind Schedule node — gate audio→video binding by sigma New Koolook node LTXAVBindSchedule decouples big motion from lip-sync in LTX 2.3. The AV transformer (comfy/ldm/lightricks/av_model.py, BasicAVTransformerBlock) adds audio into video via a gated `audio_to_video_attn` per block, run only when transformer_options["a2v_cross_attn"] is set. This node patches each block's audio_to_video_attn.forward to scale its output by a per-step gain, and a unet-function-wrapper computes that gain from the current sigma: early/high-sigma steps run near `early_gain` (coarse motion settles audio-blind), ramping (smoothstep) to full at low sigma (lips bind onto settled motion). One pass, no re-noise — the fix for keyframe transitions jumping on audio peaks. early_gain=1.0 reproduces stock behavior; a graph without the node is unchanged. Registered as a root node + added to RUNTIME_PATHS so dev-sync ships it. Pure ramp (a2v_gain) unit-tested (9 cases); model wiring validated by live render. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: LTX A/V Bind Schedule — add transition-window mode (lips synced + smooth) The global sigma ramp decoupled motion from lips but desynced the mouth (lip timing is coarse motion too, so holding audio out globally moved the mouth audio-blind). Add a WINDOWED mode: audio binds at full strength everywhere (lips sync across the whole clip) EXCEPT a +/-transition_window band around each keyframe transition, where the sigma ramp applies — so only those moments are motion-protected early and rejoined late. Implemented as a per-latent-frame gain on a2v_out: read grid_sizes from transformer_options to map frame-major video tokens -> latent frames, build a gain vector (1.0 outside bands, smoothstep-tapered to sigma_gain at each transition center), expand per-frame -> per-token, scale. New optional inputs: transition_frames, total_frames, transition_window. Empty transition_frames = prior global-ramp behavior. windowed_frame_gains unit-tested (7 cases). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: LTXAVBindSchedule — one-shot diagnostic log (shapes/grid/mode) Logs out.shape, grid_sizes, transition_fracs, and whether the per-frame mask fired vs fell back to scalar — so a single render reveals whether the a2v patch hooks the model and whether the windowed token->frame mapping aligns, instead of guessing blind. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(audio-lipsync): record validated a2v bind-schedule knee (0.35/0.25/0.55) Promote the finding that LTX 2.3's audio->video coupling must be sigma-ramped (not full-on) to keep motion smooth AND lips synced, with the validated knee early_gain=0.35 / bind_start=0.25 / bind_end=0.55 on the 8-step base pass, plus the trade-axis table and the per-frame-mode caveat (grid_sizes None + appended guide frames -> scalar fallback). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(forks): keyframe ease — strength-ramped neighbor pins to de-robotic transitions Adds `keyframe_ease` / `ease_falloff` to the Koolook LTX Director (default off). Each hard image pin keeps its exact center pose, but emits same-pose neighbor pins one latent bucket apart at decreasing strength (center*falloff**k), so the model glides into and out of the locked pose instead of snapping to a dead stop — softening the "stop/dissolve/stop" robotic feel without softening the pose. Implemented as a pure list expansion (`expand_keyframe_ease` in keyframe_grid.py, 6 unit tests) applied to guide_data after grid-snap, so the upstream guide node is untouched (no fork). Center stays exact; neighbors that fall off-clip or onto another center are skipped. Costs extra guide frames (more compute), hence opt-in. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(audio-lipsync): downgrade a2v knee to 'promising' — note lows-placement confound The 0.35/0.25/0.55 win was measured on a clip whose transitions sit on audio lows, so 'no jumps' may be the placement, not the node. Mark the controlled on-peak test (early_gain 1.0 vs 0.35 on the same on-peak layout) as still pending. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: mark vendored WhatDreamsCost fork files linguist-vendored The koolook Director ships upstream 2.0.2 code (the 8.5k-line timeline-editor JS + verbatim prompt_relay/patches) so the pack runs and distributes, but it's upstream, not authored here. Mark those linguist-vendored (drop from language stats) and the JS -diff (collapse the large blob in PR diffs). Our edited/new files stay attributed to us. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ltx-av): drop unused `import math` (ruff F401) a2v_gain uses an explicit smoothstep polynomial, not math.*, so the import was dead. Unblocks the Lint & Smoke CI gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(codeql): advanced setup that excludes vendored upstream code CodeQL default setup cannot read a config file, so it scanned the verbatim-vendored WhatDreamsCost editor (web/whatdreamscost_koolook/ ltx_director.js) and raised 4 high-sev DOM-XSS alerts in upstream code we do not own. Switch to advanced setup with a committed config whose paths-ignore mirrors the .gitattributes linguist-vendored block, so the scanner treats vendored upstream as out-of-scope while still fully scanning our own nodes and sidebar JS. Languages match the prior default-setup matrix (actions, javascript-typescript, python). NOTE: requires disabling "default setup" once in repo Settings before this workflow can run (GitHub rejects advanced runs while default setup is enabled). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): scope vendored fork exclusions to v2_0_2 * fix(forks): preserve final LTX keyframe snap bucket --------- Co-authored-by: AI Machine <ai.machine@x.baconcph.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ba39cb3658 |
Claude/goofy faraday d23e98 (#255)
* refactor(nodes): drop keyframe_batch alias from Easy Image Batch The keyframe_batch input was a deprecated alias of keyframes_insert, added in 0.4.1 during PR #246 review to keep pre-rename workflows loading. The old name only ever existed in unreleased dev builds before the rename, so no published workflow depends on it and the alias only cluttered the node with a second IMAGE input. Remove the input, its parameter, and the alias-routing fallback; the node now exposes a single keyframes_insert insert input. Tests and the behavior spec updated; CHANGELOG notes the removal under [Unreleased]. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(policy): make backward compatibility opt-in for Koolook nodes Invert the default: backward compatibility is no longer a standing constraint for Koolook-created nodes (the root k_*.py custom nodes). By default, make the cleanest change — rename/drop/reorder IDs, inputs, and outputs freely, no deprecation aliases or _v2 suffixes. Compatibility with saved workflows is only preserved when the maintainer explicitly says "check backward compatibility". Forks are explicitly excluded: anything under forks/ (including the Koolook-original nodes exposed via SKIP_VERSION_SUFFIX) keeps full back-compat discipline by default, no request needed. The __koolook_vX_Y_Z collision suffix always applies. Scoped across the binding instruction files and skills: CLAUDE.md, copilot-instructions, node-versioning.md, versioning.md, and the preflight-release / add-external-fork / tdd skills. The preflight workflows check is reframed as a drift detector for Koolook IDs and a hard back-compat gate for fork IDs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: clarify keyframe_batch alias removal * fix: retain keyframe_batch compatibility alias --------- Co-authored-by: AI Machine <ai.machine@x.baconcph.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
472960b919 |
docs(setups): add publish -> run loop architecture overview (#254)
* docs(setups): add publish -> run loop architecture overview Entry-point doc that ties the existing published-setup docs together: the loop in plain terms (Mermaid), a technical map of frontend JS -> Koolook aiohttp routes/handlers -> ComfyUI engine, and the contract / schema requirements. All file paths and symbols verified against source. Diagrams use Mermaid per docs/maintainers/conventions.md. Linked from the maintainer docs index as the "read this first" entry for the group. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(setups): fix publish-run loop references * docs: codify review-fix push rule --------- Co-authored-by: AI Machine <ai.machine@x.baconcph.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
cb19c375c9 |
feat(ci): tag-triggered release automation (publish + GitHub Release) (#252)
Push a semver tag (vX.Y.Z) at the squash-merge commit on main to publish to the Comfy Registry and create the matching GitHub Release from the CHANGELOG (only on a successful publish). Pre-publish guard refuses any tag that is not on main (merge-base --is-ancestor); per-job least-privilege permissions. publish.yml kept as a manual fallback. Docs synced: releasing.md + release_checklist.md. |
||
|
|
bda4df576a |
Release v0.4.1 (#250)
* Release v0.4.0: update footer notice * Release v0.4.1 |
||
|
|
ae7abba73b |
docs: refresh shipped help and tooltips (#249)
* docs: refresh shipped help and tooltips * docs: add snapshot transfer guide section * docs: refresh guide screenshots |
||
|
|
88dbfedf86 |
feat(nodes): Easy Image Batch keyframes_insert + insert-over-source +… (#246)
* feat(nodes): Easy Image Batch keyframes_insert + insert-over-source + clean batch - Rename optional input keyframe_batch -> keyframes_insert (node ID unchanged). - Insert-over-source mode: when keyframes_insert AND source_batch are both connected, composite the inserts over the source cut window (instead of the placeholder background). keyframes_insert alone keeps the offset/reconstruct behaviour over placeholder. - No image source connected -> clean placeholder batch sized by new width/height fallback widgets, instead of raising. width/height are appended LAST so older saved workflows keep their positional widgets_values alignment (fixes a mid-list insertion that shifted placeholder_color into source_frames). - alpha_batch marks only inserted/placed positions; source background is not counted as placed. - New pure planner plan_source_base_fill (torch-free) alongside plan_offset_placements; both unit-tested in CI. - Docs: behavior matrix spec, interactive HTML demo, and a generated "all versions" ComfyUI demo workflow under docs/designs/. Not yet implemented (specced as DRAFT in docs/designs, pending maintainer approval + tie-break decision): image1-4 "super-overwrite" layer across all modes, and removal of the Mode 2 unconnected-slot source_batch fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(deps): raise aiohttp [test] floor to >=3.14.1 The constraints lock already pins aiohttp 3.14.1 (the CVE-2026-54273..54280 fix line), but pyproject's [test] extra still declared aiohttp>=3.9. Dependabot keys off pyproject, not the lock, so the open declared range kept eight range-based alerts open even though the installed version is patched. Raising the floor to >=3.14.1 aligns the declared range with the lock and clears the alerts once this reaches the default branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(nodes): Easy Image Batch image1-4 super-overwrite + source passthrough Make the four manual slots the top compositing layer in every mode (select and insert): a connected imageN overwrites output frame imageN_frame on top of the sequence/inserts and the background. Higher slot wins a collision (image4 > image1); a slot beats a sequence pick / insert at the same position. Extracted as the torch-free plan_slot_overwrites planner + a shared _apply_slot_overwrites helper used by both code paths. Two model fixes ride along, per the finalised behaviour matrix: - An unconnected slot now contributes nothing -- imageN_frame is only a placement target for a wired image, never a source_batch pick. The old unwired-slot fallback (which surfaced seemingly-random frames) is removed. - source_batch + an empty frame list (no insert) now passes the source cut window straight through instead of a placeholder batch, matching the insert-mode empty-list passthrough. Tests: new plan_slot_overwrites planner cases + fake-tensor behaviour tests covering slot-beats-pick/insert, higher-slot-wins, passthrough-not-placed, and the removed phantom fallback. Docstring, tooltips, user guide, design spec (promoted DRAFT -> canonical), and CHANGELOG updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(nodes): assert selected_image_batch = picks/inserts + slots; drop matrix HTML demo selected_image_batch already packs every placed position -- both the source_frames picks / inserts and the manual-slot overwrites, with slots winning on overlap. Add explicit select- and insert-mode tests asserting the composition and ascending order, locking the behaviour in. Also delete docs/designs/easy-image-batch-matrix.html (stale interactive demo built against an earlier draft) and drop its companion reference from the design spec. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(nodes): Easy Image Batch passthrough inpaint mask + frame-range syntax Two refinements to the Easy Image Batch passthrough/list behaviour: - Short-source passthrough is now an extend/inpaint mask. When source_batch is connected and the frame list is empty, the covered source frames are kept content (alpha 0.0) and the uncovered tail becomes the selection (selected_image_batch / selected_frames) with alpha 1.0 -- "keep the first N, generate the rest". Full coverage -> no gap, empty selection. The selection is the gap here (inverse of select mode); insert mode is unchanged. - The source_frames field accepts inclusive ranges: "1-5, 7, 9, 14-17" expands to 1,2,3,4,5,7,9,14,15,16,17, in both select and insert, via a shared torch-free parse_frame_tokens (descending/non-integer tokens warn). Tests cover the gap selection (short/full/cut-start-offset/slot-in-gap), the parser (ranges, bad tokens), and range syntax end-to-end. Docstring, tooltip, user guide, design spec, and CHANGELOG updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(designs): remove draft-era Easy Image Batch demo workflow + builder Delete easy-image-batch-demo-setup.json and build_easy_image_batch_demo.py (the leftover draft-era demo, sibling to the already-removed matrix HTML) and drop the companion reference from the behavior spec. A purpose-built demo will replace them later; the spec matrix + the node remain the canonical reference. Also drop the now-stale "(proposed)" from the spec title (it is canonical). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(user-guide): remove stale v0.1.5 Easy Image Batch screenshot The screenshot predates the node's keyframes_insert / super-overwrite / passthrough rework and is unreferenced. Removing it to be re-created fresh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: point Easy Image Batch docs at the bundled Select-and-Rebuild demo Note the runnable demo workflow ("Easy Image Batch - Select and Rebuild", bundled into the Kforge Labs Workflows sidebar folder via #248) in both the user guide and the design spec, replacing the generated demo removed earlier. Referenced by path (not a clickable link) since the workflow lands via #248. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(nodes): address PR review — keyframe_batch alias + input guards - Restore keyframe_batch as a deprecated alias of keyframes_insert so pre-rename workflows (origin/main exposes the input) keep loading; an explicit keyframes_insert wins. Follows docs/maintainers/node-versioning.md (alias-then-deprecate); removal deferred to a release boundary. - Warn instead of silently returning a clean batch when source_frames is non-empty but no image source is connected. - Cap frame-list range expansion at 8192 frames/token to guard against typos like 1-99999999. - Docs: reframe the bundled demo-workflow note as forthcoming (the JSON lands via #248, not on this branch); document the alias, cap, and no-source guard in the user guide / design spec / CHANGELOG. Tests: alias routing + precedence, no-source warning, range cap + boundary (46 node tests). Fresh-eyes review: no correctness/compat issues (the IMAGE alias input adds no widget-position shift). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: point Easy Image Batch docs at the now-merged Select-and-Rebuild demo #248 merged to main and is now in this branch (via the main merge), so the demo workflow exists at docs/workflows/kforge-labs-workflows/. Repoint the user guide and design spec at it directly (present tense, real folder link) instead of the earlier "coming soon / pending" framing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: AI Machine <ai.machine@x.baconcph.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
4383c2fc0b | feat(sidebar): bundle Kforge demo workflows (#248) | ||
|
|
6f158c9020 |
fix(nodes): Easy_LoadVideo empty-branch degrade (bundles #243 publish-tab cleanup) (#244)
* docs(setups): add setup runner quickstart runbook Standalone reference for opening the published-setup mockup frontend (setup runner simulator): the `<comfy-address>/koolook/setup_runner_simulator.html` URL pattern, the live-registry vs setup-file (inspect-only) load paths, and demo mode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sidebar): close throwaway workflow tabs left by publish capture Publishing a setup captures ComfyUI's API-format prompt by loading the graph onto the canvas (graphToPrompt) and reloading the prior canvas. In current Comfy each loadGraphData spawns an "Unsaved Workflow" tab, so every publish left two stray tabs behind. captureWorkflowApiPrompt now snapshots the open workflows before capture and, afterward, re-activates the user's original tab and closes whatever the capture opened, via app.extensionManager.workflow. All of it is guarded and best-effort: the workflow-store API is Comfy-version specific, so if it is absent capture still succeeds and behavior falls back to today (no crash, no broken publish). Covered by a source-pattern test. Needs live verification against the 1.45 frontend (dev-sync) to confirm the closeWorkflow API shape. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(nodes): Easy_LoadVideo degrades to empty on an unselected folder branch When an upstream mode switch routes a path into Easy_LoadVideo, both the EXR sequence loader and the video loader execute eagerly. On the EXR branch the video loader was handed a folder (no filename) and raised ValueError, crashing the whole prompt -- even though that branch is discarded downstream. Lenient sequence loaders just ignore a wrong-type path; the video loader was the only one that hard-failed. _compose_input_video_path now returns EMPTY_BRANCH_SENTINEL instead of raising when input_path resolves to an existing directory. load_video turns that into a correctly-shaped empty result (IMAGE zeros, frame_count 0, empty AUDIO, empty video_info) matching VHS_LoadVideoPath returns, so the dead branch no longer crashes the prompt. VALIDATE_INPUTS and IS_CHANGED handle the sentinel too. A path that does not exist is still a genuine mistake and keeps raising. torch is imported lazily in the empty-result builder so the path logic stays importable/testable without torch. Covered by unit tests for the sentinel (absolute + relative dirs), the still-raises case, and a load_video integration test that asserts the empty result and that the loader never runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(setups): expose prompt as an always-on field in the publish frontend The publish surface modelled `prompt` as a 4th source mode (mutually exclusive with EXR/QT/Img) and hid it (visible:false), so a published setup couldn't offer "pick a source AND describe the shot". This makes prompt a standalone, always-on input. - published_surface.js: prompt becomes visible + standalone + multiline, rendered independently of the source switch; the author's prompt-widget text is surfaced as a `placeholder` hint (submitted default is empty so an untouched hint is never sent), with a `help` line. The switch excludes standalone fields from its source options. - setup_runner_simulator.js: renders always-on standalone inputs alongside the selected source (textarea + placeholder + help), includes them in the run payload, and updates the demo setup. - setup_runner_simulator.html: .field-help styling. - contract doc: documents the always-on prompt behaviour. - launch.json: portability fix (hardcoded C:\Python313 -> python). Contract test updated + a new test asserting prompt is always-on, carries the placeholder, and is excluded from the switch source options (runs on CI where Node is available). Behaviour verified in the runner simulator (demo mode) in a browser: textarea + hint, EXR/QT/Img-only switch, prompt stays visible across source changes, payload includes the typed prompt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(setups): mirror always-on prompt field in the Python publish inference The publish surface is inferred server-side in Python (koolook_setups.py _infer_app_surface, called by publishSetup), which overrides the client. The earlier change only updated the JS (published_surface.js), so re-publishing still produced the old surface with a hidden prompt. Mirror the JS here: prompt is a standalone, always-on input (visible + multiline) with the author's prompt-widget text as a placeholder hint and an empty submitted default; it is excluded from the source switch options. Keeps koolook_setups.py and published_surface.js in sync. Registry test updated to the new shape. Server + publish-contract suites green (115); full non-sidebar suite green (530). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(setups): keep the runner simulator polling through long renders The poller used a fixed 30s total budget, so a multi-minute render (e.g. 428s) timed out mid-run with "Timed out waiting for run" even though ComfyUI was still rendering and the run later landed as succeeded. timeoutMs is now a "no-progress" window: while ComfyUI keeps reporting the run as queued/running, the deadline is pushed forward, so a long-but-active render never spuriously times out; only a stalled/vanished run gives up. Default raised 30s -> 120s. The run status line shows elapsed seconds while active so a long wait visibly ticks instead of looking frozen. Happy path (queued -> running -> succeeded) unchanged; verified in the runner simulator (demo mode) in a browser. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(setups): serve simulator assets with no-store so dev-syncs take effect The runner-simulator HTML/JS route returned no cache headers, so browsers cached the assets and kept running stale JS after a dev-sync -- e.g. the old 30s poller persisted client-side even though the server was serving the long-render fix, which looked like "the fix didn't land". Serve these dev-synced assets with Cache-Control: no-store so a normal reload always picks up the current code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(setups): add live run/poll smoke check for the published-setup API tools/check_setup_run.py queues a published setup through the same routes the runner simulator uses and polls the run endpoint with the same deadline-extension logic, printing the status timeline. Lets us self-verify end-to-end run tracking against a live ComfyUI (e.g. "does a long render reach succeeded?") without driving the browser UI. Used it to confirm the run/poll server flow is correct (terminal status detected in ~2s; run-000001 reported succeeded), isolating the simulator's earlier "Timed out" to stale browser cache rather than a server bug. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(nodes): Koolook_NextVersion auto-detects the next output version Adds filesystem-aware version auto-detection so a re-run lands on a fresh version instead of failing with "Output file already exists and overwrite is disabled" (the real EasyAIPipeline failure seen on the RMGB setup). - koolook_versioning.next_version_token(directory, name, ...): scans for existing <name>_vNNN files AND sequence-folders (handles name_v002.png, name_v002.0001.exr, name_v003/), returns highest+1 zero-padded; missing/ empty dir -> start (v001); exact base-name match so other shots never bump this one; empty name matches bare vNNN; configurable prefix/padding. - Koolook_NextVersion node (Koolook/Publish): folder + name -> next version token. Wire once into both the EXR and video version inputs so branches stay in sync. IS_CHANGED returns NaN to re-scan every run. Tested: helper edge cases + node registration/run. Preflight clean (20 node IDs). Full non-sidebar suite green (539). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(nodes): 'auto' version keyword for EasyAIPipeline + Easy_VideoCombine No new node or wiring needed: type 'auto' (or 'next') in the version field and the node scans its own output folder for existing <name>_vNNN entries and writes the next free version -- fixing "Output file already exists and overwrite is disabled" without manual version bumps. - koolook_versioning.is_auto_version(): detects the auto/next keyword. - EasyAIPipeline (build_pipeline_outputs): on auto, scans the version-folder parent (base, or base/shot/method when subfolders are on) for vNNN dirs and picks the next; version computed after path segments so the scan dir is known. - Easy_VideoCombine: on auto, resolves the token after the output dir + name root are composed, scanning that dir for <name>_vNNN outputs. - Non-auto/typed/wired tokens and disable_versioning are unchanged. Tests: is_auto_version keywords + EasyAIPipeline auto (v003 after v001/v002, v001 first run). Full non-sidebar suite green (542), preflight clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(nodes): drop redundant Koolook_NextVersion node The 'auto' version keyword on EasyAIPipeline + Easy_VideoCombine covers auto-detection without any new node or wiring, so the standalone Koolook_NextVersion node is redundant. Remove it (and its node test) to keep the node list clean; the shared next_version_token helper stays (the 'auto' keyword uses it). Node-ID count back to 19; full non-sidebar suite green (541), preflight clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(tools): satisfy security gate for setup smoke check * fix(pr244): address verified review findings * fix(video): fail if relative auto-version scan root is unavailable --------- Co-authored-by: Cristian Predut <cristian.predut@x.baconcph.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: AI Machine <ai.machine@x.baconcph.com> |
||
|
|
e165c96fcc | fix(ci): enforce dependency audit gate (#247) | ||
|
|
30d51cf365 |
fix(loop): harden Koolook_LoopStatus auto-queue (#241)
Auto server_url detection (widget defaults to 'auto'); the connected index wire is authoritative for frame-index detection (a stale numeric label no longer overrides it); fold in #207 (string-bool auto_queue_next + detected index-node logging); review hardening (IPv6 / bare --listen, Bandit B104, blank server_url, debug diagnostics). |
||
|
|
df34ddcf1a |
feat(nodes): Easy Image Batch offset mode via keyframe_batch input (#242)
Add the inverse of the node's select behaviour. When the new optional keyframe_batch IMAGE input is connected, the node scatters a packed sequence back onto the source_frames positions (i-th frame -> i-th listed number, ascending), placeholder fill in between, at total_frames length. This closes the select -> process -> reconstruct loop: feed a processed 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-4 are ignored while keyframe_batch is connected, with a console note); count 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, where torch is unavailable. Fully backward-compatible: unconnected keyframe_batch preserves the existing select path exactly. Tests: 10 new torch-free tests; full suite 630 passed, 1 skipped; preflight_release green. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ae2480eee9 |
feat(setups): publish success feedback + published-setup discoverability (#227) (#239)
* feat(setups): publish success feedback + published-setup discoverability (#227) Post-publish feedback (issue #227 Part 1): - POST /koolook/api/setups now returns the on-disk storagePath - new POST /koolook/api/setups/reveal opens the published-setups folder (shared _open_dir_in_file_manager helper; the snapshot reveal route was refactored onto it, behaviour-identical) - the publish modal renders a success card reusing the snapshot Save dialog's "Saved to ... Open folder" language (Open folder / Copy path / Close) instead of closing silently; a draft publish still gets a confirmation Published discoverability (reuses the tag system, no new schema): - publishing auto-tags the source workflow `published` -> a "published" badge in the Workflows tree + the Tags-section "published" pool - a new "P" toggle in the sidebar Tools row prunes the Workflows tree to published-only, folders force-expanded, so setups show in their origin structure; toggling off restores the full tree - workflowHasTag() shared helper (unit-tested) Deferred, still open on #227: separate destination/library setting (Part 2), relocating the storage folder beside the snapshot library, and clearing the filter when entering Compare mode. Tests: +server (storagePath, reveal route happy + 404), +client (reveal client, buildPublishSuccessView view-model), +sidebar (workflowHasTag). Full suite: 610 passed, 1 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setups): store published setups beside the snapshot library (#227) - the published-setup registry path now derives from the configured snapshot library (`_configured_dir().parent / koolook-published-setups/setups.json`) instead of a fixed user-dir folder, so it follows libraryPath / KFORGELABS_PRESETS. The default config resolves to the same path as before (target == legacy), so existing default installs are unchanged. - non-destructive one-time migration: copy a legacy user-dir registry to the new location when the new one is absent, leaving the original in place. - sidebar: clear the "P" published filter when entering Compare mode so it doesn't render both compare columns filtered with no toggle to undo it. Tests: +tests/server/test_published_setups_relocation.py (path beside library; migration copy / no-op-when-present / no-op-when-no-legacy). Full suite: 614 passed, 1 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(tests): remove unused pathlib.Path import (ruff F401) The relocation test only uses the `tmp_path` fixture, not `Path` directly, so `from pathlib import Path` was unused and failed the Lint & Smoke (ruff) gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(setups): address PR #239 review — migration masking + tag persist - HIGH: a failed legacy->new migration no longer masks real setups with the bundled sample. The default registry keeps the legacy file as its primary storage when migration could not produce the new file, so stranded data stays visible. (+2 tests: keeps-legacy-on-copy-failure / uses-new-on-success) - MEDIUM/LOW: the publish flow now awaits the `published`-tag persist so the success card reflects a committed tag (persist failures already surface via persistMutation's critical toast), and warns when the source workflow could not be tagged (vanished mid-publish) rather than on a harmless republish. Full suite: 616 passed, 1 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(setups): harden migration against partial/corrupt relocation target Addresses the remaining HIGH from the PR #239 consolidated re-review: a zero-byte/partial/corrupt setups.json at the relocated path could shadow the intact legacy registry (existence was treated as proof of migration). - migration now copies via temp file + atomic os.replace (the same pattern FileSetupStorage.save_setups uses), so an interrupted copy can never leave a partial setups.json at the target; failed-copy temp files are cleaned up. - primary selection is corruption-aware: when the relocated file exists but does not parse as a setups document while the legacy file is intact, the registry keeps serving the legacy file and logs a loud diagnostic. The unreadable file is left untouched (never overwritten) as evidence. A valid relocated file stays authoritative; if both are corrupt, the relocated path stays primary so storage diagnostics point at the current location. Tests (+4): corrupt-target-prefers-intact-legacy (file left untouched), valid-target-stays-authoritative, both-corrupt-keeps-target, and interrupted-copy-leaves-no-partial-target-and-no-tmp-litter. Full suite: 620 passed, 1 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
91cee1daae |
docs(setups): add external frontend kit (#236)
External frontend kit handoff for published-setup consumers; refreshes full-circle/status docs and neutralizes stale current-PR apiPrompt wording per review #4485848881. |
||
|
|
c6a155a033 |
fix(web): generation-agnostic Comfy draft-quota guard (#237)
* fix(web): generation-agnostic Comfy draft-quota guard "Failed to save workflow draft" toasts returned on ComfyUI frontend 1.44+ because draft storage moved to new localStorage key names (per-workspace V1 keys, then a V2 index+payload scheme) that the old exact-name guard embedded in ltx_director.js never matched - and the 1.44 V2 layer latches a session-wide storage-unavailable flag after one failed save+evict cycle, re-toasting on every edit until reload. Move the guard to its own global extension, web/koolook_draft_guard.js: - match draft keys by prefix (Comfy.Workflow.Draft) across all known generations, with unknown future keys as last-resort evictions - rescue quota failures inside localStorage.setItem (evict the oldest draft across generations, retry) before Comfy's own catch sees the error, so the save succeeds and the latch never trips - boot prune: drop the suffixed-V1 dead weight the frontend's V1->V2 migration never deletes, scope corrupt-key cleanup to the offending key, drop oversized entries, enforce a total draft budget - ship the guard with dev-sync-audio so a scoped sync cannot strand a dev install guard-less Behavior is covered by tests/js/test_draft_guard.mjs (12 scenarios, wired into CI next to the existing node smoke test); static integration contracts live in tests/sidebar/test_draft_guard.py. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(web): address deep-review findings on the draft guard - latch the "still full" hint toast once per session so a genuinely full origin cannot re-toast on repeated failing autosave attempts; regression assertion added to the exhaustion scenario - document the bounded-eviction termination contract and the post-rethrow storage-unavailable latch at the wrapper, plus the budget's V1-blob-only (1.5M) -> all-generations (2M) scope widening (also now stated in the CHANGELOG entry) - version-robust wording for the suffixed-V1 cleanup rationale: the shipped 1.44 migration leaves the keys behind (verified against the installed 1.44.19 dist - no removeItem exists there), later frontends clean up on successful migration, and an interrupted migration can leak the keys on any version - comment the intentional best-effort swallow in the toast helper Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1732030175 |
fix(setups): resolve switch-selected run results (#235)
* fix(setups): resolve switch-selected run results * fix(setups): harden switch result reporting * fix(setups): reject stale execution map writers |
||
|
|
c3c34f0171 |
Capture API prompt when publishing setups (#233)
* feat(sidebar): capture API prompt when publishing setups * feat(setups): preview and load canonical setup records * fix(setups): close publish review gaps |
||
|
|
85206ab321 |
feat(setups): add external runner simulator (#231)
* feat(setups): add external runner simulator * feat(setups): render app-surface simulator form * docs(setups): capture publish-to-run frontend plan * docs(setups): preserve sidebar inventory breadcrumbs * fix(setups): address runner simulator review findings * fix(setups): validate simulator run payload shape |
||
|
|
16c1b42760 |
Add published setup execution API (#230)
* feat(setups): add published setup execution API * fix(setups): expose app surface run results * test(setups): harden execution API failures |
||
|
|
83b170bdae |
feat(setups): add publish contract nodes (#228)
* feat(setups): infer app contract from named fields * feat(setups): add publish contract nodes * feat(setups): split publish result contract * fix(setups): resolve publish output reroutes in preview * fix(setups): validate app surface on read * fix(setups): harden app contract validation |
||
|
|
7cffe0702e |
fix(setups): publish simple widget-only workflows (#223)
* fix(setups): publish simple widget-only workflows * fix(setups): read subgraph and named widget defaults * fix(setups): allow optional publish descriptions * fix(setups): reject incomplete widget-only values |
||
|
|
3b35e6a195 |
feat(setups): infer setup surface from workflow groups (#222)
* feat(setups): infer setup surface from workflow groups * fix(setups): validate inferred setup surfaces on read * fix(setups): reject boolean setup geometry |
||
|
|
b4314f3b3d | feat(sidebar): preserve workflow groups in saved selections (#220) | ||
|
|
98c204b119 |
feat(setups): convert visual workflows to API prompts (#217)
* feat(setups): convert visual workflows to api prompts * fix(setups): reject corrupt visual graph links |
||
|
|
16023662e6 | feat(setups): publish sidebar workflows (#216) | ||
|
|
1cf2fd31a2 |
feat(setups): add published setup catalog API (#214)
* feat(setups): add published setup catalog API * fix(setups): report registry storage diagnostics |
||
|
|
8c8546c17c |
Add queue-driven loop status controller (#205)
* Add queue-driven loop status node * Document ComfyUI server API testing * Harden loop queue controller * Guard loop controller HTTP requests |
||
|
|
7f8c09ad29 | feat(sidebar): clean up workflow archives (#201) | ||
|
|
67e9d55e98 |
docs(nodes): add user guides and tooltips (#200)
* docs(nodes): add user guides and tooltips * chore: clean up node preflight nits |
||
|
|
447465ecd8 |
refactor(nodes): remove superseded LTX Director extra nodes + harden registry (#198) (#199)
* refactor(nodes): remove superseded LTX Director extra nodes + harden registry (#198) Remove the two experimental nodes layered on the WhatDreamsCost fork — KoolookAudioTranscriptTimeline (k_audio_timeline.py) and KoolookTimelineEditor (k_timeline_editor.py) — plus the transcribe_audio_timeline.py CLI and its test. The audio-transcript to Director-timing job now lives inside the fork via the Director's audio_transcript_json input, so the standalone nodes are redundant. Also drops the orphaned [audio]/faster-whisper extra and updates packaging, dev-sync, web, and docs references. Harden the node registry against the #183 failure: __init__.py now imports each node group independently and installs the /koolook/presets/* routes regardless of node-import results, so one missing or broken module degrades to 'that node absent' instead of zeroing every node and 404'ing the sidebar disk routes. A new test keeps every root k_*.py listed in dev-sync's RUNTIME_PATHS. Closes #198. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(nodes): ship loader gates via dev-sync-audio; generalize RUNTIME_PATHS guard (#198) Address the K-Flame review on #199: MEDIUM — the #198 refactor made koolook_versioning.py a single registration gate, but dev-sync-audio shipped the new __init__.py without it (and the k_*.py-glob guard test couldn't see it). Add koolook_versioning.py AND koolook_install_guard.py (whose absolute-import fallback raises uncaught) to AUDIO_PATHS so a scoped sync can never leave the loader pointing at a module the target lacks. Generalize the guard test: parse __init__.py's actual load-time import targets (relative imports + _merge_node_group calls) and assert each is in RUNTIME_PATHS, instead of globbing k_*.py which missed the non-k_ gates. Add an AUDIO_PATHS gate-coverage test. LOW — CHANGELOG now records the [audio]/faster-whisper + transcribe_audio_timeline.py removal as an external-contract break. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8058e17456 |
fix(snapshot): paired drift-corruption fix — duplicate-install guard + boot-time mismatch (#161, #162) (#186)
* fix(snapshot): prevent drift corruption from duplicate installs + boot-time mismatch (#161, #162) Closes #162 and #161 as the paired fix the issues describe — both touch the same data-loss surface and verify against the same dev-sync rig. installs at import time, log critical, register nothing in the alphabetical loser; client-side `window` sentinel as belt-and-suspenders; align .env.example / dev-iteration-loop / sync_to_dev / README at the Registry-derived `koolook/` path. compare against live /userdata + picks, set a drift flag on mismatch; gate periodic + pre-load autosave routing to `_unsaved_autosave/` while drifted; promote status pill to "drifted" precedence over "saved" with a recovery tooltip; auto-clear on the next markStateSaved (Save / Load). 26 new tests (14 install guard, 4 sidebar sentinel, 8 boot drift) — 316 total passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(snapshot): apply review fixes — canonical fingerprint + early loser exit + drift timestamp Review-driven follow-ups on PR #186: 1. **Symmetrize file-side fingerprint (MEDIUM).** `detectBootDrift` now uses a new `_canonicalStateFingerprint()` that normalizes the workflows tree through `normalizeWorkflowsStore` AND deep-sorts keys via `sortJsonValue`. Without this, hand-edited / cross-machine / pre-v0.3 files would false-positive as drifted on first boot (e.g. `archived: "false"` string vs `false` bool, missing `directories: {}`, key insertion-order asymmetry from the `{...wf, archived, module, tags}` spread). `_computeFingerprint` stays unchanged so the localStorage save baseline contract holds for existing users. New regression test verifies a shape-only diff does NOT trigger drift. 2. **Gate fork imports on `_is_winning_install` (LOW).** Non-winning duplicate installs now short-circuit before the heavy torch / PIL / diffusers / aiohttp imports, exiting in ~5 LoC instead of paying full import cost just to discard the mappings. 3. **Drift timestamp uses `driftDetectedAt`, not stale `lastNamedSaveAt` (LOW).** The localStorage save baseline could be from a prior session whose live state was already corrupt — surfacing it as "Last named save" in the drift tooltip would mislead. Replaced with the session-noticed-at timestamp from `_bootDriftDiagnostics`. 352 tests passing (316 prior + 26 from PR #186 + 1 new normalize- symmetry case). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(install-guard): fail safe on unreadable / non-UTF-8 siblings (#162) Re-review follow-up. The import-time duplicate-install scan could crash the entire plugin load (fail-silent — the alphabetical winner vanished too, not just the loser) on two unguarded paths: - detect_duplicate_koolook_installs: the per-entry is_dir()/is_file() over custom_nodes/ siblings was unguarded, so a non-traversable sibling (restrictive perms, broken mount, a special dir) raised PermissionError up into __init__.py. No duplicate even required. Now caught per-entry and skipped. - read_pyproject_version: read_text(encoding="utf-8") raises UnicodeDecodeError (a ValueError, NOT OSError) on a non-UTF-8 / binary pyproject.toml, escaping the `except OSError`. Now degrades to "?" via `except (OSError, ValueError)`. - __init__.py: wrapped the guard call site as a final backstop — any unforeseen guard failure now degrades to register-normally rather than aborting the import. Both paths reproduced before the fix; the first surfaced while running the suite from a /tmp checkout whose parent held a root-owned dir. 3 regression tests added (non-traversable sibling skipped; non-UTF-8 pyproject -> "?"; report survives a non-UTF-8 sibling). Full suite: 452 passed (449 + 3). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: kflame99 <261674183+kflame99@users.noreply.github.com> |
||
|
|
3a0f835814 | fix(audio-loop): read active director card values (#195) | ||
|
|
63f5c1db7b |
fix(audio-loop): capture card metadata and Director pins (#193)
* fix(audio-loop): capture card metadata and Director pins * fix(audio-loop): address card metadata review * fix(audio-loop): remove duplicate card import |
||
|
|
201cdf3001 |
Track audio loop card setup metadata (#191)
* feat(audio): surface director flavor and prompt mode * Track audio loop card setup metadata * Fix audio card review issues * Harden audio card metadata capture |
||
|
|
1d9d40ab70 |
Add audio transcript timeline node (#189)
* Add audio transcript timeline node * Hook audio transcripts into Koolook Director * Add modular timeline audio timing workflow * fix(frontend): guard Comfy workflow draft cache * fix(audio): compose transcript timeline prompts * fix(audio): address transcript review findings * fix(ci): keep transcript tests lightweight * fix(timeline): restore image previews from saved paths * fix(timeline): scrub restored preview blobs |
||
|
|
a5b6494848 | fix(audio): remove stale director web sync folder (#188) | ||
|
|
031af80f43 |
feat(forks): upgrade Koolook Director to stable v1.3.9 (#187)
* feat(forks): upgrade Koolook Director to stable v1.3.9 * docs(forks): clarify Director upgrade compatibility |
||
|
|
26124e08d5 |
refactor(loop-audio): card reads only multilines + Koolook Director (#177) (#185)
* refactor(loop-audio): card reads only multilines + Koolook Director (#177) The audio-lipsync card is now bounded to two source families: the five tracked Text Multiline nodes and the LTX Director (Koolook v1.3.2) node's own widgets + input wiring. SAMPLER block (BasicScheduler / KSamplerSelect / RandomNoise / CFGGuider scrapes), FORK STATE block (_dev_build.json + git status), and INERT warning are gone — they were either widget scrapes outside the two source families or duplicates of the Director's own state. New card layout (top to bottom): HEADER run NNN — {name} + date + workflow filename KNOB STATE relay_overrides (the per-render knob) BASE · NOTES overlay - info verbatim, blank lines preserved BASE · LOCKED epsilon · Audio src · Working folder (path-wrapped) BASE · SCENE Segments (N) parent, indented `1) 0 to 5 seconds` child list, flat Prompt/Audio/Keyframe coverage POST-RENDER overlay - feedback + parsed motion/sync/sharp Audio src now derived structurally from a 4-state machine (audio_vae link × use_custom_audio × audioSegments count) that mirrors ltx_director.py:610-622 — never from substring matches on prompt text. Per-segment Audio coverage checks audioSegments[] overlap, not the maintainer's [Audio]: prompt formatting. Also fixed: upstream-LTXDirector widget index bug (saved workflow keeps legacy order even after Koolook fork reordered the schema; DIRECTOR_WIDX matches the serialised order); loop_audio.py's .env discovery now falls back to the main repo when run from a worktree (same pattern as make_card.py); notes.md drops the widget-scraped Mechanical interp section; log.md columns realigned (Audio src + Segments replace Phase 1 / Phase 2 / Custom audio). Three new docs alongside the module: reading-graph.html visual flow (dark theme, color-coded bands) reading-graph.schema.yaml machine-readable mirror of the same graph CHEATSHEET.md refreshed against the new design All 231 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(lint): split semicolon statements + drop unused symbols in make_card_audio Ruff (project's Lint & Smoke gate) flagged 10 issues in the refactored card renderer: - F401 unused imports — `typing.Optional` at the top of the file and `loop_audio.REPO_ROOT` in the standalone CLI's import block. - F841 unused locals — `dur_f` and `dur_s` were left over from the dropped "Duration" row; segment time ranges convey the same info now. Kept in the state dict for notes.md; not read in the renderer. - E702 six semicolon-joined statements in the HEADER and POST-RENDER sections (`draw.text(...); y += N`). Split into separate lines. Rendering is unchanged — verified by re-running the standalone CLI against run-002. All 249 tests still pass; ruff now clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(loop-audio): resolve PR #185 multi-agent review findings Anchored by the verified multi-agent review on PR #185 — every fix traces to a confirmed finding (false-positive rate was 0/11). HIGH: - derive_audio_state now distinguishes "(no director)" from "off (no VAE)". Pre-fix, is_input_wired(None, ...) returned None, the truthiness check collapsed director-missing into VAE-unwired, and the card / log silently agreed on the wrong story. Added an early return when director_node is None. Also documented that vae_wired can be None (input socket absent in older schemas) and that we intentionally collapse it with False at the unwired branch — after the missing-director case has been split off. - extract_multilines now iterates needles longest-first. Pre-fix the per-node `break` was order-dependent on config insertion: a future short substring (e.g. "relay") in the same tracked_multilines list would have silently shadowed "relay_overrides". Sorting by length descending removes that footgun without breaking the current 5-needle layout. - parse_timeline now coerces per-segment numeric fields (start / length / trimStart) to int via int(float(...)) with try/except → 0 fallback. The upstream Director itself does this defensively at ltx_director.py:232-234; without coercion a string-valued field silently breaks video_segment_has_audio's `<` comparisons (lexical) and crashes the renderer at `start / fps`. Also drops non-dict entries from segments / audioSegments arrays so a malformed JSON can't slip past json.loads + isinstance checks. - Duplicated wrap_path + video_segment_has_audio bodies in make_card_audio.py are gone. Renderer now imports them at module level from loop_audio so the two scripts agree byte-for-byte on path-wrapping rules and audio-overlap geometry. MEDIUM: - DIRECTOR_WIDX now carries a MIRROR: comment pointing at the YAML schema's `widget_order:` block (the cross-link the review asked for as the minimum drift-prevention fix). - duration_frames / duration_seconds removed from the state dict and from render_audio_card's docstring. The renderer dropped the Duration row earlier in the PR; notes.md reads them straight from director_node via director_widget, so keeping them in state was documenting a contract the renderer didn't honour. YAML schema now splits them out under a notes_only section to match. - Log-row score formatting now uses an explicit `is not None` check (`_score()` helper) instead of `or '?'` — 0 was a legitimate score the maintainer could type that pre-fix collapsed to '?' because 0 is falsy. Matches the card's existing `_s()` helper for parity. - Card already surfaces the missing-Director state distinctly thanks to derive_audio_state returning "(no director)" — the BASE · LOCKED Audio src row shows that label loudly when applicable. No additional row needed. LOW: - Renderer falls back to "1) frames 0-120 (no fps)" when frame_rate is None instead of computing "0 to 0 seconds" silently. Test coverage: - New tests/scripts/test_loop_audio.py — 41 tests covering all five audio_src states, multiline ordering edge cases, parse_timeline coercion + malformed inputs, video_segment_has_audio overlap boundaries, parse_feedback (with explicit zero-score regression test), is_input_wired None vs False semantics, wrap_path separator-only wrapping, pick_existing_path filesystem fallback, autogen_label slug derivation. Docs: - reading-graph.html lists five audio states (was four). - reading-graph.schema.yaml audio_state_machine adds "(no director)" state + director_present input; state_dict moves duration_frames / duration_seconds into a notes_only section; invariants block grows with the missing-director split, type-coercion guarantee, longest- needle-wins guarantee, and zero-score preservation. - CHEATSHEET.md Audio src table grows from 4 to 5 rows. Validation: - 290 tests pass (was 249 — 41 new + 0 regressions). - ruff clean on scripts/loop_audio.py, scripts/make_card_audio.py, tests/scripts/test_loop_audio.py. - Standalone CLI re-renders run-002 card identically (same workflow, same pixels). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4739dd4203 |
feat: Easy_Utility, Easy_LoadVideo, and preview fixes (#184)
* feat(nodes): add Easy_Utility — INT → padded STRING dispatcher First mode `int_to_padded_string`: takes an INT widget (with up/down arrows) and emits a zero-padded STRING (default `001`, no `v` prefix). The downstream `koolook_versioning.resolve_version_token` prepends `v` and re-pads, so wiring this into `EasyAIPipeline.version` / `Easy_VideoCombine.version` yields the standard `vNNN` token at execution. Designed for extension: the `mode` widget shipped from day one so adding further conversions (string→int, formatting variants, etc.) doesn't rewire saved workflows. Note: the previously-attempted JS preview-resolver refactor that aimed to fix the "preview shows stale version" bug was reverted in this worktree before commit — it didn't actually fix the bug (the real cause is the JS resolver not descending into ComfyUI subgraphs) and it broke shot_name resolution in the process. The preview-resolver work is NOT in this commit; only the Python node + tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(nodes): guard version widget against pysssss .replace crash PR #180 added a STRING `version` widget to EasyAIPipeline and Easy_VideoCombine. Three workflow states leave widget.value undefined or non-string when graphToPrompt iterates widgets: - Pre-#180 workflows replay an INT (e.g. `1`) into the new STRING slot. - Widget-to-input conversion voids the value in newer LiteGraph. - Easy_VideoCombine's dict-based widgets_values omits the new key entirely on workflows saved before #180. With pysssss/ComfyUI-Custom-Scripts installed, presetText.js patches every STRING TextWidget.serializeValue to run callbacks that do `value.replace(...)` for `{variable}` substitution. Any of the states above crashes graphToPrompt and turns Run into a no-op for the whole workflow ("Cannot read properties of undefined (reading 'replace')"). Fix: trap `widget.value` reads/writes on the version widget via `Object.defineProperty` so the property is always coerced to a string regardless of caller. `configurable: true` so downstream code can still override the descriptor if it needs to. Tests: 231 passing; both JS files parse OK. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ai-pipeline): resolve subgraph version preview * feat(video): add path-aware load video node * fix(ai-pipeline): preview connected text names * fix(pr184): address review cleanup --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9f063345f7 |
feat(investigations): LTX23 audio-file lip-sync scaffold (#177) (#178)
* feat(investigations): scaffold #177 LTX23 audio-file lip-sync investigation Reorganize docs/investigations/ into numbered topic folders, each self-contained with narrative + upstream pin + patches + per-run snapshots. Add a generic sync helper that mirrors the dev-sync pattern for any investigation's third-party node modifications. Structure (new): docs/investigations/ 00_LTX23-base-1-step/README.md (rename of ltx-director-4k-transitions.md) 01_LTX23-audio-file-lipsync/ README.md -- problem, mechanism, patches, reversion, promotion upstream.json -- repo + version + pinned commit + sync target env + files patches/ -- source of truth for modified upstream files ltx_director.py -- adds `relay_overrides` multiline JSON input prompt_relay.py -- per-segment sigma per Prompt-Relay paper formula runs/ LOOP.md -- iteration protocol log.md -- rolling table (empty header for now) Sync helper: scripts/sync_investigation_patches.py <NN>_<topic> [--dry-run] - Reads upstream.json for target env var + file list - Backs up the target's current state once per day (<file>.bak.<YYYYMMDD>) - Worktree-aware: falls back to MAIN's .env via git rev-parse --git-common-dir - Prints a two-line report matching scripts/sync_to_dev.py shape - Per-investigation trigger phrase via `trigger:` key (sync-audio for #177) Env: .env.example -- documents new KOLOOK_WHATDREAMSCOST_PATH .env -- local path set Glossary updates (docs/reference/glossary.md): + Investigation folder + JSON file folder + Loop + Run + Investigation patches + sync_investigation_patches.py Closes the workflow gap from #177: MAIN is now the source of truth for investigation patches; the live install is a sync destination only. No manual file copying between iterations — `sync-audio` does it. * chore(ruff): exclude docs/investigations from lint The investigation patches under docs/investigations/*/patches/ are vendored upstream source (WhatDreamsCost-ComfyUI v1.3.2) carried so sync_investigation_patches.py can mirror them into the live install and so iterations can be diff'd against the pinned upstream commit. Linting them would force divergence from upstream and defeat that purpose. Mirrors the existing forks/ exclude precedent. Fixes the CI Lint & Smoke failure on PR #178. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: per-task automation modules + WhatDreamsCost Koolook fork Pivot docs/automations/ from "one folder per model" to "one folder per automation module" so the same diffusion model can host several independent tasks side by side. Replace the parallel docs/investigations/ tree with a fork of WhatDreamsCost-ComfyUI under forks/. Automation modules (new layout) - docs/automations/LTX-2.3/base-1step/ the existing single-stage automation, moved from docs/automations/LTX-2.3/ with its README, findings, and handoff-checklist rescoped to the task. The 4k-transitions backstory that produced the module moves here as backstory/4k-transitions.md. The earlier docs/investigations/experiments/ card PNGs/HTML land in example-cards/. - docs/automations/LTX-2.3/audio-lipsync/ the audio-file lip-sync module previously living under docs/investigations/01_…/. README, handoff, findings stub, backstory/, and runs/{LOOP,log}.md, all referencing the new fork. - docs/automations/README.md + CHEATSHEET.md updated to describe the module-per-task pivot and the new tree shape. - docs/automations/CONVENTIONS.md unchanged (cross-cutting loop contract still holds). WhatDreamsCost Koolook fork (new) - forks/whatdreamscost_koolook/versions/v1_3_2/ UPSTREAM_PIN.yaml pinned to upstream e81223a (v1.3.2), GPL-3.0 verified 2026-05-28. ltx_director.py MODIFIED — adds the relay_overrides multiline JSON widget on LTXDirector. Moved from docs/investigations/01_…/patches/. prompt_relay.py MODIFIED — per-segment σ per the Prompt-Relay paper formula σ = (L − w_eff) / (2 · √ln(1/ε)). Moved from docs/investigations/01_…/patches/. patches.py UNMODIFIED — vendored verbatim from upstream because ltx_director.py imports detect_model_type and apply_patches from it (same precedent as radiance_koolook's color_helpers.py). - Registered as LTXDirector__koolook_v1_3_2 ("LTX Director (Koolook v1.3.2)") so it coexists with an installed copy of upstream WhatDreamsCost-ComfyUI. Wired into the root __init__.py NODE_CLASS_MAPPINGS merge. - forks/forks_manifest.yaml + forks/THIRD_PARTY.md updated with the new entry (attribution, change log, license verified date, exposed node IDs). Removed - docs/investigations/ entire tree retired; its contents either moved to automation modules or to forks/. - scripts/sync_investigation_patches.py no longer needed — fork code lives in the Koolook tree and is synced via the standard dev-sync flow. - KOLOOK_WHATDREAMSCOST_PATH in .env.example same reason. - docs/investigations in pyproject.toml's ruff extend-exclude added in |
||
|
|
8447c05795 | docs: add automation loop visual overview (#163) | ||
|
|
9372032b46 |
feat(automations): workflow validator + LTX 2.3 stage-2-only docs (#170)
* feat(automations): workflow JSON validator + LTX 2.3 stage-2-only findings Adds scripts/validate_workflow.py — a stdlib-only CLI that checks a ComfyUI workflow JSON for internal consistency: link-table declarations vs node-IO references, slot index bounds and types, endpoint cross- checks, last_node_id/last_link_id bounds, and duplicate IDs. Catches the class of bugs that come from hand-editing workflow JSONs (e.g. when an agent re-wires a graph to build a slim variant from a Director workflow). Comfy's loader silently drops or mis-wires bad parts, so the cost of an undetected drift is "graph looks fine but behaves wrong" — exactly the failure mode this script blocks. Exit codes: 0 clean, 1 problems on stderr, 2 file missing or bad JSON. Docs: - docs/automations/CONVENTIONS.md §11 — when and how to run the validator, full list of checks performed. - docs/automations/LTX-2.3/findings.md — two new sections covering the spatial upscaler's architectural facts (ltx-2.3-spatial-upscaler-x2 is hardcoded x2, the brief enters only via Director > guide_data > DirectorGuide, attention scales (W * H * T)^2) plus three open hypotheses for a stage-2-only refine path. - CHANGELOG.md [Unreleased] entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(scripts): make validate_workflow.py robust against malformed JSON Addresses PR #170 review (Codex GPT-5.3) — the validator crashed with AttributeError on malformed-but-valid JSON, defeating its goal as a safe preflight tool. Two repros from the review: {"nodes":[1],"links":[],"last_node_id":0,"last_link_id":0} -> AttributeError: 'int' object has no attribute 'get' [1,2] -> AttributeError: 'list' object has no attribute 'get' Both confirmed locally. Now every shape assumption is guarded: - Top-level must be a JSON object; non-object short-circuits with one clear problem line. - 'nodes' and 'links' must be lists; non-list reported and treated as empty so subsequent checks still run. - Each node entry must be a dict; bad entries reported and dropped. - Each input/output entry must be a dict (extracted into a _sanitize_slots helper, cached per node so duplicates aren't reported when the same node is touched by multiple links). - Each link entry must be a 6-element list; malformed link entries reported and skipped without crashing later phases. - node_by_id only keys on int node IDs (non-int IDs can't match a link's int src/dst, so we don't mask bad data by accepting them). Adds tests/scripts/test_validate_workflow.py — 24 tests covering both review repros, every new guard, and a sampling of the existing consistency checks (link-to-missing-node, type mismatch, endpoint desync, duplicate IDs, last_node_id bound, CLI exit codes). Full suite: 168 passed in 1.59s. CHANGELOG entry updated to reflect the defensive behavior + test coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
092c204060 |
feat(automations): LTX Director iteration-loop framework + 4K investigation docs (#159)
* docs(investigations): kickoff for LTX Director 4K transition breakdown Captures the maintainer's problem statement (HD multi-keyframe works, 4K single-keyframe works, 4K multi-keyframe degenerates into dissolves between keyframes) and an initial conclusion about the WhatDreamsCost LTXDirector node's implementation. Traces the lineage to the Prompt Relay paper (Chen, Huang, Liu — arXiv:2604.10030) via Kijai's ComfyUI port, separates the three mechanisms the node bundles (cross-attention penalty, guide-image keyframes, audio/image preprocessing), and flags the candidates for the 4K dissolve: model-side limit at LTX sampling time vs. node-side length-independent σ in the penalty matrix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(investigations): round 2 — 2K vs 4K + two-stage upscale findings Adds the maintainer's round-2 observations: - 2K renders behave much better than 4K (segments morph instead of dissolving) — model is closer to its training distribution. - The two-stage workflow's upscale stage (stage 2) is overwriting stage 1's audio/animation: lip sync degrades and segment routing weakens. Captures the hypothesis space: - Why 2K > 4K: model prior (out of distribution at 4K) is dominant; token-grid scaling with a length-independent sigma is secondary. - Why stage 2 overwrites stage 1: KSampler denoise re-applies conditioning from scratch; if LTX Director isn't wired into stage 2 the Prompt Relay + audio_attn2 patches go missing; latent shape change between stages forces a penalty rebuild. Includes a 3-option pros/cons table (4K direct vs 2K direct vs 2-stage) and 6 investigation directions ordered cheapest-first, starting with auditing the stage-2 wiring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(investigations): round 3 — both stages run LTX Director; new prime suspect Maintainer's screenshot confirms both stages route through LTX Director, so round 2's missing-patch hypothesis is ruled out. Audited the stock LTX nodes used in stage 2 against ComfyUI source: - LTXVLatentUpsampler explicitly pop()s the noise_mask and runs a learned (EXPERIMENTAL) neural upscaler — not interpolation. - LTXVConcatAVLatent fills a missing noise_mask with ones_like (denoise fully) for the side that's missing. - LTXVCropGuides removes keyframe slots and clears keyframe_idxs. Traced the audio noise_mask end-to-end through stage 2 and found: audio waveform stays preserved (noise_mask=0), video keyframes stay pinned, but every non-keyframe video frame goes into stage 2's sampler at noise_mask=1 — i.e. fully regenerated at denoise=0.80. That's where stage 1's audio-aligned mouth shapes live, and that's what's getting thrown away. Prime suspect: denoise=0.80 is too high for "preserve stage 1" semantics — most LTX two-stage reference workflows use 0.30–0.40. Secondary suspect: the LTXVLatentUpsampler [BETA] is a learned model that can hallucinate spatially-dense detail (faces, mouths) before stage 2 even starts denoising. Includes a 4-step cheapest-first test plan starting with dropping stage 2 denoise to 0.30. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(investigations): round 4 — scheduler-shape finding + tracking plan Captures two outputs from the planning session so they're preserved for the next pickup: 1. Promotes the maintainer's empirical finding to a locked-in baseline: `linear_quadratic` scheduler at 8 steps produces the right sigma curve shape (flat plateau near sigma=1.0 for ~5 steps, then steep drop in the last 3) for both phases. Any other scheduler degrades fast motion and keyframe transitions. 2. Records the agreed tracking-system plan (BASELINE.md + log.md + per-run screenshot folder, with three 1-5 score axes matching the live trade-off: motion / audio sync / Phase-2 sharpness). Not yet scaffolded — explicit "next step" callout for whoever picks this up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(investigations): /make-card skill + PNG card renderer toolkit Per-iteration tracking card for the LTX Director 4K investigation. Solves the documentation-drift problem we hit on v01->v02 (multiline summary diverged from actual node values) by treating the saved workflow JSON as the source of truth. Adds: - scripts/make_card.py — reads a ComfyUI workflow JSON, paints a self-contained 540px PNG card via Pillow. Extracts Phase 1 run params (Format/Mult/Scheduler/Steps/Denoise), locked base (Sampler/CFG/Seed/epsilon/Audio src), scene (duration/segments with prompt/audio/keyframe checkboard), free-text BASE notes from `OVERLAY - INFO` Text Multiline, FEEDBACK from `OVERLAY - FEEDBACK` Text Multiline, and a POST-RENDER block that auto-computes render duration from JSON->newest-video mtime delta in the same folder. Layout mirrors the project's snapshot-dialogs design language (panelled sections, sky/amber/ green accents matching docs/designs/snapshot-dialogs.html). - scripts/watch_cards.py — background poller. Watches a working folder; auto-renders <stem>_card.png whenever a workflow JSON appears or is updated. Skips up-to-date PNGs. - .claude/skills/make-card/SKILL.md — /make-card slash command; globs the newest JSON in the working folder, calls make_card.py, shows the PNG inline. - .claude/skills/make-card/work-folder.txt — one-line config of the working folder path. Edit to switch projects. - docs/investigations/experiments/{baseline,combined-card,run-001-card}.html — design prototypes that fed into the PNG layout; kept as history. - docs/investigations/experiments/run-001-card.png and run-v04-card.png — sample card outputs from v03 and v04 of the workflow JSON, included for reference. Issue #151 tracks the eventual native ComfyUI custom node so this flow happens inside the graph; for now the external script + watcher is the loop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(launch): use absolute Windows python path for design-server The default `python3` runtimeExecutable fails on this Windows host (triggers the Microsoft Store install stub). Hard-coding C:\Python313\python.exe lets the preview MCP and the design-server target work on the maintainer's machine. Worktree-scoped change — main keeps the cross-platform `python3` default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(automations): docs/loop scaffold + env-var working folder + _AI subdir Promotes the LTX Director card-rendering loop from one-off scripts to a documented, project-portable automation framework. Adds `docs/automations/` as the home for AI-assisted iteration loops: - README.md — the pattern; how to start a loop for a new model - CONVENTIONS.md — cross-cutting: working folder, Text Multiline node contract (Working_Folder_PATH / OVERLAY - INFO / OVERLAY - FEEDBACK), card format, iterations log, skill, watcher, run-id rules, tool locations - LTX-2.3/README.md — entry point for the LTX 2.3 loop; per-iteration steps; full extraction table; active investigation status - LTX-2.3/handoff-checklist.md — 5-minute onboarding for a new machine or new person - LTX-2.3/findings.md — promoted findings (linear_quadratic scheduler locked-in; resolution behaviour; audio mode; open two-stage suspects) Replaces the committed `.claude/skills/make-card/work-folder.txt` with an env-var-driven path. Follows the project's existing `KOLOOK_*` convention — `.env.example` documents the new `KOLOOK_AUTOMATIONS_WORK_DIR`. Real path lives in `.env` (gitignored). No more leaking client-specific absolute paths into committed files. `scripts/make_card.py`: - Loads `.env` automatically; worktree-aware (walks the .git common-dir marker to find the main repo when run from a worktree). - Output folder priority: Working_Folder_PATH node in JSON → env var → JSON's parent dir. - Run with no args: auto-discovers newest *.json in the working folder. - Writes `_AI/card.png` + `_AI/iterations.md` inside the working folder, not the root. Lets the maintainer wipe AI artifacts or user content (JSONs/MP4s) independently. `/make-card` skill simplified to a single command — `python scripts/make_card.py` — the script handles env loading and discovery itself. baseline.html: stripped the project-specific label. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(automations): sync all path references to _AI/ + add layout diagram Audit pass after introducing _AI/ subfolder. Updated every doc and script that still referenced root-level card.png / iterations.md: - docs/automations/CONVENTIONS.md (3 spots) - docs/automations/LTX-2.3/README.md (per-iteration steps + composite) - docs/automations/LTX-2.3/handoff-checklist.md (4 spots, including the off-boarding section which conflated repo commits with working- folder artifacts) - docs/automations/LTX-2.3/findings.md (promotion criterion) - scripts/watch_cards.py (target PNG path now matches make_card.py's _AI/card.png — was writing to a stale <stem>_card.png location) Adds a clear "Where things live" section to docs/automations/README.md showing both layouts side-by-side: - the repo tree (where the tooling, skill, env-example, and per-model loop docs sit) - the per-project working-folder tree (user content at root, _AI/ subfolder for managed artifacts) So anyone picking this up can answer "what's in the repo vs. what's in my project folder" without reading prose. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(automations): add CHEATSHEET.md — one-page dense reference Compact reminder card for returning users. Structured by application context: ComfyUI · Scripts · Folder structure · Conventions · Quick-fail diagnostics. Skippable for first-timers; targets the maintainer who already knows the loop and just needs the right node names, score syntax, and command list at a glance. Linked from docs/automations/README.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(automations): add Bootstrap section to CHEATSHEET — bare-bones setup What's needed to get a fresh machine running the loop, separated from the daily-loop reference. Three checklists: - Repo side: Python + Pillow + .env config - ComfyUI side: was-node-suite + the three required Text Multiline nodes, with exact titles - First card: /make-card → expect _AI/card.png Deliberately skips model-specific setup (checkpoints, LoRAs, per-model custom nodes) — those live in each model's handoff-checklist.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(automations): skip *loop* files in auto-discovery + watcher The maintainer's workflow includes a "loop" stage in ComfyUI that composites the card next to the video and writes the result back into the working folder as `<base>_loop_<seq>.{mp4,json}`. Without a skip rule, auto-discovery treats this composited-card output as the newest input — the script then re-renders the card from yesterday's metadata. Now: any JSON or video file with `loop` in its stem (case-insensitive) is ignored by: - scripts/make_card.py — auto-discover newest JSON, newest video - scripts/watch_cards.py — polling glob Explicit invocation still works: `python scripts/make_card.py <wf_loop.json>` will render a card for that file specifically. Documents the rule in docs/automations/CONVENTIONS.md (new §7) and adds a one-line bullet to CHEATSHEET conventions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style(automations): satisfy Ruff lint — split if-on-one-line, rename l, drop os CI's Lint & Smoke step flagged 17 violations across the new scripts. Cleaned up: - scripts/make_card.py: 11 × E701 (multi-statement lines) split every `if cond: stmt` onto two lines. - scripts/make_card.py: 5 × E741 (ambiguous variable `l`) renamed to `link` in the two link-dict comprehensions and their downstream lookups. - scripts/watch_cards.py: 1 × F401 (unused `os` import). No functional change — `make_card.py` re-renders the working folder's card identically post-cleanup. Verified `ruff check` returns "All checks passed!" on both files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(automations): render time from ComfyUI log, not disk timestamps Disk mtimes don't reliably bracket the model run — the maintainer's workflow saves the workflow JSON manually after rendering, assembles QuickTime from EXRs separately, and ComfyUI's metadata bundle writes JSON + MP4 at the same instant. None of those give us actual render duration. Render time now sourced from ComfyUI's own log (`<comfyui-root>/user/comfyui.log`): - Find the latest `saving images: 100%` line → pair it with the next `Prompt executed in X` → that's the most recent EXR-producing render wall-time. - Supports both `Prompt executed in 18.21 seconds` and `Prompt executed in HH:MM:SS` log formats. - Skips trivial follow-up prompts (e.g. audio-VAE load between render and /make-card invocation). Log path resolution: 1. `KOLOOK_COMFYUI_LOG` env var (explicit override). 2. Inferred from existing `KOLOOK_COMFYUI_DEV_PATH` → `<comfyui-root>/user/comfyui.log`. Fallbacks remain in priority order: EXR sequence span (oldest to newest in the latest version subfolder) → MP4 mtime delta → "pending". Docs: - .env.example: documents the optional KOLOOK_COMFYUI_LOG. - CONVENTIONS.md: new §4 "Render duration" describing the resolution chain; section numbers renumbered. - CHEATSHEET.md: updated the "Render time —" diagnostic. Lint: ruff clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4409d3c9d8 |
feat(ai_pipeline): no_subfolders toggle + path normalisation + tooltips (#156)
* feat(ai_pipeline): no_subfolders toggle + path normalisation + tooltips
Three EasyAIPipeline improvements with a 49-case test suite pinning
the behavior so the user-reported "phantom undefined/ folder" bug
can't regress.
no_subfolders (default off): write directly into base_directory_path
with no shot_name/ai_method/vNNN subfolders appended. Base folder is
still auto-created if missing.
Paste-input hardening: _normalize_base_path strips surrounding
whitespace, one matched pair of quotes ("..." from Explorer's Copy
as path), and trailing path separators. Drive roots (C:\, n:/) are
preserved. Plus a belt-and-suspenders rstrip on the final
output_directory — fixes the symptom where a trailing backslash on
the user's pasted base path caused downstream save nodes to split
on /, stringify the empty tail as "undefined", and write into
.../undefined/... on disk.
Smart filename: empty pieces of shot_name / ai_method / vNNN are
filtered before the underscore join, so blank ai_method yields
RTX-upscale.exr (not RTX-upscale_.exr). Same rule for subfolder
build.
Tooltips on every input explain interactions (e.g. ai_method
becomes both filename segment and subfolder; enable_overwrite only
blocks an existing file, not directory).
JS preview helpers in web/ai_pipeline.js mirror the Python so the
Get output directory / file path buttons stay accurate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai_pipeline): sanitize shot_name/ai_method to prevent base-path escape
Reviewer-flagged blocker in PR #156: os.path.join's "last absolute path
wins" rule let absolute-looking shot_name or ai_method values (typos
like "/oops", or pasted full paths like "C:/Windows/junk") replace
base_directory_path entirely. The pre-#156 code had a partial defense
(.lstrip('/')) that the refactor dropped without replacing.
New _sanitize_segment helper: lstrip leading separators → splitdrive →
lstrip again. Strips leading seps BEFORE splitdrive so multi-slash
input like "///oops" doesn't get swallowed as a UNC prefix on Windows
(empirically observed — the user's intended segment name would be lost).
Applied symmetrically to directory build and filename build. Raw
shot_name kept in the return tuple for downstream label-only consumers.
Also fixes a minor JS preview mismatch at web/ai_pipeline.js: empty
base + no_subfolders=true used to render "/name.exr" while Python
returns "name.exr". Filter empties before joining the file_path.
16 new tests pin both the helper and the end-to-end escape vectors:
- TestSanitizeSegment (7 unit tests, platform-aware drive handling)
- 4 parametrised absolute shot_name escape cases
- 4 parametrised absolute ai_method escape cases
- 1 filename-leak regression
Full pytest: 102 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai_pipeline): flatten embedded separators in shot_name for filename
Maintainer-reported: with no_subfolders=true and an upstream node feeding
shot_name="ComfyUI-working-folder/LTX-Director-2K", the output was
base/ComfyUI-working-folder/LTX-Director-2K.%04d.exr — still creating
subfolders via the filename concat, defeating the toggle entirely.
Root cause: the prior fix sanitized only LEADING separators (path-escape
case). Internal separators in shot_name slipped through and became
subfolders when the filename string with embedded "/" was joined onto
output_directory by the filesystem.
Fix: filenames can't contain "/" or "\\" on any OS, so always flatten
them to "_" in the filename build. Directory build with
no_subfolders=false still uses the separators as subfolder delimiters
(intentional — users organizing into Project/v1/ai_method/vNNN want this).
With no_subfolders=true, shot_name="job/shot" now correctly produces
base/job_shot.ext (flat in base), not base/job/shot.ext.
3 new tests:
- Maintainer's exact case (no_subfolders=true + slash in shot_name)
- Belt-and-suspenders across 5 shot_name/ai_method/toggle combinations
- Verify no_subfolders=false still creates subfolders from slashes
(preserves intended use case)
Full pytest: 110 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai_pipeline): strip control chars + single-line Output Preview
Maintainer reported the file_path preview rendering as two visual lines
("n:/.../bear/...LTX-Director-2K" + newline + "_0.%04d.exr") and the
output looking like it contained a literal newline. Two contributing
causes:
1. The multi-line Output Preview widget word-wrapped long single-line
paths at the textarea edge. The data was always one logical string,
but the visual break made it look broken. Fix: set wrap="off",
whiteSpace="pre", overflowX="auto" on the input element so long
paths render on a single horizontal-scrollable line.
2. Upstream nodes (WAS Text Multiline etc.) can legitimately emit
strings with embedded \n / \r / \t — a stray paragraph break in the
widget content. Those would silently break filesystem saves and ALSO
wrap the preview at the actual newline character (not just the
textarea edge). Fix: new _strip_control_chars helper, applied at the
top of _normalize_base_path and _sanitize_segment (covers
base_directory_path, shot_name, ai_method) plus explicitly on
extension in generate_pipeline. Mirrored in JS for preview parity.
9 new tests: 6 helper unit tests, 3 end-to-end (shot_name w/ newline,
base path w/ newline, extension w/ newline).
Full pytest: 119 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai_pipeline): strict whitespace strip on extension + revert widget
Two issues, one commit:
1. Maintainer reported ComfyUI-HQ-Image-Save bailing with "Filepath
needs to end in .exr" even though our preview showed a clean
.%04d.exr suffix. Root cause: HQ-Image-Save validates via
os.path.splitext(filepath)[1].lower() != ".exr" — a single trailing
space on the extension widget (easy to acquire from a paste) makes
splitext return ".exr " with the space, which fails the strict
equality. Previous control-char strip only handled \r\n\t and
left regular spaces alone.
Fix: extension now runs through "".join(extension.split()) which
removes ALL whitespace (internal + surrounding, including unicode
whitespace and non-breaking spaces). Mirrored as cleanExtension
in JS. Also added .strip() to _sanitize_segment so shot_name /
ai_method with stray surrounding whitespace canonicalise too.
2. Reverted the Output Preview widget from the previous commit's
single-line wrap=off + horizontal-scroll layout back to the
original multi-line word-wrap (100px height, default textarea
behavior). The earlier change traded mouse-drag-selectability for
single-line display — wrong tradeoff. The path is one logical
string anyway (cleanLine strips real newlines), so visual wrap is
just cosmetic.
9 new tests:
- 6 parametrised extension whitespace cases (trailing/leading/internal
space, tab, non-breaking space) — all verify HQ-Image-Save's
splitext check passes after sanitization
- 3 parametrised shot_name surrounding-whitespace cases
Full pytest: 128 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ai_pipeline): no_subfolders keeps version folder; v### in tooltips
Maintainer-clarified semantics for the no_subfolders toggle: it should
only drop shot_name / ai_method from the directory chain, NOT the
version folder. Previously, no_subfolders=true stripped everything,
which meant versioned outputs all collided in the same flat directory —
defeating versioning whenever the toggle was on.
New behavior matrix for no_subfolders=true:
- disable_versioning=true → output_directory = base
- disable_versioning=false → output_directory = base/v###
So truly flat output (no subfolders at all) now requires BOTH toggles,
matching VFX convention. Versioned-but-no-name-subfolders is now
expressible with just no_subfolders=true, which is the common case.
Tooltip updates:
- no_subfolders: rewritten to describe new behavior
- base_directory_path: path-shape description updated to
shot_name/ai_method/[v###]
- ai_method: clarifies subfolder application is gated by no_subfolders
- version, disable_versioning: vNNN → v### (Nuke-style convention
consistent with the existing .%04d.exr default)
2 new tests:
- no_subfolders=True + disable_versioning=False → base/v###
- no_subfolders=True + disable_versioning=True → base (truly flat)
Existing tests use the disable_versioning=True default with
no_subfolders=True so they continue to assert base-only — they still
pass under the new logic since version_str="" gets filtered out of the
join.
Full pytest: 130 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai_pipeline): strip sentinel path components
* fix(ai_pipeline): resolve EasyUse get-set preview values
* docs: document ComfyUI preview button debugging
* fix(ai_pipeline): align drive segment sanitization
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
ff79b0f418 |
feat(video): add Easy_VideoCombine (#154)
* feat(video): add Easy_VideoCombine with absolute-path support Adds a Koolook subclass of VHS VideoCombine that bypasses ComfyUI's output-directory sandbox when filename_prefix is an absolute path — the VFX-pipeline missing piece for writing renders into a project tree (e.g. E:/projects/shot01/v003) instead of ComfyUI/output/. Implementation: - k_video_combine.py: ~60 LoC subclass + a pure _resolve_abs_target helper. Scoped-patches folder_paths.get_save_image_path for the duration of one combine_video() call so upstream's encoder, audio mux, batch manager, and progress bar run unchanged. No VHS code copied. Self-skips registration when VHS isn't importable (clean one-line stderr notice). - Discriminator: os.path.isabs(filename_prefix). No new input pin; pattern borrowed from spacepxl/ComfyUI-HQ-Image-Save's SaveEXR. - Optional create_path_if_missing BOOLEAN (default False) so typos surface as FileNotFoundError instead of spawning stray dirs. License audit (2026-05-16): - VHS upstream: GPL-3.0 (verified via raw LICENSE fetch). - HQ-Image-Save pattern source: MIT, copyright 2023 spacepxl. - Koolook: already GPL-3.0; both upstreams are compatible. - No source code from either upstream is incorporated. Verification: 18 pytest cases green (12 existing + 6 new covering relative passthrough, absolute resolution, create-on/off branches, trailing-separator normalization, and empty-basename rejection). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(starter-preset): pre-favourite Easy_VideoCombine Adds the new node to the shipped starter_preset.json picks so fresh installs surface it in the Kforge Labs sidebar's Nodes section without the user having to discover it via Comfy's stock search first. Pairs naturally with the existing LoadEXR / SaveEXRFrames picks from HQ-Image-Save. Existing installs with non-empty picks are unaffected (the seeder skips them by design); they can either Load -> "starter" in the Snapshot dialog or hit "+" once in the Nodes section to pull it in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(dev-sync): include k_video_combine.py in RUNTIME_PATHS Without this entry the new Easy_VideoCombine module is never copied into the dev ComfyUI install, so the node fails to register on restart with a silent ImportError. Caught when the maintainer restarted ComfyUI and couldn't find the node in the search palette. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(starter-preset): include all Koolook nodes in default picks Fresh installs should land on a populated Nodes (favorites) panel that immediately surfaces what Koolook provides — otherwise new users see only third-party picks and have no entry point into the Koolook-native workflow. Adds the 8 currently-registered Koolook node IDs to the shipped starter preset alongside the existing third-party essentials: EasyAIPipeline, EasyResize_Koolook, EasyWan22Prompt, Easy_Pattern, Easy_VideoCombine, Easy_hdr_VAE_decode, Easy_hdr_VAE_encode, KoolookLoadCameraPosesAbsolute, easy_ImageBatch Also adds Easy_VideoCombine to the README's Included Nodes table. Existing installs with non-empty picks are unaffected by the seeder (by design). To pull the Koolook defaults into an existing install, Quick Save first, then Snapshot -> Load -> "starter". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(video): reach VHS_VideoCombine via nodes.NODE_CLASS_MAPPINGS The previous `from videohelpersuite.nodes import VideoCombine` fails inside ComfyUI's loader context because VHS uses relative imports throughout (`from .logger ...`, `from .utils ...`) that only resolve when VHS is loaded through ComfyUI's importlib spec — not through a plain absolute import path. The error surfaced as No module named 'utils.install_util'; 'utils' is not a package at Koolook load time, which caused the node to self-skip with an empty NODE_CLASS_MAPPINGS — invisible in ComfyUI's search palette. Fix: look up VHS_VideoCombine through `nodes.NODE_CLASS_MAPPINGS`, which is the documented cross-pack interface. ComfyUI's loader (custom_nodes/.../__init__.py exec_module → merge into global) runs to completion for each pack before moving to the next, and VHS sorts before Koolook alphabetically in every layout we've seen, so by the time Koolook's __init__.py runs the global registry already has VHS_VideoCombine in it. Verified against ComfyUI's nodes.py load_custom_node() at load_custom_node() lines 2196 (exec_module) and 2225-2228 (merge), which guarantee this ordering. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(video): add output_directory input for split path/name editing Overloading filename_prefix for absolute paths means losing the "just type a name" affordance — once you put the path there, there's nowhere to specify just the file root. Across many renders into the same project tree this becomes tedious: every iteration retypes the whole path. Adds an optional `output_directory` STRING input. When set: - filename_prefix is treated as just the filename root; any path components are stripped (so accidental typos like "sub/clip" don't double-up under the directory). - Absolute output_directory writes directly to that location. - Relative output_directory joins under ComfyUI's output/. When output_directory is empty, the original overloaded filename_prefix behavior is preserved unchanged (back-compat for the one-field flow). Implementation: - New pure helper `_compose_prefix(filename_prefix, output_directory)` combines the two fields into one effective prefix. The existing isabs discrimination then handles sandboxed vs absolute writes uniformly downstream. - 6 new tests cover all combinations: empty/abs/relative directory, path stripping from filename_prefix, trailing-separator trim, empty-name fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(video): coerce frontend 'undefined' STRING values to empty The ComfyUI frontend serializes a STRING widget with empty default as the literal string "undefined" when left untouched (a known long-running quirk). Without defensive normalization, an untouched output_directory was treated as a real directory name and prepended to the user's path: filename_prefix = "E:/renders/clip_v003" (typed by user) output_directory = "undefined" (frontend default, not typed) _compose_prefix(...) -> "undefined/clip_v003" (relative) _resolve_abs_target -> None (not absolute) super().combine_video -> writes to <output>/undefined/clip_v003_* The QuickTime ended up at <ComfyUI>/output/undefined/<name>.mov with a spurious `undefined/` subfolder, instead of at the absolute path the user actually specified. Fix: a `_normalize_text_input()` helper coerces the literal strings "undefined" / "null" / "None" (case-insensitive, post-strip) to "". Applied to both filename_prefix and output_directory at the top of combine_video() before any path composition. 4 new tests cover the sentinels and a regression test for the exact path-composition behavior that produced the bug. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(video-fe): wire format-specific widgets for Easy_VideoCombine VHS's frontend extension (web/js/VHS.core.js:2030) gates its format-widget machinery on `nodeData?.name == "VHS_VideoCombine"` — strict-equality on the registered node ID. Our subclass uses a different ID (`Easy_VideoCombine`), so none of VHS's per-format widget rendering ran. The user saw: [VideoHelperSuite] - WARNING - Missing input for p has been set to hq on every execution (profile defaulting to "hq" for ProRes), and had no way to pick anything other than the default — because the profile dropdown was never rendered. Fix: a minimal frontend extension at web/easy_video_combine.js (~80 LoC) that replicates `addFormatWidgets` for our node name. Hooks the format dropdown's callback to add/remove per-format widgets the same way upstream does. Self-contained — does not import from VHS's module (their helpers aren't exported as ES imports). Verifies via the actual ComfyUI install after dev-sync; static preview servers can't exercise it (depends on LiteGraph's registered_node_types at runtime). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(video): strip 'undefined' from anywhere + one-shot debug log The previous normalization only caught the EXACT literal "undefined" / "null" / "None" at field-level. Adds two stronger defenses for the case where a sentinel still slips through: 1. _normalize_text_input now also strips surrounding quotes (paste- from-Explorer artifact) and handles embedded newlines by taking the first non-empty non-sentinel line (defends against multi-line Text Multiline inputs with trailing blank or junk lines). 2. New _strip_sentinel_components helper drops any path component that is exactly a sentinel string from the composed effective_ prefix — belt-and-suspenders for any path that somehow carries `\undefined\` as a literal segment by the time it reaches us. Also adds a one-shot print() of fp_raw, od_raw, normalized values, and effective_prefix at the top of combine_video. The user is seeing an undefined/ folder under their custom path despite the field-level normalization, and pure code tracing doesn't reproduce it — the log will surface what kwargs actually contains at run time so we can fix the real cause if defensive stripping doesn't already handle it. To be removed in a follow-up once root cause is known. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(video): trim node docstring + tooltips, remove debug print - Class docstring collapsed from ~20 lines to a single sentence (the frontend was showing it as the node description on right-click, cluttering the UI). - Widget tooltips trimmed to one short sentence each — same intent, no walls of text. - Debug print() added in |
||
|
|
55dab39c9b | Trim workflow context menu move list (#153) | ||
|
|
f74b30603e | Release v0.3.2 (#149) |