phase 2: vg-remove (ProPainter), vg-interp (RIFE), vg-cutie, farm ops, zoo mirror

- vg-remove: object/logo/watermark removal via ProPainter on MPS; static --box,
  SAM2-tracked --point for moving objects, or user --mask. Output always scaled
  back to source dims (imageio macro-block-pads ProPainter output).
- vg-interp: RIFE frame interpolation via rife-ncnn-vulkan (universal binary,
  native Metal/MoltenVK, rife-v4.6); smooth (fps x N) or --slowmo.
- vg-cutie: Cutie interactive segmentation GUI launcher (local GUI session).
- setup/fetch_phase2.sh: idempotent clones + weights + deps + patches.
- patches: propainter-cv2-reader (torchvision >= 0.23 removed read_video),
  cutie-device (get_default_model hard-coded .cuda(); now cuda->mps->cpu).
- smoke_test.sh: adds the RIFE lane (skips when not fetched).
- Farm: vidgod_roto/vidgod_index operators live in MODELBEAST (8965d22),
  verified from JING5; weights mirrored to NAS modelzoo/vidgod-weights.

All lanes verified on ultra 2026-08-24: de-logo reconstruction eyeballed clean,
24->48fps interp, Cutie headless propagation PASS, smoke test 4/4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-08-24 15:42:52 +10:00
parent 7911a59186
commit 8c84243b61
9 changed files with 392 additions and 9 deletions

View File

@ -12,10 +12,21 @@ Read README.md first for what the tools do. This file is the stuff an agent need
it's what makes them run inside the right venv from any checkout location). it's what makes them run inside the right venv from any checkout location).
- Verified 2026-08-24 on ultra: all five lanes, including a real SAM2+MatAnyone cutout - Verified 2026-08-24 on ultra: all five lanes, including a real SAM2+MatAnyone cutout
(1917 trench demo clip, 2 clicks, matte held over 6s; ~3.4 fr/s at 1920×804 on MPS). (1917 trench demo clip, 2 clicks, matte held over 6s; ~3.4 fr/s at 1920×804 on MPS).
- The MatAnyone torchvision fix lives in `patches/matanyone-cv2-reader.patch`, applied by - Three tracked patches, all applied idempotently by the setup scripts (grep guards):
setup step 6 (guard: grep CAP_PROP_FPS). Upstream may eventually fix this — if the patch `matanyone-cv2-reader.patch` + `propainter-cv2-reader.patch` (torchvision ≥0.23 removed
stops applying, check whether their `read_frame_from_videos` already avoids `read_video` — both repos read via cv2 now) and `cutie-device.patch` (get_default_model
`torchvision.io.read_video`, and drop the patch step if so. hard-coded `.cuda()`; now cuda→mps→cpu like its own interactive_demo). If a patch stops
applying, upstream probably fixed it — check and drop the step.
- Phase 2 verified 2026-08-24 on ultra: vg-remove de-logo (ProPainter on MPS),
vg-interp 24→48fps (RIFE ncnn universal binary, native Metal/MoltenVK), Cutie headless
mask propagation on MPS (GUI click-test needs a local session — not SSH-verifiable).
- Farm ops `vidgod_roto` / `vidgod_index` live in the MODELBEAST repo (commit 8965d22):
stdlib wrappers calling this repo's bin CLIs; manifest `requires_path` gates the primary
from grabbing them (that gating patch is in MODELBEAST server/remote.py + runner.py);
allowlisted for node m1 (ultra) in nodes.json (primary-only, gitignored). Both lanes
verified end-to-end with jobs submitted from JING5.
- Weights mirror: `/Volumes/projects/modelzoo/vidgod-weights/` (NAS via ultra's SMB
mounts) — models/, propainter/, cutie/, rife/. Listed in modelzoo MANIFEST.md.
- Test with `setup/smoke_test.sh` (fast) / `--roto` (full pipeline). Test media is synthesized - Test with `setup/smoke_test.sh` (fast) / `--roto` (full pipeline). Test media is synthesized
(ffmpeg lavfi + macOS `say`) — nothing copyrighted is committed. (ffmpeg lavfi + macOS `say`) — nothing copyrighted is committed.
- The clip library sqlite lives at `library/clips.sqlite` **per checkout** — the real library - The clip library sqlite lives at `library/clips.sqlite` **per checkout** — the real library

