commit c0cd64901dde1f8cbab99c335614ce9f2e3e88cf
parent b0debd619e7a475d45a8dbca895ca502b671fd05
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Tue, 18 Aug 2026 13:15:58 -0400
make-thumb.mjs: record the background, and fix the scorer ffmpeg 9 broke
Two changes to the same file, both about a thumbnail's BACKGROUND.
`bg` is written beside `bgAt`. A timestamp alone does not say which video it
is a timestamp INTO, and nothing else recorded that -- so four accepted covers
are left with a frame nobody can re-cut. The obvious guess, that a cover's
background is its own song video at bgAt, is false for all four (mean abs diff
53-115 on the corner-free centre strip), and mortal-kombat has bgAt=740.8
against a 148s video. The field is optional exactly as corners[].crop is:
absent means "not recorded", not "no background".
And actionScore's `-vsync 0` is now `-fps_mode passthrough`. ffmpeg 9 removed
-vsync, so every invocation died with "Unrecognized option" -- swallowed by the
caller's catch, which scored 0 for every candidate. "Pick the busiest frame"
had quietly become "pick the first frame that is not dark": the comment two
functions up describes this exact failure from the first time it happened, by a
different route. Measured on the yoshi bed, the picker goes from a flat 0.0
across 24 samples to a best of 15.7, and metal-slug and mortal-kombat move off
a FREE PLAY screen and a ROUND 1 card onto actual gameplay.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat:
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/umtool/song/make-thumb.mjs b/umtool/song/make-thumb.mjs
@@ -111,7 +111,13 @@ const meanLuma = (file, sr) => {
const actionScore = (file, sr) => {
// two frames 0.2s apart, side by side, so one decode gives motion AND colour
const raw = execFileSync("ffmpeg", ["-nostdin", "-v", "error",
- "-ss", String(sr), "-i", file, "-frames:v", 2, "-vsync", "0",
+ // -fps_mode, not -vsync: ffmpeg 9 REMOVED -vsync, and the removal was silent
+ // here because the throw is swallowed by the caller's catch. Every sample
+ // scored 0, so "pick the busiest frame" quietly became "pick the first frame
+ // that is not dark" -- the exact failure the comment above describes, back
+ // again by a different route. A scorer that cannot fail loudly must at least
+ // be spelled in options the installed ffmpeg still has.
+ "-ss", String(sr), "-i", file, "-frames:v", 2, "-fps_mode", "passthrough",
"-vf", "fps=5,scale=160:90,format=rgb24", "-f", "rawvideo", "-"],
{ maxBuffer: 1 << 24 });
const N = 160 * 90 * 3;
@@ -244,7 +250,12 @@ execFileSync("ffmpeg", ["-nostdin", "-v", "error", "-y", ...inputs,
rmSync(tmp, { recursive: true, force: true });
manifest.thumbs[NAME] = {
- out: OUT, bgAt: +bgAt.toFixed(2),
+ // `bg` alongside `bgAt`: a timestamp on its own does not say which video it is
+ // a timestamp INTO, and nothing else recorded that. Four accepted covers were
+ // left with a frame nobody could re-cut -- the obvious guess, that a cover's
+ // background is its own song video, is false for all four. Optional in exactly
+ // the way `corners[].crop` is: absent means "not recorded", not "no background".
+ out: OUT, bg: BG, bgAt: +bgAt.toFixed(2),
// THE BOX IS RECORDED NOW. It used to be computed and thrown away -- the
// script kept only p[3] and p[4] -- so a corner could be cut and never cut
// again the same way, which is the state two accepted corners are in today.