annie.media.preview

Build Browse preview images for a video (service; needs the media extra).

Produces the static thumbnail (clean first frame) and the annotated five-frame strip (vdet boxes in blue, the protagonist track in green) shown on each Browse row. Decoding is lazy via annie.decode, so importing this module never requires torch; call annie.decode.media_available() before using it.

Attributes

Functions

to_data_uri(→ str)

Encode a PIL image as a self-contained data: WebP URI.

band_frame_indices(→ list[int])

Evenly sample count frame indices across the span [start_sec, end_sec).

build_preview(→ tuple[PIL.Image.Image, ...)

Decode a video's strip and draw its annotations.

build_grid_preview(→ tuple[PIL.Image.Image, int])

Decode a video's middle (½) frame with its annotations drawn on it.

build_band_strip(→ list[PIL.Image.Image])

Decode a plain strip spanning one band's [start_sec, end_sec) window.

report_decode_failure(→ str)

Log a failed frame decode once, and return the reason to show on the frame box.

Module Contents

annie.media.preview.HIDPI_SCALE = 2
annie.media.preview.to_data_uri(image: PIL.Image.Image, box: tuple[int, int] | None = None) → str[source]

Encode a PIL image as a self-contained data: WebP URI.

Embedding the pixels in the element (rather than serving a per-client temp file) means a thumbnail/strip frame never produces an orphaned static route that 404s after a reconnect. The flip side is that every embedded frame is held in memory twice — server-side in the element’s props, and again in the browser tab — for as long as its row is on the page. So the image is first downscaled to the box it will actually be displayed in (times HIDPI_SCALE) and encoded as lossy WebP rather than lossless PNG, which cuts a Browse row’s payload by roughly 15x.

Parameters:
  • image – The image to encode.

  • box – The (width, height) CSS size of the element that will show the image. The image is shrunk to fit HIDPI_SCALE times this, preserving aspect ratio. None encodes at full resolution.

Returns:

A data:image/webp;base64,… string usable as a ui.image source.

annie.media.preview.band_frame_indices(start_sec: float, end_sec: float, fps: float, num_frames: int, count: int = 5) → list[int][source]

Evenly sample count frame indices across the span [start_sec, end_sec).

Converts the band’s seconds to frame indices via fps and picks count positions spread across the span (inclusive of both ends when count > 1), so a clip’s preview strip shows that span rather than the whole video. Indices are clamped into [0, num_frames - 1] and a degenerate or reversed span collapses to the single start frame.

Parameters:
  • start_sec – Span start, in seconds.

  • end_sec – Span end, in seconds.

  • fps – Frames per second of the source video.

  • num_frames – Total frame count, used to clamp indices.

  • count – Number of frames to sample.

Returns:

count (or fewer, when the span is a single frame) frame indices, ascending.

annie.media.preview.build_preview(entry: annie.core.models.VideoEntry, count: int = 5) → tuple[PIL.Image.Image, list[PIL.Image.Image], int][source]

Decode a video’s strip and draw its annotations.

Parameters:
  • entry – The video to preview; entry.video_path must be set.

  • count – Number of strip frames (first / ¼ / ½ / ¾ / last by default).

Returns:

the clean first frame as a thumbnail, a list of annotated strip frames (vdet blue, active track green), and the video’s total frame count.

Return type:

A (thumbnail, strip, num_frames) triple

Raises:
  • ValueError – If the entry has no video to decode.

  • annie.decode.MediaUnavailableError – If the media extra is absent.

annie.media.preview.build_grid_preview(entry: annie.core.models.VideoEntry) → tuple[PIL.Image.Image, int][source]

Decode a video’s middle (½) frame with its annotations drawn on it.

The Browse grid view shows a single static frame per video, so this is the fast counterpart to build_preview(): it decodes only three sample frames in one pass and keeps the middle one. "approximate" seek is safe here because the middle index — unlike the strip’s last frame — never risks the tail overshoot that annie.media.decode.read_strip() guards against with "exact" mode.

Parameters:

entry – The video to preview; entry.video_path must be set.

Returns:

the annotated middle frame (vdet blue, active track green) and the video’s total frame count.

Return type:

An (image, num_frames) pair

Raises:
annie.media.preview.build_band_strip(video_path: str | pathlib.Path, start_sec: float, end_sec: float, count: int = 5) → list[PIL.Image.Image][source]

Decode a plain strip spanning one band’s [start_sec, end_sec) window.

Used by the Segment-review task to preview a clip’s span. Unlike build_preview() this draws no track/vdet overlay — the reviewer is judging the temporal cut, so the raw frames of the span are what matters — and it decodes only the sampled frames of that span rather than the whole video.

Parameters:
  • video_path – The long video the clip belongs to.

  • start_sec – Span start, in seconds.

  • end_sec – Span end, in seconds.

  • count – Number of frames to sample across the span.

Returns:

The sampled span frames as RGB images (empty if nothing could be decoded).

Raises:

annie.media.decode.MediaUnavailableError – If the media extra is absent.

annie.media.preview.report_decode_failure(video: pathlib.Path | str | None, exc: BaseException) → str[source]

Log a failed frame decode once, and return the reason to show on the frame box.

Every Browse row and Annotator strip decodes on its own, so one broken decoder would otherwise fail silently dozens of times, or flood the Log tab. A decoder that cannot load at all is one problem for every video and is logged once per process; any other failure is logged once per video.

Parameters:
  • video – The video that failed to decode.

  • exc – The exception the decode raised.

Returns:

The short reason, for a tooltip on the broken-frame icon.