View File

@ -16,6 +16,9 @@ instead of rotoscoping splines.
| `bin/vg-index PATHS` | Shot-detect (PySceneDetect) + transcribe (mlx-whisper large-v3-turbo) + thumbnail everything into `library/clips.sqlite` (FTS5). Resumable. | | `bin/vg-index PATHS` | Shot-detect (PySceneDetect) + transcribe (mlx-whisper large-v3-turbo) + thumbnail everything into `library/clips.sqlite` (FTS5). Resumable. |
| `bin/vg-find WORDS` | Full-text search the dialogue; shows the enclosing shot. `--cut DIR` exports each matching shot as a ProRes clip. `--shots VIDEO` lists a video's shots. | | `bin/vg-find WORDS` | Full-text search the dialogue; shows the enclosing shot. `--cut DIR` exports each matching shot as a ProRes clip. `--shots VIDEO` lists a video's shots. |
| `bin/vg-beats SONG` | Beat-track a music file (librosa) → `.beats.csv` + a **Resolve marker EDL** (import: right-click timeline in media pool → Timelines → Import → Timeline Markers from EDL). | | `bin/vg-beats SONG` | Beat-track a music file (librosa) → `.beats.csv` + a **Resolve marker EDL** (import: right-click timeline in media pool → Timelines → Import → Timeline Markers from EDL). |
| `bin/vg-remove CLIP --box x1,y1,x2,y2` | Erase logos/watermarks/objects (ProPainter inpainting). `--point x,y` makes SAM2 track a *moving* object first; `--mask` takes your own. |
| `bin/vg-interp CLIP --factor 2` | RIFE frame interpolation (native Metal binary): smooth motion at fps×factor, or `--slowmo` for factor× slow motion. |
| `bin/vg-cutie --video CLIP` | Cutie interactive segmentation GUI — scribble on the actor, propagate, correct frame by frame; exports per-frame masks (run from a GUI session, not SSH). |
## Install ## Install
@ -24,6 +27,7 @@ Needs: Apple Silicon Mac, `brew install ffmpeg uv`, ~4GB disk for venvs + models
```bash ```bash
git clone ssh://git@100.71.119.27:222/monster/vidgod.git && cd vidgod git clone ssh://git@100.71.119.27:222/monster/vidgod.git && cd vidgod
./setup/setup_venvs.sh # venvs, SAM2 + MatAnyone clones, checkpoints, patches (~10 min) ./setup/setup_venvs.sh # venvs, SAM2 + MatAnyone clones, checkpoints, patches (~10 min)
./setup/fetch_phase2.sh # ProPainter + Cutie + RIFE: clones, weights, deps, patches
./setup/smoke_test.sh # fast lanes; add --roto for the full cutout pipeline ./setup/smoke_test.sh # fast lanes; add --roto for the full cutout pipeline
``` ```
@ -56,9 +60,25 @@ bin/vg-roto ~/mv/pulls/003_*.mov --point 970,220 # → ProRes 4444 alpha cuto
- Rough speed on an M1 Ultra: MatAnyone ≈ 3.4 fr/s at 1920×804; whisper large-v3-turbo - Rough speed on an M1 Ultra: MatAnyone ≈ 3.4 fr/s at 1920×804; whisper large-v3-turbo
transcribes far faster than realtime; ProRes transcodes are ffmpeg-bound. transcribes far faster than realtime; ProRes transcodes are ffmpeg-bound.
## Phase 2 (not built yet) ## Farm integration (MODELBEAST)
ProPainter (object/logo removal), RIFE interpolation, Cutie interactive segmentation GUI, Two operators live in the MODELBEAST repo (`server/operators/`): **`vidgod_roto`**
MODELBEAST farm operators for roto/index jobs, model mirror to NAS modelzoo. Upscaling (gpu lane — video + click points → the three roto outputs as job assets) and
old footage already exists fleet-side (`seedvr2_upscale` on the farm); Resolve's Optical **`vidgod_index`** (cpu lane — index a clip into the node's library; the video is
Flow retime covers slow-mo. copied to `library/ingest/` so entries persist). They're thin stdlib wrappers that
shell out to this repo's `bin/` CLIs, gated by manifest `requires_path` so only nodes
with `~/Documents/VIDGOD` set up will take the jobs (allowlisted on ultra). Submit
from any fleet box with its own MB token; outputs come back through the assets API.
## Weights mirror
All checkpoints (SAM2.1, MatAnyone, Cutie, ProPainter, the RIFE bundle) are mirrored
to the NAS at `/Volumes/projects/modelzoo/vidgod-weights/` — restore instead of
re-downloading when standing up a new box.
## Maybe later
Practical-RIFE v4.25+ (newer models than the ncnn bundle), Depth Anything V2 depth-matte
lane, DDColor colorization, a vg-roto mode that takes Cutie mask dirs end-to-end.
Upscaling already exists fleet-side (`seedvr2_upscale` on the farm); Resolve's Optical
Flow retime covers basic slow-mo without RIFE.

