Merge pull request #26 from malkuthro/release/v0.1.0-prep

Release/v0.1.0 prep
This commit is contained in:
malkuthro
2026-04-24 16:21:33 +02:00
committed by GitHub
33 changed files with 5545 additions and 228 deletions
+2 -2
View File
@@ -10,9 +10,9 @@ assignees: []
- [ ] Node import/startup passes on clean ComfyUI
- [ ] Core workflows smoke-tested
- [ ] `third_party/forks_manifest.yaml` reviewed/updated
- [ ] `forks/forks_manifest.yaml` reviewed/updated
- [ ] `CHANGELOG.md` updated
- [ ] Release notes drafted (`docs/RELEASE_NOTES_TEMPLATE.md`)
- [ ] Release notes drafted (PR body + `CHANGELOG.md`)
## Publish
+1 -1
View File
@@ -15,7 +15,7 @@ Describe what this PR changes and why.
- [ ] I updated docs (if needed)
- [ ] I updated `CHANGELOG.md` (if needed)
- [ ] I tested node loading in ComfyUI
- [ ] I documented third-party derivations/changes in `third_party/`
- [ ] I documented third-party derivations/changes in `forks/`
- [ ] I verified no secrets/tokens are included
## Testing Notes
+13 -15
View File
@@ -6,20 +6,18 @@ The format is inspired by Keep a Changelog and SemVer.
## [Unreleased]
### Added
- Initial release/process scaffolding:
- `docs/RELEASE_WORKFLOW.md`
- `docs/REPO_AUDIT_2026-02-14.md`
- `third_party/THIRD_PARTY.md`
- `third_party/forks_manifest.yaml`
- `tools/report_mods.py`
- Contribution/release templates:
- `.github/PULL_REQUEST_TEMPLATE.md`
- `.github/ISSUE_TEMPLATE/release_checklist.md`
- `docs/RELEASE_NOTES_TEMPLATE.md`
- RunPod_Comfy scaffold was previously added during iteration and has since been moved to a private management repository.
## [0.1.0] - 2026-02-14
## [0.1.0] - 2026-04-24
### Added
- Project-level release/versioning strategy and third-party tracking scaffold.
- Fork tracking moved to `forks/` with centralized workflow documentation.
- Versioned Radiance fork package layout under `forks/radiance_koolook/versions/v1_0_1`.
- `CLAUDE.md` and `Glossary.md` to keep workflow and naming conventions explicit.
### Changed
- Root node loader now imports the versioned Radiance fork entrypoint.
- Release/checklist/template references updated to current fork-based paths.
- Introduced compact node ID override for OCIO transform: `k_easy_OCIO_v101`.
### Removed
- Legacy `ACES_workflow/radiance` tree and duplicated tracking paths under `third_party/`.
- Deprecated docs/assets/workflow artifacts removed during repository cleanup.
+36
View File
@@ -0,0 +1,36 @@
# ComfyUI-Koolook Agent Workflow
## Source-of-truth model
- MAIN repo: `ComfyUI-Koolook`
- External forks root (default): `../ComfyUI-Forks` (relative to MAIN)
- External third-party code must remain outside MAIN.
## Hard rules
- Do not vendor/copy full third-party repositories into MAIN.
- Keep MAIN limited to:
- custom Koolook nodes
- wrapper loaders under `forks/`
- fork tracking docs under `forks/`
- For third-party integrations, update:
- `forks/forks_manifest.yaml`
- `forks/THIRD_PARTY.md`
- `forks/README.md` (if workflow conventions change)
## Radiance v1 policy
- Package entrypoint: `forks/radiance_koolook/__init__.py`
- Modified nodes live in MAIN and are GitHub-tracked:
- `forks/radiance_koolook/versions/v1_0_1/nodes_hdr.py`
- `forks/radiance_koolook/versions/v1_0_1/nodes_color_management.py`
- `forks/radiance_koolook/versions/v1_0_1/nodes_dna.py`
- External sibling checkout (`../ComfyUI-Forks/radiance-v1.0.1-koolook`) is raw reference only.
- Current baseline pin for raw comparison: `f1b8ae330848fa08aba24c9d3e355cb432d3515b` (`comfyui` tag).
- Exposed node IDs must stay namespaced with version suffixes (`__koolook_v1_0_1`) to avoid collisions.
- `forks/forks_manifest.yaml` must include `source_ref` + `pinned_commit`.
## Change management
- When upgrading external forks (v2, v3, etc.), add a new wrapper/version namespace.
- Never rename existing namespaced node IDs that already appear in saved workflows.
+49
View File
@@ -0,0 +1,49 @@
# Glossary
Short, plain-English definitions for this repo workflow.
## Root `__init__.py`
- The ComfyUI entry file for this plugin.
- Its job is to import node mappings and expose `NODE_CLASS_MAPPINGS` / `NODE_DISPLAY_NAME_MAPPINGS`.
- Keep it clean and focused on loading nodes.
## Main Repo
- This repository: `ComfyUI-Koolook`.
- Stores Koolook-owned code, docs, and lightweight integration wrappers.
- Should not store full external third-party repositories.
## External Forks
- Raw upstream/fork reference repos kept outside MAIN.
- Default location is a sibling folder: `../ComfyUI-Forks`.
- Each external repo should be pinned to a version/tag + exact commit.
## Wrapper
- A small adapter module in `forks/` that loads locally tracked modified nodes and namespaces node IDs.
- Purpose: avoid collisions and keep external logic separate from root loader.
- For Radiance, the package entrypoint is `forks/radiance_koolook/__init__.py`.
## Version Folder
- A folder under `forks/radiance_koolook/versions/` that stores one maintained codebase snapshot (example: `v1_0_1`).
- Keep upstream parity metadata in `UPSTREAM_PIN.yaml` inside each version folder.
## Namespace Suffix
- A suffix added to internal node IDs (example: `__koolook_v1_0_1`).
- Prevents collisions between versions (v1, v2, upstream, etc.).
- Protects old workflows from breaking.
## `forks/forks_manifest.yaml`
- Machine-readable tracker for external dependencies.
- Contains source repo, source ref, pinned commit, and external relative path.
- This is the source of truth for reproducible setup on new machines.
## `forks/THIRD_PARTY.md`
- Human-readable attribution and notes.
- Explains what was modified and why.
## Portable Paths
- Prefer relative paths so setup works on Windows/macOS/Linux.
- Keep external references under `../ComfyUI-Forks` for portable machine setup.
## Pinned Commit
- The exact git commit hash you lock to for reproducibility.
- Use this when moving to another computer so behavior matches exactly.
+60 -2
View File
@@ -33,9 +33,67 @@ RunPod management has been moved to a separate private repository for active ope
For production usage, install a pinned stable tag/commit instead of following moving branch heads.
- Release workflow: `docs/RELEASE_WORKFLOW.md`
- Release workflow: `forks/README.md`
- Change history: `CHANGELOG.md`
- Third-party tracking: `third_party/THIRD_PARTY.md`, `third_party/forks_manifest.yaml`
- Fork tracking: `forks/THIRD_PARTY.md`, `forks/forks_manifest.yaml`
- Fork workflow guide: `forks/README.md`
### Tagging Policy (from now on)
- Repo releases: `vMAJOR.MINOR.PATCH` (example: `v0.2.0`).
- Fork code versions in paths use underscore format: `v1_0_1`, `v2_3_3`.
- Comfy node ID namespace suffixes match fork version paths: `__koolook_v1_0_1`.
- External raw checkout naming:
- pinned: `<repo>-v<version>-koolook`
- rolling upstream: `<repo>-main-upstream`
## External Fork Workflow (No Vendoring in MAIN)
This repository is the MAIN control repo. Large third-party repositories must stay outside MAIN.
- Default external forks root (relative): `../ComfyUI-Forks`
- MAIN stores only:
- wrappers under `forks/`
- provenance/tracking under `forks/`
- MAIN does not store full external trees (no vendored copies).
### Radiance Koolook Version Layout
- Package entry path: `forks/radiance_koolook/__init__.py`
- Version path (current): `forks/radiance_koolook/versions/v1_0_1/`
- Modified node source is local and tracked in MAIN:
- `forks/radiance_koolook/versions/v1_0_1/nodes_hdr.py`
- `forks/radiance_koolook/versions/v1_0_1/nodes_color_management.py`
- `forks/radiance_koolook/versions/v1_0_1/nodes_dna.py`
- External checkout is reference-only raw upstream:
- default location: `../ComfyUI-Forks/radiance-v1.0.1-koolook`
- baseline used for comparison: upstream `comfyui` tag (`f1b8ae330848fa08aba24c9d3e355cb432d3515b`) because `v1.0.1` tag is not published upstream
- Node IDs are namespaced with version suffixes (current: `__koolook_v1_0_1`) to avoid collisions.
- Each version folder should include `UPSTREAM_PIN.yaml` to keep parity with external pinned references.
### Version Pinning (Portable Across PCs/macOS)
- Track every external repo in `forks/forks_manifest.yaml` with:
- `source_ref` (tag/version)
- `pinned_commit` (exact hash used in production)
- `external_checkout.relative_path_from_forks_root`
- Keep checkout folder names versioned, e.g.:
- `radiance-v1.0.1-koolook`
- `radiance-v2.3.3-koolook`
### Machine Setup Example
From the parent directory that contains `ComfyUI-Koolook`:
```bash
mkdir -p ../ComfyUI-Forks
cd ../ComfyUI-Forks
git clone https://github.com/fxtdstudios/radiance.git radiance-v1.0.1-koolook
cd radiance-v1.0.1-koolook
git checkout f1b8ae330848fa08aba24c9d3e355cb432d3515b
```
This external folder is for upstream comparison/sync work only.
## Installation
### Via ComfyUI-Manager (Recommended)
+8 -14
View File
@@ -1,13 +1,3 @@
# Import node classes from their respective files
from .k_easy_version import Easy_Version
from .k_easy_wan22_prompt import EasyWan22Prompt
from .k_easy_resize import EasyResize
from .k_ai_pipeline import EasyAIPipeline
from .k_easy_image_batch import easy_ImageBatch # Added for new VFX batch node
# Aggregate mappings from each node file
# Assuming each node file defines its own NODE_CLASS_MAPPINGS and NODE_DISPLAY_NAME_MAPPINGS
from .k_easy_version import NODE_CLASS_MAPPINGS as version_mappings
from .k_easy_version import NODE_DISPLAY_NAME_MAPPINGS as version_display
@@ -26,14 +16,17 @@ from .k_easy_image_batch import NODE_DISPLAY_NAME_MAPPINGS as batch_display # A
from .k_easy_track import NODE_CLASS_MAPPINGS as cam_loader_mappings
from .k_easy_track import NODE_DISPLAY_NAME_MAPPINGS as cam_loader_display
# Merge all mappings
from .forks.radiance_koolook import NODE_CLASS_MAPPINGS as radiance_koolook_mappings
from .forks.radiance_koolook import NODE_DISPLAY_NAME_MAPPINGS as radiance_koolook_display
NODE_CLASS_MAPPINGS = {
**version_mappings,
**wan_mappings,
**resize_mappings,
**pipeline_mappings,
**batch_mappings, # Added for new VFX batch node
**batch_mappings,
**cam_loader_mappings,
**radiance_koolook_mappings,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -41,9 +34,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
**wan_display,
**resize_display,
**pipeline_display,
**batch_display, # Added for new VFX batch node
**batch_display,
**cam_loader_display,
**radiance_koolook_display,
}
WEB_DIRECTORY = "./web"
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
-52
View File
@@ -1,52 +0,0 @@
# Release Notes Template
Use this template when publishing a GitHub Release for a stable tag.
## Title
`vX.Y.Z`
## Summary
One short paragraph explaining what changed and why users should upgrade.
## ✅ Tested Matrix
- ComfyUI commit/tag:
- Python version:
- CUDA version:
- GPU(s):
- OS:
## Added
-
## Changed
-
## Fixed
-
## Breaking Changes
- None / list here
## Known Issues
-
## Upgrade Notes
- Any migration steps for users
- Any node behavior changes to watch for
## Install
Production recommendation: install by tag/commit, not moving branch heads.
```bash
git clone --branch vX.Y.Z https://github.com/malkuthro/ComfyUI-Koolook.git
```
-46
View File
@@ -1,46 +0,0 @@
# Release Workflow (Stable + Dev)
This project supports production users, so releases should be predictable.
## Branches
- `main` → active development
- `release/stable` → tested commits only (optional but recommended)
If you do not want two long-lived branches yet, keep using `main` and only publish tested tags.
## Versioning (SemVer)
Use tags in the format `vMAJOR.MINOR.PATCH`:
- `v1.0.0` = first stable release
- `v1.0.1` = bugfixes only
- `v1.1.0` = new backward-compatible features
- `v2.0.0` = breaking changes
## Minimal Release Checklist
1. Confirm node load on a clean ComfyUI install
2. Confirm key workflows still run
3. Update `CHANGELOG.md`
4. Create and push annotated tag:
```bash
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git push origin vX.Y.Z
```
5. Publish GitHub release notes (what changed, tested matrix, known issues)
- use `docs/RELEASE_NOTES_TEMPLATE.md`
## Production Install Recommendation
Users in production should install by tag (not moving branch heads).
Example:
```bash
git clone --branch v1.0.0 https://github.com/malkuthro/ComfyUI-Koolook.git
```
Or in ComfyUI Manager, pin to a known stable tag/commit.
-29
View File
@@ -1,29 +0,0 @@
# Repo Audit — 2026-02-14
## Summary
The repository is functional but currently mixes multiple concerns (custom nodes, workflow assets, local environment artifacts, and experiments). This increases maintenance risk for production users.
## Findings
1. **Mixed-purpose layout**
- Root contains node source, workflows, utility projects, docs, and assets.
- Recommendation: gradually separate into clear areas (e.g. `nodes/`, `workflows/`, `runpod/`, `docs/`).
2. **Environment artifact present (`.venv/`)**
- `.venv` is ignored by `.gitignore`, but still exists locally.
- Recommendation: keep untracked; never commit virtual env artifacts.
3. **Release policy not explicit**
- No formal SemVer/tag workflow documented.
- Addressed by adding `docs/RELEASE_WORKFLOW.md` and `CHANGELOG.md`.
4. **Third-party modifications not centrally tracked**
- Some code is derived/modified from external repos.
- Addressed by adding `third_party/THIRD_PARTY.md` and `third_party/forks_manifest.yaml`.
## Immediate Next Steps
1. Fill `third_party/forks_manifest.yaml` for each external-derived node.
2. Publish first stable tag after verification.
3. Add CI release checks later (optional phase).
-7
View File
@@ -1,7 +0,0 @@
# RunPod Management Notice
RunPod setup, image build/deploy scaffolding, and recipe iteration were moved to a private repository.
This public repository stays focused on ComfyUI custom node/plugin code.
If you need RunPod runtime operations, use the private management repository.
+62
View File
@@ -0,0 +1,62 @@
# Forks Workflow
This folder is the single source for all fork tracking metadata inside MAIN.
## Purpose
- Keep modified forked code in MAIN under versioned paths (for GitHub tracking).
- Keep raw external checkouts outside MAIN under `../ComfyUI-Forks` (for upstream reference/sync).
- Keep all fork metadata in one place:
- `forks/forks_manifest.yaml`
- `forks/THIRD_PARTY.md`
## Folder Layout in MAIN
- `forks/radiance_koolook/__init__.py` -> package entrypoint used by root `__init__.py`
- `forks/radiance_koolook/versions/v1_0_1/` -> local modified node sources
- `forks/radiance_koolook/versions/<version>/UPSTREAM_PIN.yaml` -> exact upstream parity pin
## External Folder Naming (outside MAIN)
Root: `../ComfyUI-Forks`
- Pinned baseline checkout:
- `radiance-v1.0.1-koolook`
- Rolling upstream checkout:
- `radiance-main-upstream`
These names are referenced from `forks/forks_manifest.yaml`.
## Referencing System
For each fork entry in `forks/forks_manifest.yaml`:
- `source_repo` = canonical upstream URL
- `source_ref` = release/tag (or baseline descriptor)
- `pinned_commit` = exact reproducible commit hash
- `external_checkout.relative_path_from_forks_root` = pinned raw reference folder
- `external_checkout.upstream_tracking_path_from_forks_root` = rolling upstream folder
- `local_paths` = files tracked in MAIN
## Tagging Policy (from now on)
- MAIN repo tags use SemVer: `vMAJOR.MINOR.PATCH`.
- Local fork version folders use underscore variant of the same version:
- `v1_0_1`, `v2_3_3`, etc.
- Node mapping namespace suffixes must match folder versions:
- `__koolook_v1_0_1`, `__koolook_v2_3_3`, etc.
- Keep `source_ref` + `pinned_commit` in `forks/forks_manifest.yaml` and `UPSTREAM_PIN.yaml`.
## Upgrade Flow Example (Radiance)
1. Compare current local version (`forks/radiance_koolook/versions/v1_0_1`) with pinned raw baseline (`../ComfyUI-Forks/radiance-v1.0.1-koolook`).
2. Pull latest upstream in `../ComfyUI-Forks/radiance-main-upstream`.
3. Create a new local version folder in MAIN, e.g. `forks/radiance_koolook/versions/v2_3_3`.
4. Port/validate modifications.
5. Namespace node IDs for that version.
6. Add new entry/update metadata in `forks/forks_manifest.yaml`.
## Rule of Thumb
- If it is modified production code -> keep in MAIN under `forks/.../versions/...`
- If it is raw upstream reference -> keep outside MAIN under `../ComfyUI-Forks`
+24
View File
@@ -0,0 +1,24 @@
# Third-Party Code & Attribution
Use this file to track external repositories, licenses, and local modifications.
## Entry Template
- Name:
- Upstream repo URL:
- Upstream commit/tag used:
- License:
- Local path(s):
- What changed locally:
- Why changed:
- Last reviewed:
## Notes
- Keep this file updated when syncing upstream changes.
- If a node is heavily modified, document behavior differences clearly for users.
- For external fork variants, track exact upstream tag and pinned commit in `forks_manifest.yaml`.
- Freeze baseline first, then namespace node IDs before introducing newer upstream variants.
- Keep paths relative for portability (`../ComfyUI-Forks` by default).
- Keep only wrappers/manifests in MAIN; do not vendor large third-party trees here.
- Keep modified node source files in MAIN when you want full GitHub tracking and one-repo commits.
+1
View File
@@ -0,0 +1 @@
"""Forked/variant node packages managed inside this repository."""
+63
View File
@@ -0,0 +1,63 @@
version: 1
updated_at: "2026-04-24"
external_forks:
root_relative_to_main: "../ComfyUI-Forks"
root_env_var: "KOLOOK_FORKS_DIR"
notes: "Keep raw upstream/fork clones here. Modified working nodes live in MAIN and are tracked by this repository."
naming_convention:
pinned_baseline: "<repo>-v<version>-koolook"
rolling_upstream: "<repo>-main-upstream"
entries:
- id: "radiance_koolook_v1_0_1_baseline"
name: "Radiance Koolook v1.0.1 baseline"
source_repo: "https://github.com/fxtdstudios/radiance.git"
source_ref: "comfyui (closest public baseline; v1.0.1 tag not found upstream)"
pinned_commit: "f1b8ae330848fa08aba24c9d3e355cb432d3515b"
external_checkout:
relative_path_from_forks_root: "radiance-v1.0.1-koolook"
upstream_tracking_path_from_forks_root: "radiance-main-upstream"
default_branch: "koolook/v1-multiframe"
local_paths:
- "forks/radiance_koolook/versions/v1_0_1/nodes_hdr.py"
- "forks/radiance_koolook/versions/v1_0_1/nodes_color_management.py"
- "forks/radiance_koolook/versions/v1_0_1/nodes_dna.py"
- "forks/radiance_koolook/versions/v1_0_1/__init__.py"
- "forks/radiance_koolook/versions/v1_0_1/UPSTREAM_PIN.yaml"
- "forks/radiance_koolook/__init__.py"
status: "modified"
sync_state: "local-modified-from-upstream"
license: "to_verify_at_source_ref"
notes: "Local modifications include single-frame to multi-frame behavior changes and are committed in MAIN. External checkout is kept only as raw upstream reference."
- id: "easy_resize_inspiration"
name: "Easy Resize (inspired component)"
source_repo: "https://github.com/kijai/ComfyUI-KJNodes"
source_ref: "unknown"
local_paths:
- "k_easy_resize.py"
status: "modified"
sync_state: "needs-upstream-reference"
license: "unknown"
notes: "Inspired by Resize Image V2; verify exact upstream commit/license metadata."
- id: "promptjson_integration_reference"
name: "PromptJSON interoperability reference"
source_repo: "https://github.com/NeuralSamurAI/ComfyUI-PromptJSON"
source_ref: "n/a"
local_paths: []
status: "reference_only"
sync_state: "n/a"
license: "unknown"
notes: "No vendored code; integration reference in docs/workflows."
- id: "wan22_parameter_reference"
name: "Wan 2.2 parameter docs reference"
source_repo: "https://alidocs.dingtalk.com/i/nodes/EpGBa2Lm8aZxe5myC99MelA2WgN7R35y"
source_ref: "n/a"
local_paths:
- "config.json"
- "k_easy_wan22_prompt.py"
status: "reference_only"
sync_state: "manual"
license: "unknown"
notes: "Prompt parameter taxonomy inspired by external docs; no direct vendored code tracked yet."
+12
View File
@@ -0,0 +1,12 @@
# Radiance Koolook
Versioned local fork of Radiance nodes.
- Package entrypoint: `forks/radiance_koolook/__init__.py`
- Version folders: `forks/radiance_koolook/versions/<version>/`
- Each version folder keeps:
- modified node files tracked in MAIN
- `UPSTREAM_PIN.yaml` for parity with pinned external raw checkout
Current active version:
- `v1_0_1`
+12
View File
@@ -0,0 +1,12 @@
"""Radiance Koolook package entrypoint for versioned node sets."""
from .versions.v1_0_1 import NODE_CLASS_MAPPINGS as V1_0_1_NODE_CLASS_MAPPINGS
from .versions.v1_0_1 import NODE_DISPLAY_NAME_MAPPINGS as V1_0_1_NODE_DISPLAY_NAME_MAPPINGS
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
NODE_CLASS_MAPPINGS.update(V1_0_1_NODE_CLASS_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(V1_0_1_NODE_DISPLAY_NAME_MAPPINGS)
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
@@ -0,0 +1 @@
"""Versioned Radiance Koolook node sets."""
@@ -0,0 +1,6 @@
source_repo: "https://github.com/fxtdstudios/radiance.git"
source_ref: "comfyui (closest public baseline; v1.0.1 tag not found upstream)"
pinned_commit: "f1b8ae330848fa08aba24c9d3e355cb432d3515b"
external_checkout_relative_to_forks_root: "radiance-v1.0.1-koolook"
upstream_tracking_relative_to_forks_root: "radiance-main-upstream"
external_work_branch: "koolook/v1-multiframe"
@@ -0,0 +1,52 @@
"""
Radiance Koolook v1.0.1 mappings.
Loads locally tracked modified nodes and namespaces mapping IDs.
"""
from .nodes_hdr import (
NODE_CLASS_MAPPINGS as HDR_NODE_CLASS_MAPPINGS,
NODE_DISPLAY_NAME_MAPPINGS as HDR_NODE_DISPLAY_NAME_MAPPINGS,
)
from .nodes_color_management import (
NODE_CLASS_MAPPINGS as COLOR_NODE_CLASS_MAPPINGS,
NODE_DISPLAY_NAME_MAPPINGS as COLOR_NODE_DISPLAY_NAME_MAPPINGS,
)
NAMESPACE_SUFFIX = "__koolook_v1_0_1"
DISPLAY_SUFFIX = " (Koolook v1.0.1)"
# Optional compact IDs for frequently used nodes.
# If a node is listed here, this ID is used instead of the default
# "<original_id>__koolook_v1_0_1" format.
SHORT_ID_OVERRIDES = {
"RadianceOCIOColorTransformV2": "k_easy_OCIO_v101",
}
def _namespace_mappings(class_mappings, display_mappings):
namespaced_classes = {}
namespaced_display = {}
for node_id, node_cls in class_mappings.items():
namespaced_id = SHORT_ID_OVERRIDES.get(node_id, f"{node_id}{NAMESPACE_SUFFIX}")
namespaced_classes[namespaced_id] = node_cls
base_display = display_mappings.get(node_id, node_id)
namespaced_display[namespaced_id] = f"{base_display}{DISPLAY_SUFFIX}"
return namespaced_classes, namespaced_display
_ALL_CLASSES = {}
_ALL_DISPLAY = {}
_ALL_CLASSES.update(HDR_NODE_CLASS_MAPPINGS)
_ALL_CLASSES.update(COLOR_NODE_CLASS_MAPPINGS)
_ALL_DISPLAY.update(HDR_NODE_DISPLAY_NAME_MAPPINGS)
_ALL_DISPLAY.update(COLOR_NODE_DISPLAY_NAME_MAPPINGS)
NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS = _namespace_mappings(
_ALL_CLASSES, _ALL_DISPLAY
)
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
@@ -0,0 +1,240 @@
"""
FXTD Radiance - Color Management (OCIO)
Professional Color Management using OpenColorIO.
"""
import torch
import os
import sys
import numpy as np
# Try importing PyOpenColorIO
try:
import PyOpenColorIO as OCIO
except ImportError:
OCIO = None
class RadianceOCIOColorTransformV2:
@staticmethod
def _list_colorspaces(config):
names = []
try:
if hasattr(config, "getNumColorSpaces") and hasattr(config, "getColorSpaceNameByIndex"):
names = [config.getColorSpaceNameByIndex(i) for i in range(config.getNumColorSpaces())]
elif hasattr(config, "getColorSpaces"):
names = [cs.getName() if hasattr(cs, "getName") else getattr(cs, "name", "") for cs in config.getColorSpaces()]
except Exception:
pass
return [n for n in names if n]
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"config_file": ("STRING", {"default": "", "multiline": True}),
"input_space": ("STRING", {"default": "ACES - ACEScg"}),
"output_space": ("STRING", {"default": "Output - sRGB"}),
"direction": (["Forward", "Inverse"],),
}
}
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("image", "debug_info")
FUNCTION = "transform"
CATEGORY = "FXTD Studios/Radiance/Color Management"
def transform(self, image, config_file, input_space, output_space, direction):
if OCIO is None:
print("PyOpenColorIO not found inside Radiance nodes. Please install it to use this node: pip install PyOpenColorIO")
return (image, "ERROR: PyOpenColorIO not installed")
try:
# Sanitize config path (trim whitespace/quotes/newlines)
config_file = (config_file or "").strip().strip('"').strip("'")
# Load config
if config_file and os.path.exists(config_file):
config = OCIO.Config.CreateFromFile(config_file)
config_source = config_file
else:
if config_file:
return (image, f"ERROR: config file not found: {config_file}")
try:
config = OCIO.Config.CreateFromEnv()
config_source = "OCIO env"
except Exception:
return (image, "ERROR: no config_file provided and OCIO env not set")
# Verify spaces exist and report available ones
available_spaces = self._list_colorspaces(config)
if input_space not in available_spaces:
return (image, f"ERROR: input colorspace '{input_space}' not found in config ({config_source}). Available: {', '.join(available_spaces[:30])}")
if output_space not in available_spaces:
return (image, f"ERROR: output colorspace '{output_space}' not found in config ({config_source}). Available: {', '.join(available_spaces[:30])}")
# Create Transform
if direction == "Forward":
transform = OCIO.ColorSpaceTransform(src=input_space, dst=output_space)
else:
transform = OCIO.ColorSpaceTransform(src=output_space, dst=input_space)
processor = config.getProcessor(transform)
cpu = processor.getDefaultCPUProcessor()
# Process Image
# Supports batch x H x W x C; preserves alpha and dtype/device
orig_dtype = image.dtype
img_np = np.ascontiguousarray(image.detach().cpu().numpy())
if img_np.ndim != 4 or img_np.shape[-1] < 3:
return (image, "WARN: input tensor is not (batch,H,W,C)")
batch, h, w, c = img_np.shape
alpha_np = None
if c > 3:
alpha_np = img_np[..., 3:].copy()
img_np = img_np[..., :3]
# Flatten all frames and process in one call
flat = img_np.reshape(-1, 3).astype(np.float32)
cpu.applyRGB(flat)
result_np = flat.reshape(batch, h, w, 3)
if alpha_np is not None:
result_np = np.concatenate([result_np, alpha_np], axis=-1)
result_t = torch.from_numpy(result_np)
if result_t.dtype != orig_dtype:
result_t = result_t.to(orig_dtype)
debug_info = f"Config: {config_source} | Batch frames processed: {batch}, shape: {list(result_t.shape)}"
return (result_t, debug_info)
except Exception as e:
print(f"[Radiance] OCIO Error: {e}")
return (image, f"ERROR: {e}")
class RadianceLogCurveDecode:
"""
Logarithmic to Linear conversion for specific camera curves.
Implements exact mathematical formulas for industry standard curves.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"curve": ([
"ARRI LogC3",
"ARRI LogC4",
"Sony S-Log3",
"RED Log3G10",
"Canon Log3",
"Panasonic V-Log"
],),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "decode"
CATEGORY = "FXTD Studios/Radiance/Color Management"
def decode(self, image, curve):
t = image.clone()
# ---------------------------------------------------------
# ARRI LogC3 (Alexa Wide Gamut)
# ---------------------------------------------------------
if curve == "ARRI LogC3":
cut = 0.010591
a = 5.555556
b = 0.052272
c = 0.247190
d = 0.385537
e = 5.367655
f = 0.092809
mask = t > e * cut + f
t[mask] = (torch.pow(10.0, (t[mask] - d) / c) - b) / a
t[~mask] = (t[~mask] - f) / e
# ---------------------------------------------------------
# ARRI LogC4 (Alexa 35 REVEAL Color Science)
# ---------------------------------------------------------
elif curve == "ARRI LogC4":
# Official ARRI LogC4 Linearization
# Linear = (2^(14.0056 * t + 0.55479) - 1.46788) / 8.68337 for t > 0.092864
# Linear = (t - 0.092864) / 5.555556 for t <= 0.092864
c4_mask = t > 0.092864
t[c4_mask] = (torch.pow(2.0, 14.0056 * t[c4_mask] + 0.55479) - 1.46788) / 8.68337
t[~c4_mask] = (t[~c4_mask] - 0.092864) / 5.555556
# ---------------------------------------------------------
# Sony S-Log3
# ---------------------------------------------------------
elif curve == "Sony S-Log3":
# S-Log3 Decoding
# x = ((y * 1023 - 95) / (4 * 261.5)) - 0.01 (Linear part)
# x = (0.19) * 10^((y * 1023 - 420) / 261.5) - 0.01 (Log part)
cut_v = 171.2102946929 / 1023.0
mask_s = t >= cut_v
t[mask_s] = torch.pow(10.0, ((t[mask_s] * 1023.0 - 420.0) / 261.5)) * 0.18 + (0.18 * 0.01 - 0.01) # Refined
# Actually standard formula: x = (0.18 + 0.01) * ... - 0.01. Note 0.19 = 0.18+0.01
t[mask_s] = (0.19) * torch.pow(10.0, ((t[mask_s] * 1023.0 - 420.0) / 261.5)) - 0.01
t[~mask_s] = ((t[~mask_s] * 1023.0 - 95.0) / (4.0 * 261.5)) - 0.01
# ---------------------------------------------------------
# RED Log3G10
# ---------------------------------------------------------
elif curve == "RED Log3G10":
# RED Log3G10
# A = 0.224282, B = 155.975327, C = 0.01
# Linear = (10^((t - A) / 0.25) - 1) / B - C
# Note: 0.25 comes from the log encoding side (B in encoding).
# The encoding B is 0.25.
t = (torch.pow(10.0, (t - 0.224282) / 0.25) - 1.0) / 155.975327 - 0.01
# ---------------------------------------------------------
# Canon Log3
# ---------------------------------------------------------
elif curve == "Canon Log3":
# Canon Log 3 Decoding
# if t < 0.073059361:
# x = (t - 0.073059361) / 5.765765766
# else:
# x = (10^((t - 0.073059361)/0.529136) - 1) / 10.1596
mask_cl = t < 0.073059361
t[mask_cl] = (t[mask_cl] - 0.073059361) / 5.765765766
t[~mask_cl] = (torch.pow(10.0, (t[~mask_cl] - 0.073059361) / 0.529136) - 1.0) / 10.1596
# ---------------------------------------------------------
# Panasonic V-Log
# ---------------------------------------------------------
elif curve == "Panasonic V-Log":
# Panasonic V-Log Decoding
# x = (y - 0.125) / 5.625 (y < 0.181)
# x = 10^((y - 0.598206) / 0.241514) - 0.008761 (y >= 0.181)
mask_v = t < 0.181
t[mask_v] = (t[mask_v] - 0.125) / 5.625
t[~mask_v] = torch.pow(10.0, (t[~mask_v] - 0.598206) / 0.241514) - 0.008761
return (t,)
NODE_CLASS_MAPPINGS = {
"RadianceOCIOColorTransformV2": RadianceOCIOColorTransformV2,
"RadianceLogCurveDecode": RadianceLogCurveDecode
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RadianceOCIOColorTransformV2": "Radiance OCIO Color Transform v2",
"RadianceLogCurveDecode": "Radiance Log Curve Decode"
}
@@ -0,0 +1,256 @@
import torch
import numpy as np
import json
import zlib
import struct
import time
from typing import Dict, Any, Tuple, Optional
# Get version from package
try:
from . import __version__ as FXTD_VERSION
except ImportError:
FXTD_VERSION = "3.2.0"
class FXTD_DigitalDNA:
"""
Core engine for FXTD Digital DNA (Signature Architecture).
Embeds invisible, lossless metadata into 32-bit floating point images.
"""
# Magic header to identify FXTD DNA (32 bits)
# "FXTD" in ASCII binary: 01000110 01011000 01010100 01000100
MAGIC_HEADER = "01000110010110000101010001000100"
VERSION = "1.0"
@staticmethod
def _float_to_int_bits(f_val):
"""Reinterpret float32 bits as int32."""
s = struct.pack('>f', f_val)
return struct.unpack('>I', s)[0]
@staticmethod
def _int_bits_to_float(i_val):
"""Reinterpret int32 bits as float32."""
s = struct.pack('>I', i_val)
return struct.unpack('>f', s)[0]
@classmethod
def encode(cls, image: torch.Tensor, metadata: Dict[str, Any]) -> torch.Tensor:
"""
Embed metadata into the image tensor's LSBs.
Returns a new tensor with the embedded signature.
"""
# Ensure we work with CPU numpy for bit manipulation
device = image.device
img_np = image.detach().cpu().numpy().astype(np.float32)
# Prepare payload
payload = {
"dna_ver": cls.VERSION,
"data": metadata
}
json_str = json.dumps(payload)
# Compress to save space and obfuscate
compressed = zlib.compress(json_str.encode('utf-8'))
# Convert bytes to bit string
bits = ''.join(f'{byte:08b}' for byte in compressed)
# Add length prefix (32 bits) so we know when to stop reading
length_bin = f'{len(bits):032b}'
# Full bit stream: Header + Length + Data
full_stream = cls.MAGIC_HEADER + length_bin + bits
# Check capacity
total_pixels = img_np.size
if len(full_stream) > total_pixels:
print(f"[FXTD DNA] Error: Image too small for signature ({len(full_stream)} bits > {total_pixels} pixels)")
return image
# Embed bits
# Flatten image for easy indexing
flat_img = img_np.reshape(-1)
# Create a writable copy for modification
# We manually manipulate the bits by viewing as int32
# Note: In numpy, view() shares memory, but we want to modify values via int representation
# Ideally, we do this element-wise or vectorized. Vectorized is faster.
# Vectorized approach:
# 1. Take the first N pixels where N is stream length
# 2. Convert to int representation
# 3. Set LSB to match stream
# 4. Convert back to float
stream_len = len(full_stream)
target_indices = np.arange(stream_len)
# Get float values
float_vals = flat_img[target_indices]
# Reinterpret as int32 (read-only usually if checking view)
# We use frombuffer for safe reinterpretation
int_vals = np.frombuffer(float_vals.tobytes(), dtype=np.uint32).copy()
# Prepare bit mask (clear LSB)
# 0xFFFFFFFE = ...11111110
clear_mask = np.uint32(0xFFFFFFFE)
int_vals &= clear_mask
# Create additive bits from stream
# Map '1' -> 1, '0' -> 0
stream_bits = np.array([int(b) for b in full_stream], dtype=np.uint32)
# Combine
int_vals |= stream_bits
# Convert back to float
new_floats = np.frombuffer(int_vals.tobytes(), dtype=np.float32)
# Apply back to flattened image
flat_img[target_indices] = new_floats
# Reshape back to original
result_np = flat_img.reshape(img_np.shape)
return torch.from_numpy(result_np).to(device)
@classmethod
def decode(cls, image: torch.Tensor) -> Tuple[bool, Optional[Dict[str, Any]]]:
"""
Attempt to read FXTD DNA from an image.
Returns (is_valid, metadata_dict).
"""
img_np = image.detach().cpu().numpy().astype(np.float32)
flat_img = img_np.reshape(-1)
# Read header bits first
header_len = len(cls.MAGIC_HEADER)
if flat_img.size < header_len:
return False, None
# Get raw bits from first chunk
# Reinterpret logic
check_len = header_len + 32 # Header + Length
# Safety check
if flat_img.size < check_len:
return False, None
# Extract bits
float_vals = flat_img[:check_len]
int_vals = np.frombuffer(float_vals.tobytes(), dtype=np.uint32)
# Get LSBs
lsbs = int_vals & 1
# Reconstruct string
extracted_bits = "".join(str(b) for b in lsbs)
# Check specific magic header
extracted_header = extracted_bits[:header_len]
if extracted_header != cls.MAGIC_HEADER:
return False, None
# Read length
length_bin = extracted_bits[header_len:header_len+32]
try:
payload_len = int(length_bin, 2)
except ValueError:
return False, None
# Check if we have enough pixels for the full payload
total_len = header_len + 32 + payload_len
if flat_img.size < total_len:
return False, None
# Now read the full payload
# Optimization: We already read the first chunk, but easier to read all needed now
float_payload = flat_img[header_len+32 : total_len]
int_payload = np.frombuffer(float_payload.tobytes(), dtype=np.uint32)
payload_lsbs = int_payload & 1
payload_bits = "".join(str(b) for b in payload_lsbs)
# Convert bits back to bytes
try:
byte_array = bytearray()
for i in range(0, len(payload_bits), 8):
byte = payload_bits[i:i+8]
if len(byte) == 8:
byte_array.append(int(byte, 2))
# Decompress
json_str = zlib.decompress(byte_array).decode('utf-8')
data = json.loads(json_str)
return True, data
except Exception as e:
# print(f"DNA Decode Error: {e}")
return False, None
class FXTDSignatureMixin:
"""
Mixin for ComfyUI Nodes to easily sign their output.
"""
def sign_image(self, image: torch.Tensor, extra_metadata: Dict[str, Any] = None) -> torch.Tensor:
"""Sign the image with this node's signature."""
# Basic node info
node_class = self.__class__.__name__
metadata = {
"created_by": "FXTD Studio",
"node": node_class,
"timestamp": time.time(),
"fxtd_ver": FXTD_VERSION
}
if extra_metadata:
metadata.update(extra_metadata)
return FXTD_DigitalDNA.encode(image, metadata)
# --------------------------------------------------------------------------------
# NODES
# --------------------------------------------------------------------------------
class FXTDDNAReader:
"""
Node to verify and read FXTD Digital DNA from images.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
}
}
RETURN_TYPES = ("BOOLEAN", "STRING")
RETURN_NAMES = ("is_signed", "signature_data")
FUNCTION = "read_dna"
CATEGORY = "FXTD Studios/Radiance/Utilities"
DESCRIPTION = "Verifies if an image contains a valid FXTD Digital DNA signature."
def read_dna(self, image):
# Handle batch - just check the first image for now
# Ideally we check all, but metadata usually uniform across batch
if image.dim() == 4:
tgt_image = image[0]
else:
tgt_image = image
is_valid, data = FXTD_DigitalDNA.decode(tgt_image)
if is_valid:
info_str = json.dumps(data.get("data", {}), indent=2)
print(f"[FXTD DNA] Valid Signature Found: {info_str}")
else:
info_str = "No valid signature found."
return (is_valid, info_str)
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "koolook"
description = "This repository provides custom nodes for ComfyUI tailored for VFX and AI image/video generation workflows"
version = "1.0.1"
version = "0.1.0"
license = {file = "LICENSE"}
# classifiers = [
# # For OS-independent nodes (works on all operating systems)
-19
View File
@@ -1,19 +0,0 @@
# Third-Party Code & Attribution
Use this file to track external repositories, licenses, and local modifications.
## Entry Template
- Name:
- Upstream repo URL:
- Upstream commit/tag used:
- License:
- Local path(s):
- What changed locally:
- Why changed:
- Last reviewed:
## Notes
- Keep this file updated when syncing upstream changes.
- If a node is heavily modified, document behavior differences clearly for users.
-35
View File
@@ -1,35 +0,0 @@
version: 1
updated_at: "2026-02-14"
entries:
- id: "easy_resize_inspiration"
name: "Easy Resize (inspired component)"
source_repo: "https://github.com/kijai/ComfyUI-KJNodes"
source_ref: "unknown"
local_paths:
- "k_easy_resize.py"
status: "modified"
sync_state: "needs-upstream-reference"
license: "unknown"
notes: "Inspired by Resize Image V2; verify exact upstream commit/license metadata."
- id: "promptjson_integration_reference"
name: "PromptJSON interoperability reference"
source_repo: "https://github.com/NeuralSamurAI/ComfyUI-PromptJSON"
source_ref: "n/a"
local_paths: []
status: "reference_only"
sync_state: "n/a"
license: "unknown"
notes: "No vendored code; integration reference in docs/workflows."
- id: "wan22_parameter_reference"
name: "Wan 2.2 parameter docs reference"
source_repo: "https://alidocs.dingtalk.com/i/nodes/EpGBa2Lm8aZxe5myC99MelA2WgN7R35y"
source_ref: "n/a"
local_paths:
- "config.json"
- "k_easy_wan22_prompt.py"
status: "reference_only"
sync_state: "manual"
license: "unknown"
notes: "Prompt parameter taxonomy inspired by external docs; no direct vendored code tracked yet."
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Simple report for third-party modification tracking.
Reads third_party/forks_manifest.yaml and prints a short summary for maintainers.
Reads forks/forks_manifest.yaml and prints a short summary for maintainers.
"""
from __future__ import annotations
@@ -17,7 +17,7 @@ except Exception:
def main() -> int:
repo_root = Path(__file__).resolve().parents[1]
manifest_path = repo_root / "third_party" / "forks_manifest.yaml"
manifest_path = repo_root / "forks" / "forks_manifest.yaml"
if not manifest_path.exists():
print(f"Manifest not found: {manifest_path}")
@@ -1,3 +0,0 @@
https://arxiv.org/html/2504.14899v1
https://github.com/alibaba-damo-academy/Uni3C