Skip to content

Reader

app.infrastructure.video.reader

View source code

PyAV-based video reader with GPU acceleration, rotation extraction, and frame seeking.

See Also

Layer: Infrastructure. Context: Implements concrete processing and I/O behind application-facing contracts.

ZeroStreamsInVideoException

Bases: VideoStreamStateException

Signal the zero streams in video error condition.

PARAMETER DESCRIPTION
msg

Error message to include in the exception.

TYPE: str

path

Filesystem path of the video or resource.

TYPE: str

__init__(msg: str, path: str) -> None

VideoReader

Opens and decodes video files using PyAV (ffmpeg wrapper) with GPU acceleration and rotation support.

ATTRIBUTE DESCRIPTION
_path

Video file path.

TYPE: str

_container

PyAV container handle.

TYPE: InputContainer | None

_stream

First video stream extracted from the container.

TYPE: VideoStream | None

_current_index

Current sequential frame position.

TYPE: int

_frame_iter

Iterator over decoded frames from the current stream.

_base_rotation

Rotation degrees extracted from video metadata.

TYPE: int

_manual_rotation

User-applied rotation from the UI.

TYPE: int

_w

Final width after total rotation is applied.

TYPE: int

_h

Final height after total rotation is applied.

TYPE: int

Note

Rotation extraction fallback order: 1. stream.metadata.get("rotate"). 2. stream.side_data[...].rotation. 3. Android MP4 fallback via _get_mp4_rotation().

Frame seeking modes: - Sequential path: buffer-friendly for playback or batch processing. - Hard seek path: fast jump to an arbitrary frame via FFmpeg seek. - Post-seek anchor: single-use frame timestamp recovery after a seek. - Initial sequential read: a newly opened reader starts at zero, so the first read_next_frame() returns actual_index == 0.

Rotation handling: - Metadata rotation is extracted when the reader opens. - Additional rotation can be applied through manual_rotation. - Final dimensions use (base_rot + manual_rot) % 360 and swap width and height for 90 or 270 degrees. - Each frame is rotated with np.rot90() and normalized with ascontiguousarray().

VideoReader is not thread-safe. Use a separate instance per decoding thread.

Example
reader = VideoReader("/path/to/video.mp4")
frame = reader.read_frame_at_index(100)
idx, frame = reader.read_next_frame()
metadata = reader.metadata
reader.close()
PARAMETER DESCRIPTION
path

Filesystem path of the video or resource.

TYPE: str

basename property

Return filename only (no directory path).

container: InputContainer deletable property writable

Get PyAV InputContainer (video file handle).

RETURNS DESCRIPTION
InputContainer

Open PyAV container associated with this reader.

RAISES DESCRIPTION
VideoStreamStateException

If no container has been assigned.

fps: float property

Frames per second (playback speed).

RETURNS DESCRIPTION
float

Average frame rate of the selected video stream.

RAISES DESCRIPTION
VideoStreamStateException

If the stream has no average frame rate.

frame_count: int property

Total frames in video (or 0 if unknown).

RETURNS DESCRIPTION
int

Frame count reported by the selected stream, or 0 when unavailable.

height: int property

Final height after rotation applied (may be swapped with width).

RETURNS DESCRIPTION
int

Decoded frame height after base and manual rotations are applied.

manual_rotation: int property writable

Return the user-applied rotation in degrees, normalized to [0, 360).

RETURNS DESCRIPTION
int

Manual rotation offset currently applied to decoded frames.

metadata: VideoMetadata property

Return VideoMetadata snapshot (static; immutable after creation).

RETURNS DESCRIPTION
VideoMetadata

Snapshot containing the source path, rotated dimensions, frame rate,

VideoMetadata

and stream frame count.

path: str property

Return full file path.

RETURNS DESCRIPTION
str

Source path supplied when this reader was created.

path_file_name: str property

Return filename only (split on /).

RETURNS DESCRIPTION
str

Final slash-separated component of the source path.

stream: VideoStream deletable property writable

Get PyAV VideoStream (first video track).

RETURNS DESCRIPTION
VideoStream

Selected PyAV video stream.

RAISES DESCRIPTION
VideoStreamStateException

If no video stream has been assigned.

width: int property

Final width after rotation applied (may be swapped with height).

RETURNS DESCRIPTION
int

Decoded frame width after base and manual rotations are applied.

__init__(path: str) -> None

close() -> None

Close and release video file handle.

open() -> None

Open _path via av.open() with GPU acceleration (hwaccel=auto).

read_frame_at_index(frame_index: int) -> RGBFrame | None

Read frame at frame_index (0-based), using fast or hard seek path.

Note

Three-path logic: - Path 1: sequential buffer iteration when the target is within ±60 frames. - Path 2: hard seek to PTS, then anchor on the first decoded frame timestamp. - Fallback: return None if the frame cannot be reached. A request for index zero decodes and returns the first frame, which is also the frame used by the first export iteration.

Implementation details: - _current_index tracks a pure sequential count and ignores variable frame rate timestamps. - After a seek, timestamp data is used once to re-anchor _current_index. - Subsequent frames switch back to sequential counting. - Permission errors trigger a close, reopen, and retry path.

RETURNS DESCRIPTION
RGBFrame | None

RGBFrame | None: The requested RGB frame, or None on failure.

read_next_frame() -> tuple[int, RGBFrame]

Read next frame sequentially, return (frame_index, rgb_frame).

Note

Increments _current_index by 1, uses pure sequential counting, and returns the current index before incrementing. Since open() resets the counter to zero, the first call returns frame index zero.

RAISES DESCRIPTION
ValueError

If the stream ends and StopIteration is raised.