chore: import upstream snapshot with attribution
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:35 +08:00
commit 85453da49f
4031 changed files with 710987 additions and 0 deletions
+279
View File
@@ -0,0 +1,279 @@
#!/usr/bin/env node
/*
* matte.cjs — subject matting via hyperframes' built-in `remove-background`
* (rembg-equivalent u2net_human_seg, Apache-2.0, 320×320 input, ~9 fps on
* CoreML). Replaces the previous bundled PP-MattingV2 ONNX (34 MB asset +
* an onnxruntime inference loop in this script): one engine, zero bundled
* weights — the model auto-downloads once (~168 MB) to ~/.cache/hyperframes/.
*
* Semantics note (validated 2026-06-12 on 6 scenes + a cold-start E2E): a
* HUMAN segmenter by intent, not surgically. Thin offset furniture (mic boom
* arms) is usually excluded — captions render over it, behind the person —
* but large salient objects near the subject (a telescope rig) can still
* leak into the matte and occlude captions. Objects HELD by the subject
* (products, phones) may drop out intermittently, letting captions pass in
* front. Never assume: sample frames_fg/ before placing the hero.
*
* Pipeline:
* source.mp4 → hyperframes remove-background → ProRes 4444 .mov (lossless
* alpha; temp, deleted) → ffmpeg fps=<matte.fps> → frames_fg/f_%04d.png.
* frames_bg/ is extracted at the same rate (preview tooling reads it).
*
* node matte.cjs <project-dir>
* Reads: <project>/source.mp4 (any video in the project dir is adopted)
* Writes: <project>/frames_fg/f_%04d.png (RGBA, subject opaque),
* <project>/frames_bg/f_%04d.png, <project>/matte.fps
* Env: HYPERFRAMES_ROOT — hyperframes checkout (default ~/Downloads/hyperframes)
*/
const path = require("path");
const fs = require("fs");
const os = require("os");
const cp = require("child_process");
function hfCli() {
const roots = [
process.env.HYPERFRAMES_ROOT,
path.resolve(__dirname, "..", "..", ".."), // skills/embedded-captions/scripts → repo root if in-repo
path.join(os.homedir(), "Downloads", "hyperframes"),
].filter(Boolean);
for (const root of roots) {
const cli = path.join(root, "packages", "cli", "dist", "cli.js");
if (fs.existsSync(cli)) return cli;
}
console.error("[matte] cannot find hyperframes cli — set HYPERFRAMES_ROOT to a built checkout");
process.exit(3);
}
function ensureSource(project) {
const src = path.join(project, "source.mp4");
if (!fs.existsSync(src)) {
const found = fs
.readdirSync(project)
.filter((f) => /\.(mp4|mov|webm|mkv)$/i.test(f) && !f.startsWith("_"))
.map((f) => path.join(project, f))[0];
if (!found) return src;
try {
fs.symlinkSync(path.basename(found), src);
} catch {
fs.copyFileSync(found, src);
}
console.log(`[matte] resolved source.mp4 -> ${path.basename(found)}`);
}
return src;
}
function rateOf(expr) {
const [n, d] = String(expr || "").split("/");
const f = parseFloat(n) / parseFloat(d || "1");
return Number.isFinite(f) && f > 0 ? f : 0;
}
// Prefer avg_frame_rate (frames/duration — the truth) over r_frame_rate (the
// container's nominal tick rate, which lies on VFR sources: a 24fps screen
// recording can carry r_frame_rate=60 and would 2.5x-desync the matte).
function probeRates(src) {
try {
const out = cp
.execFileSync("ffprobe", [
"-v",
"0",
"-select_streams",
"v:0",
"-show_entries",
"stream=r_frame_rate,avg_frame_rate",
"-of",
"default=nk=1:nw=1",
src,
])
.toString()
.trim()
.split("\n");
return { r: rateOf(out[0]), avg: rateOf(out[1]) };
} catch {
return { r: 0, avg: 0 };
}
}
function probeFps(src) {
const { r, avg } = probeRates(src);
const f = avg || r;
return f > 0 ? Math.max(1, Math.round(f)) : 24;
}
// VFR when nominal and actual disagree by >5% — the remove-background engine
// mishandles VFR timestamps (observed: 2251 fg frames vs 902 bg on one clip),
// so VFR sources get normalized to CFR before matting.
function isVfr(src) {
const { r, avg } = probeRates(src);
return r > 0 && avg > 0 && Math.abs(r - avg) / avg > 0.05;
}
function extractFrames(src, dst, fps, extra = []) {
fs.mkdirSync(dst, { recursive: true });
if (fs.readdirSync(dst).some((f) => f.endsWith(".png"))) return false;
cp.execFileSync(
"ffmpeg",
["-y", "-i", src, "-vf", `fps=${fps}`, ...extra, path.join(dst, "f_%04d.png")],
{ stdio: "ignore" },
);
return true;
}
function countPngs(dir) {
try {
return fs.readdirSync(dir).filter((f) => f.endsWith(".png")).length;
} catch {
return 0;
}
}
async function main() {
const project = path.resolve(process.argv[2] || "");
if (!process.argv[2]) {
console.error("usage: matte.cjs <project-dir>");
process.exit(1);
}
const src = ensureSource(project);
if (!fs.existsSync(src)) {
console.error(`[matte] no source video found in ${project}`);
process.exit(2);
}
const fpsFile = path.join(project, "matte.fps");
// read-with-catch (no exists-then-read TOCTOU): a missing/corrupt fps file
// simply falls through to probing the source.
let fps = 0;
try {
fps = parseInt(fs.readFileSync(fpsFile, "utf8").replace(/\D/g, ""), 10) || 0;
} catch {
/* no cached fps — probe below */
}
if (!fps) fps = probeFps(src);
fs.writeFileSync(fpsFile, String(fps));
const framesBg = path.join(project, "frames_bg");
const framesFg = path.join(project, "frames_fg");
if (extractFrames(src, framesBg, fps))
console.log(`[matte] source fps=${fps} → frames_bg extracted`);
const want = countPngs(framesBg);
if (want > 0 && countPngs(framesFg) >= want) {
console.log(`[matte] frames_fg already complete (${want} frames) — nothing to do`);
return;
}
// 1) subject matte via hyperframes (ProRes 4444 keeps the alpha lossless).
// VFR sources are normalized to CFR first — remove-background trusts
// timestamps and emits a desynced frame count on VFR input (the ghost
// double-subject bug: the pasted matte runs at the wrong speed).
let matteSrc = src;
if (isVfr(src)) {
const cfr = path.join(project, "_src_cfr.mp4");
if (!fs.existsSync(cfr)) {
console.log(
`[matte] VFR source detected (nominal != actual fps) → normalizing to ${fps}fps CFR for matting`,
);
cp.execFileSync(
"ffmpeg",
[
"-y",
"-i",
src,
"-fps_mode",
"cfr",
"-r",
String(fps),
"-c:v",
"libx264",
"-crf",
"16",
"-preset",
"fast",
"-an",
cfr,
],
{ stdio: "ignore" },
);
}
matteSrc = cfr;
}
const mov = path.join(project, "_matte_tmp.mov");
const t0 = Date.now();
const cached = fs.existsSync(
path.join(
os.homedir(),
".cache",
"hyperframes",
"background-removal",
"models",
"u2net_human_seg.onnx",
),
);
console.log(
`[matte] hyperframes remove-background (u2net_human_seg${cached ? "" : "; first run downloads ~168 MB"})… model load takes ~1-2 min with no output — not hung`,
);
const r = cp.spawnSync("node", [hfCli(), "remove-background", matteSrc, "-o", mov], {
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf8",
});
if (r.status !== 0 || !fs.existsSync(mov)) {
console.error("[matte] remove-background FAILED:");
console.error((r.stderr || r.stdout || "").split("\n").slice(-8).join("\n"));
process.exit(4);
}
// 2) burst to RGBA pngs at the project rate
fs.mkdirSync(framesFg, { recursive: true });
cp.execFileSync(
"ffmpeg",
["-y", "-i", mov, "-vf", `fps=${fps}`, "-pix_fmt", "rgba", path.join(framesFg, "f_%04d.png")],
{ stdio: "ignore" },
);
fs.rmSync(mov, { force: true });
// 3) count parity with frames_bg. Composite overlays fg by INDEX at matte.fps,
// so any fg/bg count mismatch is a time desync (subject ghosting). Handle
// BOTH directions: pad when short, linearly remap when long — and shout
// when the mismatch is big enough to mean broken timestamps upstream.
let got = countPngs(framesFg);
const tol = Math.max(2, Math.round(want * 0.01));
if (Math.abs(got - want) > tol) {
console.error(
`[matte] WARN frame-count desync: fg=${got} vs bg=${want} (tolerance ${tol}). ` +
`Reconciling by remap — if the source is VFR this run predates the CFR fix; ` +
`delete frames_fg/ frames_bg/ matte.fps and re-run matte.cjs.`,
);
}
if (got > want && want > 0) {
// keep want frames sampled evenly across got (re-times fg onto the bg timeline)
const keep = [];
for (let j = 1; j <= want; j++)
keep.push(Math.min(got, Math.max(1, Math.round((j - 0.5) * (got / want)))));
const tmp = path.join(project, "_fg_remap");
fs.mkdirSync(tmp, { recursive: true });
keep.forEach((srcIdx, k) => {
fs.copyFileSync(
path.join(framesFg, `f_${String(srcIdx).padStart(4, "0")}.png`),
path.join(tmp, `f_${String(k + 1).padStart(4, "0")}.png`),
);
});
fs.rmSync(framesFg, { recursive: true, force: true });
fs.renameSync(tmp, framesFg);
got = countPngs(framesFg);
}
while (got < want && got > 0) {
fs.copyFileSync(
path.join(framesFg, `f_${String(got).padStart(4, "0")}.png`),
path.join(framesFg, `f_${String(got + 1).padStart(4, "0")}.png`),
);
got++;
}
console.log(
`[matte] done in ${((Date.now() - t0) / 1000).toFixed(1)}s → fg=${got} bg=${want} @ ${fps}fps${got === want ? " (parity ok)" : ""}`,
);
}
main().catch((e) => {
console.error("[matte]", e.message);
process.exit(1);
});