15
bin/vg-cutie Executable file
View File

@ -0,0 +1,15 @@
#!/bin/zsh
# vg-cutie — launch the Cutie interactive segmentation GUI (scribble/click on the
# actor, propagate, correct, export per-frame masks).
#
# Usage: vg-cutie --video CLIP.mp4 [--num_objects N] [--workspace DIR]
# vg-cutie --images FRAME_DIR
#
# Needs a GUI session on this Mac (run from Terminal, not plain SSH).
# Masks land in the workspace dir (default ./workspace/<video-name>/masks) —
# feed them to vg-remove via --mask, or use them as roto sources.
export PATH=/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin
export PYTORCH_ENABLE_MPS_FALLBACK=1
VG="$(cd "$(dirname "$0")/.." && pwd)"
cd "$VG/tools/Cutie" || exit 1
exec "$VG/venvs/roto/bin/python" interactive_demo.py "$@"

65
bin/vg-interp Executable file
View File

@ -0,0 +1,65 @@
#!/bin/zsh
# vg-interp — RIFE frame interpolation (rife-ncnn-vulkan, Metal via MoltenVK).
# Usage: vg-interp CLIP [--factor 2|4] [--slowmo] [--out FILE] [--model rife-v4.6]
# default: smooth motion (fps × factor, audio kept)
# --slowmo: same fps, clip runs factor× longer (audio dropped)
# Output: ProRes LT .mov next to the input (CLIP_interp.mov / CLIP_slowmo.mov)
export PATH=/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin
set -u
VG="$(cd "$(dirname "$0")/.." && pwd)"
RIFE="$VG/tools/rife/rife-ncnn-vulkan"
[[ -x "$RIFE" ]] || { echo "rife not installed — run setup/fetch_phase2.sh" >&2; exit 1; }
IN="${1:?usage: vg-interp CLIP [--factor N] [--slowmo] [--out FILE] [--model NAME]}"
shift
FACTOR=2; SLOWMO=0; OUT=""; MODEL="rife-v4.6"
while [[ $# -gt 0 ]]; do
case "$1" in
--factor) FACTOR="$2"; shift 2 ;;
--slowmo) SLOWMO=1; shift ;;
--out) OUT="$2"; shift 2 ;;
--model) MODEL="$2"; shift 2 ;;
*) echo "unknown arg: $1" >&2; exit 1 ;;
esac
done
IN="${IN:A}"
[[ -f "$IN" ]] || { echo "no such file: $IN" >&2; exit 1; }
fps=$(ffprobe -v error -select_streams v:0 -show_entries stream=avg_frame_rate -of csv=p=0 "$IN")
fps_num="${fps%%/*}"; fps_den="${fps##*/}"
in_fps=$(( fps_num * 1.0 / fps_den ))
W=$(mktemp -d "${TMPDIR:-/tmp}/vginterp.XXXXXX")
trap "rm -rf $W" EXIT
mkdir -p "$W/in" "$W/out"
echo "extracting frames ..."
ffmpeg -hide_banner -loglevel error -y -i "$IN" "$W/in/%08d.png" </dev/null
n_in=$(ls "$W/in" | wc -l | tr -d " ")
n_out=$(( n_in * FACTOR ))
echo "$n_in frames in -> $n_out frames (${MODEL})"
"$RIFE" -i "$W/in" -o "$W/out" -m "$VG/tools/rife/$MODEL" -n "$n_out" -f "%08d.png" || exit 1
n_got=$(ls "$W/out" | wc -l | tr -d " ")
echo "rife produced $n_got frames"
if [[ $SLOWMO -eq 1 ]]; then
out_fps=$in_fps
DEF="${IN%.*}_slowmo.mov"
else
out_fps=$(( in_fps * FACTOR ))
DEF="${IN%.*}_interp.mov"
fi
OUT="${OUT:-$DEF}"
out_fps=$(printf "%g" "$out_fps")
echo "encoding ProRes at ${out_fps}fps ..."
if [[ $SLOWMO -eq 1 ]]; then
ffmpeg -hide_banner -loglevel error -y -framerate "$out_fps" -i "$W/out/%08d.png" \
-c:v prores_ks -profile:v 1 -pix_fmt yuv422p10le "$OUT" </dev/null
else
ffmpeg -hide_banner -loglevel error -y -framerate "$out_fps" -i "$W/out/%08d.png" -i "$IN" \
-map 0:v:0 -map '1:a:0?' -c:v prores_ks -profile:v 1 -pix_fmt yuv422p10le \
-c:a pcm_s16le -shortest "$OUT" </dev/null
fi
echo "done: $OUT"

