- Bundle example inputs (example_inputs/: pinhole + fisheye JPEG, example trajectory .npy); install.py copies them into ComfyUI's input/ folder and every LoadImage/LoadTrajectory default now points at them, so the shipped workflows queue on a fresh install. - Template browser: add same-name .jpg thumbnails for 10 workflows (workflows/ is already a recognized template dir); drop the stray workflows/__init__.py. - Move superseded reference graphs to workflows/legacy/ (SD-checkpoint outpaint variant, manual multi-view fisheye depth) - out of the template browser, still documented. - video_camera.json: remove dead-end BlurMaskFast (radius 0/0 no-op), easy-mathInt pad math and dangling VHS_VideoInfo; swap KJNodes GetImageRangeFromBatch for built-in ImageFromBatch. Pack deps cut from five to two (VideoHelperSuite + Florence2). - New workflows/record_trajectory.json - produces the .npy trajectory that PC_enricher and wan_vace_ref_to_video consume. - Stamp extra.camera_comfyui_rev=1 in all workflows for future migrations; prune 8 pre-existing stale link references found by a bidirectional integrity sweep. - CI: .github/workflows/validate.yml runs the schema validator, installer-logic tests and 4D smoke suite (CPU torch) on PRs and main. - README + docs/workflows_review.md updated accordingly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
216 lines
7.5 KiB
Python
216 lines
7.5 KiB
Python
"""Post-install setup for camera-comfyUI.
|
|
|
|
ComfyUI-Manager runs this script automatically after installing the node pack
|
|
(both git and registry installs), right after `pip install -r requirements.txt`.
|
|
Manual users can run it themselves: `python install.py` from this directory.
|
|
|
|
It makes the optional heavy dependencies work out of the box:
|
|
* vggt — pip-installed from GitHub (not on PyPI); needed by VideoPoseEstimator.
|
|
* SHARP — the submodules/ml-sharpt checkout; needed by ImageToSplat & co.
|
|
* gsplat — SHARP's rasterizer backend (JIT-compiles CUDA kernels on first use).
|
|
* inpainting_flux — the ComfyUI-Flux-Inpainting node pack, cloned as a sibling
|
|
custom node; needed by OutpaintAnyProjection / SplatTrajectoryEnricher.
|
|
|
|
Every step is idempotent and non-fatal: the node pack degrades gracefully at
|
|
runtime (see __init__.py), so a failed optional step only disables the nodes
|
|
that need it. The script therefore always exits 0 and reports what it skipped.
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
NODE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
LOG_PREFIX = "[camera-comfyUI install]"
|
|
|
|
VGGT_GIT_URL = "https://github.com/facebookresearch/vggt.git"
|
|
SHARP_GIT_URL = "https://github.com/apple/ml-sharp"
|
|
FLUX_PACK_GIT_URL = "https://github.com/rubi-du/ComfyUI-Flux-Inpainting.git"
|
|
# Folder names under custom_nodes/ that count as "the flux inpainting pack is
|
|
# already installed" (must stay in sync with flux_fisheye_filling_nodes.py).
|
|
FLUX_PACK_CANDIDATES = (
|
|
"inpainting_flux",
|
|
"ComfyUI-Flux-Inpainting",
|
|
"ComfyUI-Flux-Inpainting-main",
|
|
"comfyui-flux-inpainting",
|
|
)
|
|
|
|
|
|
def _log(message: str) -> None:
|
|
print(f"{LOG_PREFIX} {message}", flush=True)
|
|
|
|
|
|
def _run(cmd, cwd=None) -> None:
|
|
_log("$ " + " ".join(cmd))
|
|
subprocess.check_call(cmd, cwd=cwd)
|
|
|
|
|
|
def _pip_install(*args: str) -> None:
|
|
_run([sys.executable, "-m", "pip", "install", *args])
|
|
|
|
|
|
def _git() -> str:
|
|
git = shutil.which("git")
|
|
if git is None:
|
|
raise RuntimeError(
|
|
"git executable not found on PATH; cannot fetch GitHub dependencies"
|
|
)
|
|
return git
|
|
|
|
|
|
def _importable(module_name: str) -> bool:
|
|
import importlib.util
|
|
|
|
try:
|
|
return importlib.util.find_spec(module_name) is not None
|
|
except (ImportError, ValueError):
|
|
return False
|
|
|
|
|
|
def ensure_base_requirements() -> None:
|
|
"""Install requirements.txt if it clearly has not been installed yet.
|
|
|
|
ComfyUI-Manager installs it before running this script, so this only fires
|
|
for manual `python install.py` users.
|
|
"""
|
|
if _importable("diffusers") and _importable("transformers"):
|
|
_log("base requirements already satisfied")
|
|
return
|
|
_pip_install("-r", os.path.join(NODE_DIR, "requirements.txt"))
|
|
|
|
|
|
def ensure_vggt() -> None:
|
|
"""VGGT (VideoPoseEstimator). Not on PyPI — install from GitHub over https."""
|
|
if _importable("vggt"):
|
|
_log("vggt already installed")
|
|
return
|
|
_pip_install(f"vggt @ git+{VGGT_GIT_URL}")
|
|
_log("vggt installed from GitHub")
|
|
|
|
|
|
def ensure_sharp_checkout() -> None:
|
|
"""Materialize the SHARP submodule (ImageToSplat / VideoToFusedSplats).
|
|
|
|
Registry archives already bundle it; git installs need `submodule update`
|
|
(ComfyUI-Manager does not clone recursively). If this directory is not a
|
|
git checkout at all, fall back to a direct clone.
|
|
"""
|
|
sharp_dir = os.path.join(NODE_DIR, "submodules", "ml-sharpt")
|
|
sharp_src = os.path.join(sharp_dir, "src", "sharp")
|
|
if os.path.isdir(sharp_src):
|
|
_log("SHARP checkout already present")
|
|
return
|
|
|
|
git = _git()
|
|
if os.path.exists(os.path.join(NODE_DIR, ".git")):
|
|
_run([git, "submodule", "update", "--init", "--recursive"], cwd=NODE_DIR)
|
|
if os.path.isdir(sharp_src):
|
|
_log("SHARP submodule initialized")
|
|
return
|
|
|
|
if os.path.isdir(sharp_dir) and os.listdir(sharp_dir):
|
|
raise RuntimeError(
|
|
f"{sharp_dir} exists but does not contain src/sharp; "
|
|
"remove it and re-run install.py"
|
|
)
|
|
_run([git, "clone", "--depth", "1", SHARP_GIT_URL, sharp_dir])
|
|
_log("SHARP cloned from GitHub")
|
|
|
|
|
|
def ensure_gsplat() -> None:
|
|
"""gsplat backs SHARP's import chain and the fast splat render path.
|
|
|
|
pip install is lightweight (CUDA kernels JIT-compile on first use), but it
|
|
can still fail on exotic setups — that only disables the SHARP/gsplat nodes.
|
|
"""
|
|
if _importable("gsplat"):
|
|
_log("gsplat already installed")
|
|
return
|
|
_pip_install("gsplat")
|
|
_log("gsplat installed")
|
|
|
|
|
|
def ensure_flux_inpainting_pack() -> None:
|
|
"""Clone ComfyUI-Flux-Inpainting next to this pack if no copy exists yet."""
|
|
custom_nodes_dir = os.path.dirname(NODE_DIR)
|
|
if os.path.basename(custom_nodes_dir).lower() != "custom_nodes":
|
|
_log(
|
|
"not installed under a ComfyUI custom_nodes directory; "
|
|
"skipping ComfyUI-Flux-Inpainting setup"
|
|
)
|
|
return
|
|
|
|
for name in FLUX_PACK_CANDIDATES:
|
|
if os.path.isdir(os.path.join(custom_nodes_dir, name)):
|
|
_log(f"flux inpainting pack already present ({name})")
|
|
return
|
|
|
|
git = _git()
|
|
target = os.path.join(custom_nodes_dir, "inpainting_flux")
|
|
_run([git, "clone", "--depth", "1", FLUX_PACK_GIT_URL, target])
|
|
pack_requirements = os.path.join(target, "requirements.txt")
|
|
if os.path.isfile(pack_requirements):
|
|
_pip_install("-r", pack_requirements)
|
|
_log("ComfyUI-Flux-Inpainting installed as custom_nodes/inpainting_flux")
|
|
|
|
|
|
def ensure_example_inputs() -> None:
|
|
"""Copy bundled example inputs into ComfyUI's input dir (no overwrite).
|
|
|
|
The shipped example workflows reference these files, so a fresh install
|
|
can queue them immediately.
|
|
"""
|
|
src_dir = os.path.join(NODE_DIR, "example_inputs")
|
|
if not os.path.isdir(src_dir):
|
|
_log("no example_inputs directory; skipping")
|
|
return
|
|
custom_nodes_dir = os.path.dirname(NODE_DIR)
|
|
if os.path.basename(custom_nodes_dir).lower() != "custom_nodes":
|
|
_log("not installed under a ComfyUI custom_nodes directory; "
|
|
"skipping example input setup")
|
|
return
|
|
input_dir = os.path.join(os.path.dirname(custom_nodes_dir), "input")
|
|
os.makedirs(input_dir, exist_ok=True)
|
|
copied = 0
|
|
for name in os.listdir(src_dir):
|
|
target = os.path.join(input_dir, name)
|
|
if not os.path.exists(target):
|
|
shutil.copy2(os.path.join(src_dir, name), target)
|
|
copied += 1
|
|
_log(f"example inputs ready ({copied} file(s) copied to {input_dir})")
|
|
|
|
|
|
STEPS = (
|
|
("base requirements", ensure_base_requirements),
|
|
("vggt (VideoPoseEstimator)", ensure_vggt),
|
|
("SHARP checkout (ImageToSplat)", ensure_sharp_checkout),
|
|
("gsplat (splat rasterizer)", ensure_gsplat),
|
|
("ComfyUI-Flux-Inpainting (OutpaintAnyProjection)", ensure_flux_inpainting_pack),
|
|
("example workflow inputs", ensure_example_inputs),
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
failures = []
|
|
for title, step in STEPS:
|
|
_log(f"--- {title} ---")
|
|
try:
|
|
step()
|
|
except Exception as exc: # keep going: each dependency is optional
|
|
failures.append((title, exc))
|
|
_log(f"WARNING: {title} failed: {exc}")
|
|
|
|
if failures:
|
|
_log("finished with warnings - the affected optional nodes stay disabled:")
|
|
for title, exc in failures:
|
|
_log(f" * {title}: {exc}")
|
|
_log("re-run `python install.py` after fixing the issue (network/git/pip)")
|
|
else:
|
|
_log("all optional dependencies are ready")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|