Files
Alexankharin-camera-comfyUI/install.py
T
Alexander KharinandClaude Fable 5 d77af07189 Apply workflow improvements: runnable defaults, templates, dep trim, CI
- 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>
2026-07-16 18:49:14 +03:00

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())