Skip to content

video

A video: the frames one physical camera recorded.

One file, one sorted set of parts, or a folder of stills per camera; no geometry and no name (the layout and the rig say which views it carries, the project's manifest what it is called). A video holds no decoder, so there is nothing to close (ADR-0101): open hands back a reader the caller owns, and read opens one for a single frame. One instance per video, hashed by identity, so views of one video share it; paths arrive resolved (ADR-0042).

Video

The frames one camera recorded, as one stream.

Attributes:

Name Type Description
paths

The files it is made of, in stream order.

Source code in src/deeperfly/video.py
class Video:
    """The frames one camera recorded, as one stream.

    Attributes:
        paths: The files it is made of, in stream order.
    """

    def __init__(self, paths: Sequence[str | Path]) -> None:
        """Wrap `paths`, resolved and in stream order, as one video (never empty)."""
        self.paths = tuple(Path(p) for p in paths)
        if not self.paths:
            raise ValueError("a video has no files")
        self._n_frames: int | None = None
        self._fps: float | None = None
        self._size: tuple[int, int] | None = None

    def __repr__(self) -> str:
        """One line: the files it is made of, elided past the first few."""
        shown = ", ".join(p.name for p in self.paths[:3])
        if len(self.paths) > 3:
            shown += f", ... ({len(self.paths)} files)"
        return f"Video({shown})"

    def open(self) -> FrameReader:
        """A new reader over this video, for a forward pass; the caller closes it."""
        return open_reader(list(self.paths))

    def read(self, index: int = 0) -> UInt8[np.ndarray, "H W 3"]:
        """Frame `index`, through a reader opened for it alone, so a seek per call.

        More than a few frames read faster through one reader from
        [open][deeperfly.video.Video.open].
        """
        with self.open() as reader:
            return reader[int(index)]

    @property
    def n_frames(self) -> int:
        """How many frames the video holds, read once.

        Raises:
            ValueError: If the source carries no frame count.
        """
        if self._n_frames is None:
            self._probe()
        assert self._n_frames is not None
        return self._n_frames

    @property
    def fps(self) -> float | None:
        """The frame rate the source declares, or `None` (an image sequence)."""
        if self._n_frames is None:
            self._probe()
        return self._fps

    @property
    def size(self) -> tuple[int, int]:
        """The frame `(height, width)` in pixels, read from the first frame."""
        if self._size is None:
            self._probe()
        assert self._size is not None
        return self._size

    def _probe(self) -> None:
        """Read the count, the rate and the frame size, through one reader."""
        with self.open() as reader:
            count = reader.count()
            if count is None:
                raise ValueError(f"cannot determine the frame count of {self.paths[0]}")
            rate = reader.fps()
            h, w = reader[0].shape[:2]
        self._n_frames = int(count)
        self._fps = float(rate) if rate else None
        self._size = (int(h), int(w))

fps property

fps: float | None

The frame rate the source declares, or None (an image sequence).

n_frames property

n_frames: int

How many frames the video holds, read once.

Raises:

Type Description
ValueError

If the source carries no frame count.

size property

size: tuple[int, int]

The frame (height, width) in pixels, read from the first frame.

__init__

__init__(paths: Sequence[str | Path]) -> None

Wrap paths, resolved and in stream order, as one video (never empty).

Source code in src/deeperfly/video.py
def __init__(self, paths: Sequence[str | Path]) -> None:
    """Wrap `paths`, resolved and in stream order, as one video (never empty)."""
    self.paths = tuple(Path(p) for p in paths)
    if not self.paths:
        raise ValueError("a video has no files")
    self._n_frames: int | None = None
    self._fps: float | None = None
    self._size: tuple[int, int] | None = None

__repr__

__repr__() -> str

One line: the files it is made of, elided past the first few.

Source code in src/deeperfly/video.py
def __repr__(self) -> str:
    """One line: the files it is made of, elided past the first few."""
    shown = ", ".join(p.name for p in self.paths[:3])
    if len(self.paths) > 3:
        shown += f", ... ({len(self.paths)} files)"
    return f"Video({shown})"

open

open() -> FrameReader

A new reader over this video, for a forward pass; the caller closes it.

Source code in src/deeperfly/video.py
def open(self) -> FrameReader:
    """A new reader over this video, for a forward pass; the caller closes it."""
    return open_reader(list(self.paths))

read

read(index: int = 0) -> UInt8[np.ndarray, 'H W 3']

Frame index, through a reader opened for it alone, so a seek per call.

More than a few frames read faster through one reader from open.

Source code in src/deeperfly/video.py
def read(self, index: int = 0) -> UInt8[np.ndarray, "H W 3"]:
    """Frame `index`, through a reader opened for it alone, so a seek per call.

    More than a few frames read faster through one reader from
    [open][deeperfly.video.Video.open].
    """
    with self.open() as reader:
        return reader[int(index)]