Files
Dragon-s-Lair-X68k/tools/analysis/07_motion_survey.py
T
prosolis e565dfbbab Take the disc mount out of the source, so someone else's copy of the media works
Three files hardcoded /media/reala-misaki/BDROM -- extract.py, 07_motion_survey.py
and check.sh -- which made the pipeline unrunnable for anyone whose disc mounts
anywhere else. DLX_BDROM now overrides it everywhere, defaulting to
/media/$USER/BDROM, so this box is unchanged and another one works. Verified by
running extract.py against a symlinked mount at a different path.

check.sh's failure message names the path it looked at and the variable to set,
instead of assuming udisks put it where this machine puts it.

README gains a "Reproducing this" section: no media ships here and none of it is
redistributable, so it says what you have to bring (the disc) and what is
already packaged (vasm is vendored as a binary with its source tarball; the
k-means is hand-rolled, so numpy and Pillow are the whole Python dependency).
It also names the two gates that SKIP rather than fail -- the px68k second-core
pass and the IPL ROM DMAC gate -- because both live outside this repo and a
silent skip is worth reading as a skip.

One trap called out rather than left to bite: scene selection is a hard-coded
stream number, not a search. A different pressing that numbers its .m2ts files
differently will extract the wrong footage and the green light will PASS on it.

check.sh ALL GREEN.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
2026-08-24 18:19:54 -07:00

60 lines
2.5 KiB
Python

#!/usr/bin/env python3
"""Find the worst sustained-motion window in a stream, cheaply.
STATUS lists "a sustained action sequence is the one thing that could still
break the bitrate" as the open risk, and every clip measured so far has been
1.2-1.7 s. Picking a hot clip by eye is how you get a comfortable answer, so
this scans the whole stream instead.
Proxy: mean absolute inter-frame difference at 96x72, decimated to the target
12 fps. It is a proxy, not a bitrate -- but the codec's cost is dominated by
how many blocks fail SKIP, and that is what frame difference measures. The
window it picks then gets encoded for real.
Usage: python3 tools/analysis/07_motion_survey.py 00223 [window_seconds]
"""
import subprocess, sys, os, getpass
import numpy as np
# See tools/encoder/extract.py: DLX_BDROM overrides where the disc is mounted.
BDROM = os.environ.get("DLX_BDROM") or f"/media/{getpass.getuser()}/BDROM"
STREAM_DIR = f"{BDROM}/BDMV/STREAM"
W, H, FPS = 96, 72, 12
def frames(stream):
src = f"{STREAM_DIR}/{stream}.m2ts"
vf = f"fps={FPS},crop=1440:1080:240:0,scale={W}:{H}:flags=bilinear"
p = subprocess.Popen(["ffmpeg", "-v", "error", "-i", src, "-vf", vf,
"-f", "rawvideo", "-pix_fmt", "gray", "-"],
stdout=subprocess.PIPE)
buf = p.stdout.read()
p.wait()
n = len(buf) // (W * H)
return np.frombuffer(buf[:n*W*H], np.uint8).reshape(n, H, W).astype(np.int16)
def main():
stream = sys.argv[1]
win_s = float(sys.argv[2]) if len(sys.argv) > 2 else 10.0
f = frames(stream)
d = np.abs(np.diff(f, axis=0)).mean(axis=(1, 2)) # per-frame motion energy
print(f"{stream}: {len(f)} frames @ {FPS}fps = {len(f)/FPS:.1f}s")
print(f" motion energy mean {d.mean():.2f} median {np.median(d):.2f} "
f"p90 {np.percentile(d,90):.2f} max {d.max():.2f}")
w = int(win_s * FPS)
if len(d) < w:
print("stream shorter than the window"); return
# sustained = highest mean over a sliding window, not the single hottest frame
k = np.convolve(d, np.ones(w) / w, mode="valid")
best = int(np.argmax(k))
print(f" hottest sustained {win_s:.0f}s window: t = {best/FPS:.1f}s "
f"(mean {k[best]:.2f}, {k[best]/d.mean():.2f}x stream mean)")
quiet = int(np.argmin(k))
print(f" quietest {win_s:.0f}s window: t = {quiet/FPS:.1f}s "
f"(mean {k[quiet]:.2f}, {k[quiet]/d.mean():.2f}x stream mean)")
np.save(f"tmp/motion_{stream}.npy", d)
print(f" per-frame energy -> tmp/motion_{stream}.npy")
if __name__ == "__main__":
main()