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.

Functions

audio_volume(media_path)

Return dict with mean_db and max_db of the media's audio stream.

detect_silence(media_path, *[, noise_db, ...])

Return SilenceSpan``s in the audio of ``media_path via ffmpeg.

extract_frames(media_path, out_dir, *[, ...])

Extract frames from media_path at fps to out_dir.

ssim(a, b)

A global-moment structural-similarity score between two single-channel images.

ssim_image_files(path_a, path_b)

SSIM between two image files (any format Pillow can read).

transcribe(media_path, *[, model_size])

Return the transcribed speech in media_path.

Classes

SilenceSpan(start, end)

A contiguous run of near-silence inside an audio stream.

class an.verify.media.SilenceSpan(start, end)[source]#

Bases: object

A contiguous run of near-silence inside an audio stream.

an.verify.media.audio_volume(media_path)[source]#

Return dict with mean_db and max_db of the media’s audio stream.

Return type:

dict[str, float]

an.verify.media.detect_silence(media_path, *, noise_db=-30.0, min_duration_s=0.3)[source]#

Return SilenceSpan``s in the audio of ``media_path via 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).

Return type:

list[SilenceSpan]

an.verify.media.extract_frames(media_path, out_dir, *, fps=4.0, pattern='frame_%04d.png')[source]#

Extract frames from media_path at fps to out_dir.

Return type:

list[Path]

an.verify.media.ssim(a, b)[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.

Return type:

float

>>> import numpy as np
>>> x = np.zeros((8, 8), dtype=np.float32)
>>> ssim(x, x)
1.0
an.verify.media.ssim_image_files(path_a, path_b)[source]#

SSIM between two image files (any format Pillow can read).

Return type:

float

an.verify.media.transcribe(media_path, *, model_size='tiny')[source]#

Return the transcribed speech in media_path.

Lazily imports faster-whisper. Raises RuntimeError with a clear message when the package isn’t installed.

Return type:

str