annie.media.previews

The preview loader: decodes and encodes preview frames off the event loop (service).

Browse and the Annotator show decoded frames for every visible row. Each row used to start its own decode the moment it was built, with no limit, and encode the frames on the event loop. This module puts all of that behind one small interface:

  • Bounded: at most max_workers decodes run at once, on the loader’s own threads.

  • Newest batch first: requests that arrive together (one page of rows being built) form a batch and start in order, top row first; a newer batch (rows just scrolled into view) goes ahead of older ones, so what is on screen fills first.

  • Skips the unwanted: each request carries a wanted check, asked just before its decode starts. A row that was refreshed or closed while it waited is skipped and gets SKIPPED, without decoding anything.

  • Encoded in the worker: results are WebP data URIs sized for their box, ready to show, so the event loop only assigns them.

  • Cached: a bounded LRU keeps recent results, so a row scrolled back into view shows at once. PreviewLoader.clear() empties it (e.g. on rescan). Errors are never cached.

The decoders are injected (defaulting to annie.media.preview and annie.media.probe.has_audio()), so tests drive the loader with plain fakes.

Attributes

Box

A display box (width, height) in CSS px; frames are encoded to fit it.

BATCH_WINDOW

Seconds within which consecutive requests count as one batch (one page being built).

SKIPPED

Returned instead of a result when the request was no longer wanted when its turn came.

Classes

Strip

A row's frame strip, ready to show.

Tile

A grid tile's single frame, ready to show.

PreviewLoader

Bounded, newest-batch-first, cached decoding of preview frames (see the module docs).

Module Contents

annie.media.previews.Box

A display box (width, height) in CSS px; frames are encoded to fit it.

annie.media.previews.BATCH_WINDOW = 0.05

Seconds within which consecutive requests count as one batch (one page being built).

annie.media.previews.SKIPPED: Final

Returned instead of a result when the request was no longer wanted when its turn came.

class annie.media.previews.Strip[source]

A row’s frame strip, ready to show.

uris

One WebP data URI per strip frame, sized for the requested box.

num_frames

The video’s total frame count.

class annie.media.previews.Tile[source]

A grid tile’s single frame, ready to show.

uri

The WebP data URI of the annotated middle frame.

num_frames

The video’s total frame count.

class annie.media.previews.PreviewLoader(max_workers: int, *, cache_size: int = 300, strip_fn: collections.abc.Callable[[annie.core.models.VideoEntry], tuple[PIL.Image.Image, list[PIL.Image.Image], int]] = build_preview, tile_fn: collections.abc.Callable[[annie.core.models.VideoEntry], tuple[PIL.Image.Image, int]] = build_grid_preview, band_fn: collections.abc.Callable[[pathlib.Path, float, float, int], list[PIL.Image.Image]] = build_band_strip, audio_fn: collections.abc.Callable[[pathlib.Path], bool] = has_audio)[source]

Bounded, newest-batch-first, cached decoding of preview frames (see the module docs).

Every public method must be awaited on the event loop that serves the UI.

Parameters:
  • max_workers – How many decodes may run at once.

  • cache_size – How many results the LRU keeps.

  • strip_fn – Decodes a video’s annotated strip (default build_preview()).

  • tile_fn – Decodes a video’s annotated middle frame (default build_grid_preview()).

  • band_fn – Decodes frames across a time span (default build_band_strip()).

  • audio_fn – Probes for an audio stream (default has_audio()).

async strip(entry: annie.core.models.VideoEntry, box: Box, *, wanted: collections.abc.Callable[[], bool]) → Strip | _Skipped[source]

Decode entry’s annotated strip, encoded for box.

Parameters:
  • entry – The video; its active_track_id is part of the cache key.

  • box – The display box of one strip frame.

  • wanted – Asked just before decoding; False skips the request.

Returns:

The Strip, or SKIPPED.

Raises:

Exception – Whatever the decoder raised (e.g. an unreadable file).

async tile(entry: annie.core.models.VideoEntry, box: Box, *, wanted: collections.abc.Callable[[], bool]) → Tile | _Skipped[source]

Decode entry’s annotated middle frame for a grid tile, encoded for box.

Parameters:
  • entry – The video.

  • box – The tile’s display box.

  • wanted – Asked just before decoding; False skips the request.

Returns:

The Tile, or SKIPPED.

async band(video_path: pathlib.Path, start: float, end: float, count: int, box: Box, *, wanted: collections.abc.Callable[[], bool]) → list[str] | _Skipped[source]

Decode count frames spread over [start, end) seconds, encoded for box.

Parameters:
  • video_path – The video file.

  • start – Span start in seconds.

  • end – Span end in seconds.

  • count – How many frames to sample.

  • box – The display box of one frame.

  • wanted – Asked just before decoding; False skips the request.

Returns:

One data URI per decoded frame, or SKIPPED.

async audio(video_path: pathlib.Path, *, wanted: collections.abc.Callable[[], bool]) → bool | _Skipped[source]

Probe whether video_path has an audio stream (bounded like a decode, not cached).

Parameters:
  • video_path – The video file.

  • wanted – Asked just before probing; False skips the request.

Returns:

Whether an audio stream exists, or SKIPPED.

clear() → None[source]

Forget every cached result (call when the files may have changed).