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).
- 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).
- The MatAnyone torchvision fix lives in `patches/matanyone-cv2-reader.patch`, applied by
setup step 6 (guard: grep CAP_PROP_FPS). Upstream may eventually fix this — if the patch
stops applying, check whether their `read_frame_from_videos` already avoids
`torchvision.io.read_video`, and drop the patch step if so.
- Three tracked patches, all applied idempotently by the setup scripts (grep guards):
`matanyone-cv2-reader.patch` + `propainter-cv2-reader.patch` (torchvision ≥0.23 removed
`read_video` — both repos read via cv2 now) and `cutie-device.patch` (get_default_model
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
(ffmpeg lavfi + macOS `say`) — nothing copyrighted is committed.
- 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-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-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
@ -24,6 +27,7 @@ Needs: Apple Silicon Mac, `brew install ffmpeg uv`, ~4GB disk for venvs + models
```bash
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/fetch_phase2.sh # ProPainter + Cutie + RIFE: clones, weights, deps, patches
./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
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,
MODELBEAST farm operators for roto/index jobs, model mirror to NAS modelzoo. Upscaling
old footage already exists fleet-side (`seedvr2_upscale` on the farm); Resolve's Optical
Flow retime covers slow-mo.
Two operators live in the MODELBEAST repo (`server/operators/`): **`vidgod_roto`**
(gpu lane — video + click points → the three roto outputs as job assets) and
**`vidgod_index`** (cpu lane — index a clip into the node's library; the video is
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"
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
echo "== 4. vg-roto (bundled demo clip, full SAM2+MatAnyone) =="
DEMO="$VG/tools/MatAnyone/inputs/video/test-sample3.mp4"