149
bin/vg-remove Executable file
View File

@ -0,0 +1,149 @@
#!/bin/sh
"exec" "`dirname $0`/../venvs/roto/bin/python" "$0" "$@"
"""vg-remove — erase objects/logos/watermarks from video (ProPainter inpainting).
Usage:
vg-remove CLIP.mp4 --box 1700,40,1900,140 static region (logo/watermark bug)
vg-remove CLIP.mp4 --point 640,360 moving object: SAM2 tracks it, then inpaint
vg-remove CLIP.mp4 --mask mask.png your own mask (white = remove);
also takes a dir of per-frame masks
Options: --out DIR (default CLIP_removed/), --frame N (SAM2 prompt frame for --point),
--resize 0.5 (inpaint at half res — much faster/lighter, result upscaled back),
--dilate N (mask dilation, default 4), --sub-len N (subvideo chunk, default 80)
Output in DIR: CLIP_clean.mov (ProRes LT, original audio) + CLIP_clean_preview.mp4
"""
import argparse, os, subprocess, sys
from pathlib import Path
os.environ["PATH"] = "/opt/homebrew/bin:" + os.environ.get("PATH", "")
os.environ.setdefault("PYTORCH_ENABLE_MPS_FALLBACK", "1")
VG = Path(__file__).resolve().parent.parent
PROPAINTER = VG / "tools/ProPainter"
RPY = VG / "venvs/roto/bin/python"
SAM2_CKPT = VG / "models/sam2.1_hiera_large.pt"
SAM2_CFG = "configs/sam2.1/sam2.1_hiera_l.yaml"
def run(cmd, **kw):
r = subprocess.run([str(c) for c in cmd], **kw)
if r.returncode != 0:
sys.exit(f"command failed: {' '.join(str(c) for c in cmd)}")
return r
def probe_video(path):
out = subprocess.run(
["ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=width,height,avg_frame_rate",
"-of", "csv=p=0", str(path)], capture_output=True, text=True).stdout.strip()
w, h, fr = out.split(",")[:3]
num, den = fr.split("/")
return int(w), int(h), (float(num) / float(den) if float(den) else 25.0)
def box_mask(w, h, box, dest):
import numpy as np
from PIL import Image
x1, y1, x2, y2 = (int(v) for v in box)
m = np.zeros((h, w), dtype=np.uint8)
m[max(0, y1):min(h, y2), max(0, x1):min(w, x2)] = 255
Image.fromarray(m).save(dest)
def sam2_track_masks(video, points, frame_n, masks_dir):
import numpy as np, torch
from PIL import Image
from sam2.build_sam import build_sam2_video_predictor
frames = masks_dir.parent / "frames"
frames.mkdir(parents=True, exist_ok=True)
if not list(frames.glob("*.jpg")):
run(["ffmpeg", "-hide_banner", "-loglevel", "error", "-y", "-i", video,
"-q:v", "2", "-start_number", "0", frames / "%05d.jpg"])
device = "mps" if torch.backends.mps.is_available() else "cpu"
print(f"SAM2: tracking object on {device} ...")
pred = build_sam2_video_predictor(SAM2_CFG, str(SAM2_CKPT), device=device)
state = pred.init_state(video_path=str(frames), offload_video_to_cpu=True,
offload_state_to_cpu=True)
pc = np.array(points, dtype=np.float32)
pl = np.ones(len(points), dtype=np.int32)
pred.add_new_points_or_box(state, frame_idx=frame_n, obj_id=1, points=pc, labels=pl)
masks_dir.mkdir(exist_ok=True)
n = 0
for fidx, _, logits in pred.propagate_in_video(state):
m = (logits[0] > 0).cpu().numpy().squeeze().astype(np.uint8) * 255
Image.fromarray(m).save(masks_dir / f"{fidx:05d}.png")
n += 1
print(f"SAM2: {n} frames tracked")
def main():
ap = argparse.ArgumentParser()
ap.add_argument("video")
ap.add_argument("--box", help="x1,y1,x2,y2 static region to remove")
ap.add_argument("--point", action="append", default=[], help="x,y on the object (repeatable)")
ap.add_argument("--mask", help="mask PNG (white = remove) or dir of per-frame masks")
ap.add_argument("--frame", type=int, default=0)
ap.add_argument("--resize", type=float, default=1.0, help="inpaint at this scale (0.5 = half res)")
ap.add_argument("--dilate", type=int, default=4)
ap.add_argument("--sub-len", type=int, default=80)
ap.add_argument("--out", default=None)
args = ap.parse_args()
video = Path(args.video).expanduser().resolve()
if not video.exists():
sys.exit(f"no such file: {video}")
stem = video.stem
outdir = Path(args.out).expanduser() if args.out else video.parent / f"{stem}_removed"
work = outdir / "work"
work.mkdir(parents=True, exist_ok=True)
w, h, fps = probe_video(video)
if args.mask:
mask = Path(args.mask).expanduser().resolve()
elif args.box:
mask = work / "box_mask.png"
box_mask(w, h, [float(v) for v in args.box.split(",")], mask)
print(f"static mask for box {args.box}")
elif args.point:
mask = work / "masks"
points = [tuple(float(v) for v in p.split(",")) for p in args.point]
sam2_track_masks(video, points, args.frame, mask)
else:
sys.exit("need --box, --point, or --mask")
print("ProPainter: inpainting (the slow part) ...")
cmd = [RPY, "inference_propainter.py", "--video", str(video), "--mask", str(mask),
"--output", str(work), "--mask_dilation", str(args.dilate),
"--subvideo_length", str(args.sub_len), "--save_fps", str(round(fps))]
if args.resize != 1.0:
cmd += ["--resize_ratio", str(args.resize)]
r = subprocess.run([str(c) for c in cmd], cwd=PROPAINTER)
if r.returncode != 0:
sys.exit("ProPainter failed")
results = sorted((work / stem).glob("inpaint_out.mp4")) or sorted(work.rglob("inpaint_out.mp4"))
if not results:
sys.exit(f"no inpaint_out.mp4 under {work}")
clean = results[-1]
out_mov = outdir / f"{stem}_clean.mov"
out_prev = outdir / f"{stem}_clean_preview.mp4"
print("composing ProRes + preview ...")
# always scale back to source dims: imageio pads ProPainter's output to a
# macro-block multiple (e.g. 804 -> 816) even at native res
scale = f"scale={w}:{h}:flags=bicubic"
run(["ffmpeg", "-hide_banner", "-loglevel", "error", "-y", "-i", clean, "-i", video,
"-filter_complex", f"[0:v]{scale},format=yuv422p10le[v]",
"-map", "[v]", "-map", "1:a:0?", "-c:v", "prores_ks", "-profile:v", "1",
"-c:a", "pcm_s16le", "-shortest", out_mov])
run(["ffmpeg", "-hide_banner", "-loglevel", "error", "-y", "-i", out_mov,
"-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "aac", out_prev])
print(f"\ndone:\n {out_mov}\n {out_prev} <- preview")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,19 @@
diff --git a/cutie/utils/get_default_model.py b/cutie/utils/get_default_model.py
index 3fa5e63..bdf91d5 100644
--- a/cutie/utils/get_default_model.py
+++ b/cutie/utils/get_default_model.py
@@ -20,9 +20,11 @@ def get_default_model() -> CUTIE:
cfg['weights'] = os.path.join(weight_dir, 'cutie-base-mega.pth')
get_dataset_cfg(cfg)
- # Load the network weights
- cutie = CUTIE(cfg).cuda().eval()
- model_weights = torch.load(cfg.weights)
+ # Load the network weights (cuda -> mps -> cpu, like interactive_demo.py)
+ device = ('cuda' if torch.cuda.is_available() else
+ 'mps' if torch.backends.mps.is_available() else 'cpu')
+ cutie = CUTIE(cfg).to(device).eval()
+ model_weights = torch.load(cfg.weights, map_location=device)
cutie.load_weights(model_weights)
return cutie

View File

@ -0,0 +1,25 @@
diff --git a/inference_propainter.py b/inference_propainter.py
index 4d7f92f..b093954 100644
--- a/inference_propainter.py
+++ b/inference_propainter.py
@@ -49,10 +49,16 @@ def resize_frames(frames, size=None):
def read_frame_from_videos(frame_root):
if frame_root.endswith(('mp4', 'mov', 'avi', 'MP4', 'MOV', 'AVI')): # input video path
video_name = os.path.basename(frame_root)[:-4]
- vframes, aframes, info = torchvision.io.read_video(filename=frame_root, pts_unit='sec') # RGB
- frames = list(vframes.numpy())
- frames = [Image.fromarray(f) for f in frames]
- fps = info['video_fps']
+ # torchvision.io.read_video was removed in torchvision >= 0.23 — read via cv2
+ cap = cv2.VideoCapture(frame_root)
+ fps = cap.get(cv2.CAP_PROP_FPS) or 24
+ frames = []
+ while True:
+ ok, frame = cap.read()
+ if not ok:
+ break
+ frames.append(Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)))
+ cap.release()
else:
video_name = os.path.basename(frame_root)
frames = []

