#!/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 import numpy as np STREAM_DIR = "/media/reala-misaki/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()