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:
parent
7911a59186
commit
8c84243b61
19
CLAUDE.md
19
CLAUDE.md
@ -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
|
||||
|
||||
30
README.md
30
README.md
@ -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
15
bin/vg-cutie
Executable 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
65
bin/vg-interp
Executable 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
149
bin/vg-remove
Executable 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()
|
||||
19
patches/cutie-device.patch
Normal file
19
patches/cutie-device.patch
Normal 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
|
||||
25
patches/propainter-cv2-reader.patch
Normal file
25
patches/propainter-cv2-reader.patch
Normal 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
68
setup/fetch_phase2.sh
Executable 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"
|
||||
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user