* 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 in943be67removed (defensive strip handled the undefined/ bug; no further diagnosis needed). - Local fp_raw / od_raw variables (only used by the debug print) collapsed back into direct call sites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(video): suppress metadata PNG + silent intermediate by default Upstream VHS writes 3 files per render when audio is connected: - <name>_NNNNN.png (first-frame sidecar with workflow metadata) - <name>_NNNNN.mov (silent intermediate) - <name>_NNNNN-audio.mov (final audio-muxed video) Both extras are controlled by hidden VHS_MetadataImage and VHS_KeepIntermediate flags in extra_pnginfo['workflow']['extra'], both default-on upstream. Result: a single 500MB render lands as three files (one needed, two clutter), nearly 2x the disk footprint and a confusing folder. Adds two optional BOOLEAN inputs (both default False on Koolook): - save_metadata_png - keep_silent_intermediate combine_video injects them into extra_pnginfo before delegating to super(), so the existing upstream flags do the actual work — no post-hoc file deletion, no fork divergence from upstream's encoder path. The extra_pnginfo dict is copied (not mutated in place) so other nodes in the workflow that reference the same dict are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * diag(video): log frame-count + audio length, restore PNG default-on Two changes to investigate a doubled-length output report: 1. Default save_metadata_png back to True (was False since946e170). The PNG sidecar carries the embedded workflow JSON — it's the only audit trail when a render comes out wrong. Removing it by default left no way to diagnose retroactively. Users who don't want the sidecar can still toggle it off explicitly; ones who do want auditability (most VFX pipelines) get it automatically. 2. One-shot diagnostic print() at the top of combine_video logging: - input image batch size + shape - audio sample count + sample rate -> seconds - frame_rate -> expected video seconds - pingpong + loop_count This pins down whether a doubled-length output is the image stream arriving already-doubled (workflow issue: ImageConcatMulti in batch mode, an upstream pingpong, etc.) vs combine_video itself doing something with the frames. To be removed in a follow-up once the current report is diagnosed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(video): restore Easy_VideoCombine widgets by name * feat(video): polish Easy_VideoCombine review workflow * test(video): fix Easy_VideoCombine pytest coverage * docs: require repo-local Python venv * fix(video): handle existing silent mux intermediates --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
70 lines
2.5 KiB
TOML
70 lines
2.5 KiB
TOML
[project]
|
|
name = "koolook"
|
|
description = "This repository provides custom nodes for ComfyUI tailored for VFX and AI image/video generation workflows"
|
|
version = "0.3.2"
|
|
license = {file = "LICENSE"}
|
|
# classifiers = [
|
|
# # For OS-independent nodes (works on all operating systems)
|
|
# "Operating System :: OS Independent",
|
|
#
|
|
# # OR for OS-specific nodes, specify the supported systems:
|
|
# "Operating System :: Microsoft :: Windows", # Windows specific
|
|
# "Operating System :: POSIX :: Linux", # Linux specific
|
|
# "Operating System :: MacOS", # macOS specific
|
|
#
|
|
# # GPU Accelerator support. Pick the ones that are supported by your extension.
|
|
# "Environment :: GPU :: NVIDIA CUDA", # NVIDIA CUDA support
|
|
# "Environment :: GPU :: AMD ROCm", # AMD ROCm support
|
|
# "Environment :: GPU :: Intel Arc", # Intel Arc support
|
|
# "Environment :: NPU :: Huawei Ascend", # Huawei Ascend support
|
|
# "Environment :: GPU :: Apple Metal", # Apple Metal support
|
|
# ]
|
|
|
|
|
|
[project.urls]
|
|
Repository = "https://github.com/malkuthro/ComfyUI-Koolook"
|
|
# Used by Comfy Registry https://registry.comfy.org
|
|
Documentation = "https://www.kforgelabs.com/comfyui-projects/comfyui-koolook/"
|
|
"Bug Tracker" = "https://github.com/malkuthro/ComfyUI-Koolook/issues"
|
|
|
|
[tool.comfy]
|
|
PublisherId = "kforgelabs"
|
|
DisplayName = "ComfyUI-Koolook"
|
|
Icon = ""
|
|
includes = []
|
|
# "requires-comfyui" = ">=1.0.0" # ComfyUI version compatibility
|
|
|
|
[tool.ruff]
|
|
extend-exclude = [".venv", "forks", "upscaler_FIX", "nuke_CAM_exporter"]
|
|
|
|
[tool.setuptools]
|
|
py-modules = [
|
|
"k_ai_pipeline",
|
|
"k_easy_image_batch",
|
|
"k_easy_pattern",
|
|
"k_easy_resize",
|
|
"k_easy_track",
|
|
"k_easy_wan22_prompt",
|
|
"k_video_combine",
|
|
"koolook_routes",
|
|
]
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
"k_easy_resize.py" = ["F841"]
|
|
|
|
[tool.pytest.ini_options]
|
|
# ``testpaths`` scopes discovery to ``tests/``. ``pythonpath`` makes the repo
|
|
# root importable so tests can ``import koolook_routes`` without an editable
|
|
# install. ``importmode=importlib`` avoids the legacy ``sys.path.prepend``
|
|
# import dance so re-running individual files works cleanly.
|
|
testpaths = ["tests"]
|
|
pythonpath = ["."]
|
|
addopts = ["--import-mode=importlib"]
|
|
|
|
[project.optional-dependencies]
|
|
# Test deps for ``pytest`` runs. CI installs via ``pip install .[test]``;
|
|
# locally use ``.venv/bin/pip install -e '.[test]'`` from the repo root.
|
|
# ``aiohttp`` is a runtime dep of ``koolook_routes`` and must be importable
|
|
# even when only a pure helper is under test.
|
|
test = ["pytest>=8", "aiohttp>=3.9"]
|