an.verify.media
Media verification helpers — audio + frame quality checks for rendered mp4s.
Phase 8 Tier 2. These complement the existing IR-only LayoutLintVerifier
by inspecting actual mp4 output: silence detection (catches cutoff dialogue),
audio level (catches missing audio), and per-frame perceptual diff via SSIM
(catches “all frames are identical” or “scene drifted between frames”).
Designed to depend only on ffmpeg / ffprobe and numpy (Pillow when
loading frames). No scikit-image, no opencv.
- class an.verify.media.SilenceSpan(start: float, end: float)[source]
A contiguous run of near-silence inside an audio stream.
- an.verify.media.audio_volume(media_path: str | Path) dict[str, float][source]
Return dict with mean_db and max_db of the media’s audio stream.
- an.verify.media.detect_silence(media_path: str | Path, *, noise_db: float = -30.0, min_duration_s: float = 0.3) list[SilenceSpan][source]
Return
SilenceSpan``s in the audio of ``media_pathvia ffmpeg.Wraps
ffmpeg -af silencedetect=...and parses the stderr “silence_start” / “silence_end” lines. Useful for catching dialogue that got cut off (silence at the start/end of a shot when speech was expected).
- an.verify.media.extract_frames(media_path: str | Path, out_dir: str | Path, *, fps: float = 4.0, pattern: str = 'frame_%04d.png') list[Path][source]
Extract frames from
media_pathatfpstoout_dir.
- an.verify.media.ssim(a: ndarray, b: ndarray) float[source]
A global-moment structural-similarity score between two single-channel images.
Not Wang et al.’s SSIM, despite what this docstring said for a long time: that estimator is computed over sliding local windows, and this one uses one global mean, variance and covariance per image. The formula is the same; the reduction is not, and the difference is the whole behaviour. Global moments are blind to a small local change — a total eye-blink scores 0.9989 here — because the flat fill that is most of a cutout frame drags the mean to 1.0.
That blindness is fine for what this function is used for: catching a frozen render, where every pixel is identical or none is. It is not fine as a quality metric, which is why
an.bench.metrics.ssim_map()exists beside it rather than replacing it — MediaQualityVerifier’s frozen-render threshold was tuned against this reduction, and that verifier is in the default orchestrate chain.Inputs are float arrays in [0, 1]. Returns a float in roughly
[-1, 1]; 1 means identical.>>> import numpy as np >>> x = np.zeros((8, 8), dtype=np.float32) >>> ssim(x, x) 1.0