68
setup/fetch_phase2.sh Executable file
View File

@ -0,0 +1,68 @@
#!/bin/zsh
# VIDGOD phase 2 — ProPainter (vg-remove), Cutie (vg-cutie), RIFE (vg-interp):
# clones, weights, deps, patches. Idempotent: re-run to resume.
# Heartbeat: ~/.jobs/vidgod-phase2.status
export PATH=/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin
VG="$(cd "$(dirname "$0")/.." && pwd)"
JOB=vidgod-phase2
hb(){ mkdir -p ~/.jobs; echo "$(date '+%F %T') | $1 | $2" > ~/.jobs/$JOB.status; }
die(){ hb "FAILED" "$1"; echo "FATAL: $1" >&2; exit 1; }
get(){ # get URL DEST
[ -s "$2" ] && return 0
curl -sfL --retry 3 -o "$2.part" "$1" || return 1
mv "$2.part" "$2"
}
RPY="$VG/venvs/roto/bin/python"
[ -x "$RPY" ] || die "run setup_venvs.sh first (no roto venv)"
hb "1/8" "clone ProPainter"
[ -d "$VG/tools/ProPainter" ] || git clone --depth 1 https://github.com/sczhou/ProPainter "$VG/tools/ProPainter" || die "propainter clone"
hb "2/8" "ProPainter weights (4 files, ~500MB)"
mkdir -p "$VG/tools/ProPainter/weights"
B=https://github.com/sczhou/ProPainter/releases/download/v0.1.0
get $B/ProPainter.pth "$VG/tools/ProPainter/weights/ProPainter.pth" || die "ProPainter.pth"
get $B/recurrent_flow_completion.pth "$VG/tools/ProPainter/weights/recurrent_flow_completion.pth" || die "rfc.pth"
get $B/raft-things.pth "$VG/tools/ProPainter/weights/raft-things.pth" || die "raft.pth"
get $B/i3d_rgb_imagenet.pt "$VG/tools/ProPainter/weights/i3d_rgb_imagenet.pt" || echo "WARN: i3d optional weight failed"
hb "3/8" "patch ProPainter video reader (torchvision >= 0.23 removed read_video)"
if ! grep -q "CAP_PROP_FPS" "$VG/tools/ProPainter/inference_propainter.py"; then
git -C "$VG/tools/ProPainter" apply "$VG/patches/propainter-cv2-reader.patch" || die "propainter patch"
echo "patch applied"
fi
hb "4/8" "clone Cutie"
[ -d "$VG/tools/Cutie" ] || git clone --depth 1 https://github.com/hkchengrex/Cutie "$VG/tools/Cutie" || die "cutie clone"
hb "5/8" "Cutie weights + device patch"
mkdir -p "$VG/tools/Cutie/weights"
get https://github.com/hkchengrex/Cutie/releases/download/v1.0/cutie-base-mega.pth "$VG/tools/Cutie/weights/cutie-base-mega.pth" || die "cutie weights"
if ! grep -q "mps" "$VG/tools/Cutie/cutie/utils/get_default_model.py"; then
git -C "$VG/tools/Cutie" apply "$VG/patches/cutie-device.patch" || die "cutie patch"
echo "patch applied"
fi
hb "6/8" "RIFE ncnn macos build (universal binary + models, ~450MB zip)"
mkdir -p "$VG/tools/rife"
if [ ! -x "$VG/tools/rife/rife-ncnn-vulkan" ]; then
get https://github.com/nihui/rife-ncnn-vulkan/releases/download/20221029/rife-ncnn-vulkan-20221029-macos.zip /tmp/rife.zip || die "rife zip"
ditto -x -k /tmp/rife.zip "$VG/tools/rife-extract" || die "rife unzip"
inner=$(find "$VG/tools/rife-extract" -name "rife-ncnn-vulkan" -type f | head -1)
[ -n "$inner" ] || die "rife binary not in zip"
cp -R "$(dirname "$inner")"/* "$VG/tools/rife/"
chmod +x "$VG/tools/rife/rife-ncnn-vulkan"
rm -rf "$VG/tools/rife-extract" /tmp/rife.zip
fi
hb "7/8" "python deps (ProPainter + Cutie) into roto venv"
uv pip install --python "$RPY" einops scipy av addict pyyaml future matplotlib \
PySide6 easydict gitpython gdown hickle pycocotools charset-normalizer || die "deps"
uv pip install --python "$RPY" --no-deps -e "$VG/tools/Cutie" || die "cutie -e"
hb "8/8" "smoke: imports + rife arch"
"$RPY" -c "import cutie, matplotlib, einops, scipy; print('phase2 imports ok')" || die "import smoke"
file "$VG/tools/rife/rife-ncnn-vulkan" | grep -q arm64 || echo "WARN: rife binary has no arm64 slice"
hb "DONE" "phase2 tools + weights + patches ready"
echo "PHASE2 FETCH DONE — vg-remove / vg-interp / vg-cutie ready"

View File

@ -39,6 +39,17 @@ else
echo "SKIP: no 'say' binary for speech synthesis" echo "SKIP: no 'say' binary for speech synthesis"
fi fi
if [ -x "$VG/tools/rife/rife-ncnn-vulkan" ]; then
echo "== 4. vg-interp (RIFE 2x on a tiny clip) =="
ffmpeg -hide_banner -loglevel error -y -f lavfi -i "testsrc2=duration=2:size=320x180:rate=25" -c:v libx264 -crf 20 "$T/interp_in.mp4"
"$VG/bin/vg-interp" "$T/interp_in.mp4" --factor 2 --out "$T/interp_out.mov" >/dev/null 2>&1
frames=$(ffprobe -v error -select_streams v:0 -show_entries stream=nb_frames -of csv=p=0 "$T/interp_out.mov" 2>/dev/null)
[ -n "$frames" ] && [ "$frames" -ge 90 ]
check $? "vg-interp doubled the frame count ($frames frames)"
else
echo "SKIP: rife not installed (run setup/fetch_phase2.sh)"
fi
if [ "${1:-}" = "--roto" ]; then if [ "${1:-}" = "--roto" ]; then
echo "== 4. vg-roto (bundled demo clip, full SAM2+MatAnyone) ==" echo "== 4. vg-roto (bundled demo clip, full SAM2+MatAnyone) =="
DEMO="$VG/tools/MatAnyone/inputs/video/test-sample3.mp4" DEMO="$VG/tools/MatAnyone/inputs/video/test-sample3.mp4"