modelbeast/server/operators/vidgod_roto/run.py
John King 8965d2240b feat(operators): vidgod_roto + vidgod_index, requires_path node gating
Two stdlib-only wrapper ops around the VIDGOD stack (gitea monster/vidgod):
click-to-cutout (SAM2.1+MatAnyone -> ProRes 4444 alpha) and clip-library
indexing (PySceneDetect + mlx-whisper). They shell out to ~/Documents/VIDGOD
bin CLIs, which carry their own venvs — no MODELBEAST venv or install script.

New optional manifest field requires_path: gates the LOCAL node on a path
existing (expanduser), so ops whose stack lives only on some boxes are not
grabbed by the primary. Remote nodes stay allowlist-gated via nodes.json
(vidgod_* allowlisted on m1/ultra only for now).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 14:44:48 +10:00

59 lines
2.0 KiB
Python

"""vidgod_roto — MODELBEAST wrapper around VIDGOD's vg-roto.
Stdlib-only on purpose: runs under the node's system python3 and shells out to
~/Documents/VIDGOD/bin/vg-roto, which brings its own venv (SAM 2.1 + MatAnyone).
"""
import argparse
import json
import shutil
import subprocess
import sys
from pathlib import Path
ap = argparse.ArgumentParser()
ap.add_argument("--input", required=True)
ap.add_argument("--outdir", required=True)
ap.add_argument("--params", default="{}")
args = ap.parse_args()
p = json.loads(args.params)
vg_roto = Path.home() / "Documents/VIDGOD/bin/vg-roto"
if not vg_roto.exists():
sys.exit("VIDGOD not installed on this node — clone gitea monster/vidgod to "
"~/Documents/VIDGOD and run setup/setup_venvs.sh")
outdir = Path(args.outdir)
workdir = outdir / "roto"
cmd = [str(vg_roto), args.input, "--out", str(workdir),
"--frame", str(int(p.get("frame", 0))),
"--mode", p.get("mode", "matte"),
"--max-size", str(int(p.get("max_size", -1)))]
for pt in str(p.get("points", "")).split(";"):
if pt.strip():
cmd += ["--point", pt.strip()]
for pt in str(p.get("neg_points", "")).split(";"):
if pt.strip():
cmd += ["--neg", pt.strip()]
if str(p.get("box", "")).strip():
cmd += ["--box", str(p["box"]).strip()]
print("+", " ".join(cmd), flush=True)
subprocess.run(cmd, check=True, stderr=sys.stdout)
# vg-roto writes <stem>_{alpha.mov,matte.mov,green.mp4} into workdir; lift them
# to outdir top-level so the runner registers them as job outputs.
outputs = []
for f in sorted(workdir.glob("*_*.m*")):
dest = outdir / f.name
shutil.move(str(f), dest)
outputs.append({"path": f.name, "name": f.name})
if not outputs:
sys.exit("vg-roto produced no outputs")
shutil.rmtree(workdir, ignore_errors=True)
(outdir / "result.json").write_text(json.dumps({
"outputs": outputs,
"summary": {"mode": p.get("mode", "matte"), "files": [o["name"] for o in outputs]},
}))
print("done:", ", ".join(o["name"] for o in outputs), flush=True)