studio packs: vendor top ComfyUI node packs as first-class hanzo packages
Add a comfy->studio import shim so upstream ComfyUI custom nodes run unmodified against this fork's renamed core, then vendor the popular packs (Impact-Pack/Subpack, IPAdapter_plus, controlnet_aux, Ultimate SD Upscale, Frame-Interpolation, VideoHelperSuite, rgthree, Custom-Scripts, KJNodes, cg-use-everywhere, SAM2, segment_anything) pinned to immutable commits and reproducibly rebuilt in the image. - studio_compat: one meta-path finder aliasing comfy*->studio* and mirroring Comfy*->Studio* API classes (the deferred phase-4 of scripts/rename_modules.py, done with the 3.12 find_spec API); activated by one import at top of main.py. - nodes.py: honor the upstream `comfy_entrypoint` V3 extension name too. - custom_nodes/hanzo-packs.txt: pinned pack manifest (SHAs + SPDX licenses). - custom_nodes-requirements.txt + scripts/install_custom_nodes.sh: pack deps with the cu130 torch / NumPy 2.x / Transformers 5.x stack locked (derived from the live env, never downgraded); ultralytics installed --no-deps. - Dockerfile/.dockerignore/.gitignore: image vendors the packs at build. Live GB10 worker verified: node types 644 -> 1290 (+646), 0 import failures. was-node-suite quarantined (hard numba dep incompatible with NumPy 2.5).
This commit is contained in:
+8
-2
@@ -1,7 +1,11 @@
|
||||
models/
|
||||
output/
|
||||
input/
|
||||
custom_nodes/
|
||||
custom_nodes/*
|
||||
# ...but the pack manifest must reach the build context so the image can vendor
|
||||
# the packs reproducibly (scripts/install_custom_nodes.sh clones them at build).
|
||||
# Local clones stay excluded so the image builds them fresh from the manifest.
|
||||
!custom_nodes/hanzo-packs.txt
|
||||
user/
|
||||
temp/
|
||||
orgs/
|
||||
@@ -19,7 +23,9 @@ venv/
|
||||
.ruff_cache/
|
||||
tests/
|
||||
tests-unit/
|
||||
scripts/
|
||||
scripts/*
|
||||
# ...except the pack installer, which the image build invokes.
|
||||
!scripts/install_custom_nodes.sh
|
||||
notebooks/
|
||||
*.swp
|
||||
.env
|
||||
|
||||
+7
-2
@@ -5,8 +5,12 @@ __pycache__/
|
||||
!/input/example.png
|
||||
/models/
|
||||
/temp/
|
||||
/custom_nodes/
|
||||
!custom_nodes/example_node.py.example
|
||||
# Ignore vendored pack contents but keep the directory listable so the tracked
|
||||
# files below can be re-included (a file under a fully-excluded dir cannot be).
|
||||
/custom_nodes/*
|
||||
!/custom_nodes/example_node.py.example
|
||||
# Pinned pack manifest is tracked; the pack sources it clones are not (vendored at build).
|
||||
!/custom_nodes/hanzo-packs.txt
|
||||
extra_model_paths.yaml
|
||||
/.vs
|
||||
.vscode/
|
||||
@@ -24,3 +28,4 @@ web_custom_versions/
|
||||
openapi.yaml
|
||||
filtered-openapi.yaml
|
||||
uv.lock
|
||||
/custom_nodes_quarantine/
|
||||
|
||||
@@ -25,6 +25,11 @@ RUN chmod +x /tmp/branding/apply-branding.sh && /tmp/branding/apply-branding.sh
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Vendor the Hanzo Studio custom-node packs (segmentation, controlnet, ipadapter,
|
||||
# upscaling, video, utils) at their pinned commits and install their deps with the
|
||||
# torch/numpy/transformers stack locked. See custom_nodes/hanzo-packs.txt.
|
||||
RUN chmod +x scripts/install_custom_nodes.sh && scripts/install_custom_nodes.sh
|
||||
|
||||
# Create directories for runtime volumes
|
||||
RUN mkdir -p models output input custom_nodes user
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Hand-resolved Python deps for the vendored custom-node packs (see
|
||||
# custom_nodes/hanzo-packs.txt). Install via scripts/install_custom_nodes.sh, which
|
||||
# locks the studio cu130 torch stack / NumPy 2.x / Transformers 5.x (derived from
|
||||
# the live env) so none of these can downgrade them. Only deps NOT already in
|
||||
# requirements.txt are listed; the studio-critical packages are locked, not here.
|
||||
# `ultralytics` is intentionally absent -- the installer adds it with --no-deps so
|
||||
# it cannot pull the non-headless opencv-python that fights opencv-*-headless.
|
||||
|
||||
# --- Impact-Pack / segment_anything: detailer + SAM masks ---
|
||||
segment-anything
|
||||
timm
|
||||
scikit-image
|
||||
piexif
|
||||
dill
|
||||
matplotlib
|
||||
|
||||
# --- Impact-Subpack (ultralytics detectors): light deps only; ultralytics itself
|
||||
# is installed --no-deps by the installer, then these fill in its runtime needs ---
|
||||
py-cpuinfo
|
||||
ultralytics-thop
|
||||
pandas
|
||||
|
||||
# --- comfyui_controlnet_aux: pose/depth/edge preprocessors ---
|
||||
opencv-contrib-python-headless
|
||||
onnxruntime
|
||||
addict
|
||||
yapf
|
||||
omegaconf
|
||||
ftfy
|
||||
yacs
|
||||
fvcore
|
||||
trimesh
|
||||
albumentations
|
||||
mediapipe
|
||||
python-dateutil
|
||||
importlib_metadata
|
||||
scikit-learn
|
||||
|
||||
# --- ComfyUI-KJNodes ---
|
||||
color-matcher
|
||||
mss
|
||||
|
||||
# --- VideoHelperSuite / Frame-Interpolation: video I/O ---
|
||||
imageio
|
||||
imageio-ffmpeg
|
||||
|
||||
# --- misc utils used across packs ---
|
||||
gitpython
|
||||
joblib
|
||||
pilgram
|
||||
fairscale
|
||||
@@ -0,0 +1,35 @@
|
||||
# Hanzo Studio custom-node packs -- pinned, reproducible, first-class "hanzo packages".
|
||||
#
|
||||
# Format: <dir_name> <git_url> <commit_sha> <spdx_license> # friendly-ref
|
||||
#
|
||||
# Installed by scripts/install_custom_nodes.sh (invoked in the Docker image build
|
||||
# and runnable locally) so the studio.hanzo.ai image ships the same packs the
|
||||
# local GB10 worker runs. Upstream packs import comfy.* / subclass io.ComfyNode /
|
||||
# expose comfy_entrypoint -- studio_compat aliases those to the studio.* names at
|
||||
# boot and the loader honors comfy_entrypoint, so packs run UNMODIFIED (no fork,
|
||||
# no source edits). Python deps: custom_nodes-requirements.txt (+ -constraints.txt).
|
||||
#
|
||||
# Pinned to immutable commit SHAs. To bump a pack: replace its SHA + re-run the
|
||||
# installer (patch-bump; never blind-track HEAD). All licenses are OSI (GPL/AGPL/
|
||||
# MIT/Apache) -- none are non-commercial.
|
||||
|
||||
ComfyUI-Impact-Pack https://github.com/ltdrdata/ComfyUI-Impact-Pack.git 429d0159ad429e64d2b3916e6e7be9c22d025c3c GPL-3.0 # 8.28
|
||||
ComfyUI-Impact-Subpack https://github.com/ltdrdata/ComfyUI-Impact-Subpack.git 50c7b71a6a224734cc9b21963c6d1926816a97f1 AGPL-3.0 # 1.3.4 Ultralytics detectors
|
||||
ComfyUI_IPAdapter_plus https://github.com/cubiq/ComfyUI_IPAdapter_plus.git a0f451a5113cf9becb0847b92884cb10cbdec0ef GPL-3.0 # HEAD 2025-04-14
|
||||
comfyui_controlnet_aux https://github.com/Fannovel16/comfyui_controlnet_aux.git e8b689a513c3e6b63edc44066560ca5919c0576e Apache-2.0 # HEAD 2026-04-13
|
||||
ComfyUI_UltimateSDUpscale https://github.com/ssitu/ComfyUI_UltimateSDUpscale.git a5547db9e1d07d3318bb21e9e9c474f4c1e9c8df GPL-3.0 # HEAD 2026-06-22
|
||||
ComfyUI-Frame-Interpolation https://github.com/Fannovel16/ComfyUI-Frame-Interpolation.git 26545cc2dd95bc3d27f056016300673bdeee78f5 MIT # HEAD 2026-03-29
|
||||
ComfyUI-VideoHelperSuite https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite.git 4ee72c065db22c9d96c2427954dc69e7b908444b GPL-3.0 # HEAD 2026-05-14
|
||||
rgthree-comfy https://github.com/rgthree/rgthree-comfy.git 27b4f4cdcf3b127c29d5d8135ac1536ecbd4c383 MIT # HEAD 2026-06-20
|
||||
ComfyUI-Custom-Scripts https://github.com/pythongosssss/ComfyUI-Custom-Scripts.git 609f3afaa74b2f88ef9ce8d939626065e3247469 MIT # HEAD 2026-02-12
|
||||
ComfyUI-KJNodes https://github.com/kijai/ComfyUI-KJNodes.git e27a505b3ba6ce42687fe00500deda103d9d6071 GPL-3.0 # HEAD 2026-07-01
|
||||
cg-use-everywhere https://github.com/chrisgoringe/cg-use-everywhere.git 632ed7bb51bb18ceb03ccaefe1f34be8bd416500 Apache-2.0 # HEAD 2026-06-22
|
||||
ComfyUI-segment-anything-2 https://github.com/kijai/ComfyUI-segment-anything-2.git 0c35fff5f382803e2310103357b5e985f5437f32 Apache-2.0 # HEAD 2025-09-28 SAM2
|
||||
comfyui_segment_anything https://github.com/storyicon/comfyui_segment_anything.git ab6395596399d5048639cdab7e44ec9fae857a93 Apache-2.0 # HEAD GroundingDINO+SAM
|
||||
|
||||
# --- QUARANTINED (not installed; kept here as a record) -------------------------
|
||||
# was-node-suite-comfyui https://github.com/WASasquatch/was-node-suite-comfyui.git ea935d1044ae5a26efa54ebeb18fe9020af49a45 MIT
|
||||
# Reason: hard `from numba import jit` at import; numba has no release supporting
|
||||
# the studio venv's NumPy 2.5 ("Numba needs NumPy 2.4 or less"). Re-enabling would
|
||||
# require downgrading NumPy (forbidden -- breaks the cu130 torch stack) or waiting
|
||||
# for a numba release that supports NumPy 2.5. Revisit then. License (MIT) is fine.
|
||||
@@ -1,3 +1,4 @@
|
||||
import studio_compat # noqa: F401 aliases comfy.* -> studio.* for upstream custom nodes
|
||||
import studio.options
|
||||
studio.options.enable_args_parsing()
|
||||
|
||||
|
||||
@@ -2258,9 +2258,14 @@ async def load_custom_node(module_path: str, ignore=set(), module_parent="custom
|
||||
if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS") and getattr(module, "NODE_DISPLAY_NAME_MAPPINGS") is not None:
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
return True
|
||||
# V3 Extension Definition
|
||||
elif hasattr(module, "studio_entrypoint"):
|
||||
entrypoint = getattr(module, "studio_entrypoint")
|
||||
# V3 Extension Definition. Honor the upstream ComfyUI `comfy_entrypoint`
|
||||
# name too, so vendored packs register through the V3 extension API
|
||||
# unmodified (their ComfyExtension/io.ComfyNode are aliased to the
|
||||
# Studio* classes by studio_compat).
|
||||
elif hasattr(module, "studio_entrypoint") or hasattr(module, "comfy_entrypoint"):
|
||||
entrypoint = getattr(module, "studio_entrypoint", None)
|
||||
if entrypoint is None:
|
||||
entrypoint = getattr(module, "comfy_entrypoint")
|
||||
if not callable(entrypoint):
|
||||
logging.warning(f"studio_entrypoint in {module_path} is not callable, skipping.")
|
||||
return False
|
||||
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
# install_custom_nodes.sh -- vendor the Hanzo Studio custom-node packs.
|
||||
#
|
||||
# Reads custom_nodes/hanzo-packs.txt, clones each pack at its pinned commit (git,
|
||||
# idempotent), then installs their hand-resolved Python deps
|
||||
# (custom_nodes-requirements.txt) with the studio torch/numpy/transformers stack
|
||||
# LOCKED so nothing gets downgraded. The lock is DERIVED FROM THE LIVE ENV (not a
|
||||
# hardcoded file), so it is correct in both contexts:
|
||||
# - Docker image build (CPU torch): STUDIO_PY=python pip install
|
||||
# - Local uv venv (GB10, cu130 torch):
|
||||
# STUDIO_PY=.venv/bin/python \
|
||||
# STUDIO_PIP="uv pip install --python .venv/bin/python" \
|
||||
# scripts/install_custom_nodes.sh
|
||||
#
|
||||
# Upstream `comfy.*` imports are aliased to `studio.*` at boot by studio_compat,
|
||||
# so packs run unmodified. Idempotent; safe to re-run to reconcile pins.
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
CN="$REPO/custom_nodes"
|
||||
MANIFEST="$CN/hanzo-packs.txt"
|
||||
REQS="$REPO/custom_nodes-requirements.txt"
|
||||
PY="${STUDIO_PY:-python}"
|
||||
PIP="${STUDIO_PIP:-python -m pip install}"
|
||||
CGEN="$(mktemp)"
|
||||
trap 'rm -f "$CGEN"' EXIT
|
||||
|
||||
log() { printf '[packs] %s\n' "$*"; }
|
||||
|
||||
# 0. Derive a constraints file locking the studio-critical packages to whatever
|
||||
# versions are ALREADY installed, so pack deps can never move the torch stack,
|
||||
# NumPy 2.x or Transformers 5.x. A dep that is incompatible surfaces as a
|
||||
# resolver error instead of silently breaking the runtime.
|
||||
"$PY" - > "$CGEN" <<'PY'
|
||||
import importlib.metadata as md
|
||||
CRIT = ["torch", "torchvision", "torchaudio", "torchsde", "numpy",
|
||||
"transformers", "tokenizers", "pillow", "scipy", "timm",
|
||||
"safetensors", "huggingface-hub"]
|
||||
for name in CRIT:
|
||||
try:
|
||||
print(f"{name}=={md.version(name)}")
|
||||
except md.PackageNotFoundError:
|
||||
pass
|
||||
PY
|
||||
log "locked stack:"; sed 's/^/[packs] /' "$CGEN"
|
||||
|
||||
# 1. Clone / pin every pack from the manifest (comment + blank lines skipped).
|
||||
log "reconciling packs from $MANIFEST"
|
||||
while read -r name url sha _rest; do
|
||||
case "$name" in ''|\#*) continue ;; esac
|
||||
dir="$CN/$name"
|
||||
if [ -d "$dir/.git" ]; then
|
||||
git -C "$dir" fetch -q origin "$sha" 2>/dev/null || git -C "$dir" fetch -q --all --tags
|
||||
else
|
||||
git clone -q --filter=blob:none "$url" "$dir"
|
||||
fi
|
||||
git -C "$dir" checkout -q "$sha"
|
||||
log "pinned $name @ $(git -C "$dir" rev-parse --short HEAD)"
|
||||
done < "$MANIFEST"
|
||||
|
||||
# 2. Install pack dependencies with the derived stack lock.
|
||||
log "installing pack dependencies"
|
||||
$PIP -c "$CGEN" -r "$REQS"
|
||||
|
||||
# 3. ultralytics (AGPL detectors for Impact-Subpack): --no-deps so it cannot pull
|
||||
# the non-headless opencv-python that would fight opencv-*-headless.
|
||||
log "installing ultralytics (--no-deps)"
|
||||
$PIP -c "$CGEN" --no-deps ultralytics
|
||||
|
||||
log "done -- $(grep -cvE '^\s*(#|$)' "$MANIFEST") packs pinned"
|
||||
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
studio_compat -- import-compat shim for upstream ComfyUI custom nodes.
|
||||
|
||||
Hanzo Studio renamed ComfyUI's internal packages (``comfy`` -> ``studio``,
|
||||
``comfy_extras`` -> ``studio_extras``, ... and ``comfy/comfy_types`` ->
|
||||
``studio/node_types``; see ``scripts/rename_modules.py``). Third-party custom
|
||||
node packs import the upstream ``comfy*`` names, so they fail to import against
|
||||
this fork.
|
||||
|
||||
This module installs ONE meta-path finder that lazily aliases every ``comfy*``
|
||||
import to its ``studio*`` counterpart, so vendored packs run unmodified -- no
|
||||
forking, no source edits, one place, one way. It is the compatibility redirect
|
||||
that ``scripts/rename_modules.py`` deferred (its phase 4), implemented with the
|
||||
modern ``find_spec`` API (``find_module``/``load_module`` were removed in
|
||||
Python 3.12).
|
||||
|
||||
Activated once, from the top of ``main.py``::
|
||||
|
||||
import studio_compat # noqa: F401
|
||||
"""
|
||||
import importlib
|
||||
import importlib.abc
|
||||
import importlib.machinery
|
||||
import sys
|
||||
|
||||
# Root package aliases: upstream ComfyUI name -> Hanzo Studio name.
|
||||
_ROOTS = {
|
||||
"comfy": "studio",
|
||||
"comfy_extras": "studio_extras",
|
||||
"comfy_api": "studio_api",
|
||||
"comfy_api_nodes": "studio_api_nodes",
|
||||
"comfy_config": "studio_config",
|
||||
"comfy_execution": "studio_execution",
|
||||
}
|
||||
|
||||
# Submodule renames applied to the target name after the root swap.
|
||||
# ComfyUI's ``comfy/comfy_types`` lives at ``studio/node_types`` in this fork.
|
||||
_SUBMODULE_RENAMES = {
|
||||
"studio.comfy_types": "studio.node_types",
|
||||
}
|
||||
|
||||
# External pip packages that share the ``comfy*``/``comfyui*`` prefix but are
|
||||
# NOT part of the renamed core -- never alias these (they resolve normally).
|
||||
# Mirrors the EXTERNAL_PACKAGES set in scripts/rename_modules.py.
|
||||
_EXTERNAL = frozenset({
|
||||
"comfy_kitchen",
|
||||
"comfy_aimdo",
|
||||
"comfyui_manager",
|
||||
"comfyui_frontend_package",
|
||||
"comfyui_workflow_templates",
|
||||
"comfyui_embedded_docs",
|
||||
})
|
||||
|
||||
|
||||
def _target_name(fullname):
|
||||
"""Return the ``studio*`` module name that ``fullname`` aliases, or None."""
|
||||
root = fullname.split(".", 1)[0]
|
||||
if root in _EXTERNAL or root not in _ROOTS:
|
||||
return None
|
||||
target = _ROOTS[root] + fullname[len(root):]
|
||||
for src, dst in _SUBMODULE_RENAMES.items():
|
||||
if target == src or target.startswith(src + "."):
|
||||
target = dst + target[len(src):]
|
||||
break
|
||||
return target
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Class-name compatibility.
|
||||
#
|
||||
# The fork mechanically swapped the ``Comfy`` prefix for ``Studio`` on the
|
||||
# public node-API classes (comfy_api.latest.ComfyExtension -> StudioExtension;
|
||||
# comfy_api.latest.io.ComfyNode -> studio_api.latest.io.StudioNode, ...).
|
||||
# Newer packs subclass ``io.ComfyNode`` / return ``ComfyExtension``. We apply
|
||||
# the inverse swap as attribute aliases, lazily, the first time a pack imports
|
||||
# the aliased module -- by which point studio_api.latest is safely importable.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _mirror_comfy_prefix(module):
|
||||
"""Bind ``Comfy<X>`` aliases for every public ``Studio<X>`` in ``module``."""
|
||||
for name in list(vars(module)):
|
||||
if name.startswith("Studio"):
|
||||
alias = "Comfy" + name[len("Studio"):]
|
||||
if not hasattr(module, alias):
|
||||
setattr(module, alias, getattr(module, name))
|
||||
|
||||
|
||||
def _mirror_studio_api_latest(module):
|
||||
_mirror_comfy_prefix(module)
|
||||
io_mod = getattr(module, "io", None) # studio_api.latest._io_public
|
||||
if io_mod is not None:
|
||||
_mirror_comfy_prefix(io_mod)
|
||||
|
||||
|
||||
# target module name -> hook(module) run once, right after it is aliased.
|
||||
_POST_IMPORT = {
|
||||
"studio_api.latest": _mirror_studio_api_latest,
|
||||
}
|
||||
|
||||
|
||||
class _AliasLoader(importlib.abc.Loader):
|
||||
def __init__(self, target):
|
||||
self._target = target
|
||||
|
||||
def create_module(self, spec):
|
||||
# Import (or reuse) the real module and hand it back as the alias.
|
||||
# Returning an already-initialized module is why exec_module is a no-op;
|
||||
# _init_module_attrs(override=False) preserves the target's __name__ etc.
|
||||
module = importlib.import_module(self._target)
|
||||
hook = _POST_IMPORT.get(self._target)
|
||||
if hook is not None:
|
||||
hook(module)
|
||||
return module
|
||||
|
||||
def exec_module(self, module):
|
||||
pass
|
||||
|
||||
|
||||
class _AliasFinder(importlib.abc.MetaPathFinder):
|
||||
def find_spec(self, fullname, path=None, target=None):
|
||||
name = _target_name(fullname)
|
||||
if name is None:
|
||||
return None
|
||||
return importlib.machinery.ModuleSpec(fullname, _AliasLoader(name))
|
||||
|
||||
|
||||
def install():
|
||||
"""Insert the alias finder at the head of ``sys.meta_path`` (idempotent)."""
|
||||
if not any(isinstance(f, _AliasFinder) for f in sys.meta_path):
|
||||
sys.meta_path.insert(0, _AliasFinder())
|
||||
|
||||
|
||||
install()
|
||||
Reference in New Issue
Block a user