#!/usr/bin/env python3 """Reject empty and nearly black captures by decoding pixels and measuring coverage.""" from __future__ import annotations import argparse import math from pathlib import Path import subprocess import sys LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"] def inspect(path: Path) -> tuple[bool, str]: probe = subprocess.run([*LIMIT, "timeout", "30s", "ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error", "-i", str(path), "-frames:v", "1", "-f", "rawvideo", "-pix_fmt", "rgb24", "pipe:1"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30, check=False) if probe.returncode != 0 or not probe.stdout or len(probe.stdout) % 3: return False, f"decode_failed exit={probe.returncode} bytes={len(probe.stdout)}" raw = probe.stdout count = len(raw) // 3 total = 0 square_total = 0 covered = 0 for index in range(0, len(raw), 3): luminance = (77 * raw[index] + 150 * raw[index + 1] + 29 * raw[index + 2]) >> 8 total += luminance square_total += luminance * luminance covered += luminance > 12 mean = total / count variance = max(0.0, square_total / count - mean * mean) deviation = math.sqrt(variance) coverage = covered / count passed = deviation >= 2.0 and coverage >= 0.001 return passed, (f"pixels={count} mean_luma={mean:.3f} stddev_luma={deviation:.3f} " f"coverage_luma_gt12={coverage:.5f} thresholds=(stddev>=2.0,coverage>=0.001)") def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("image", type=Path) args = parser.parse_args() if not args.image.is_file(): print(f"[CaptureQuality] image={args.image} status=FAIL reason=missing") return 1 passed, detail = inspect(args.image) print(f"[CaptureQuality] image={args.image} {detail} status={'PASS' if passed else 'FAIL'}") return 0 if passed else 1 if __name__ == "__main__": raise SystemExit(main())