Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename timestamps (and related operations) to container_timestamps #7

Merged
merged 5 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ with plv.Reader(video_path) as video:

# Index frames by time
ts = video[10].time
frame = video.by_timestamp[ts]
frames = video.by_timestamp[ts : ts + 10]
frame = video.by_container_timestamps[ts]
frames = video.by_container_timestamps[ts : ts + 10]
```

You can write video files like this:
Expand Down
16 changes: 2 additions & 14 deletions examples/flexible_video_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ def main(video_path: Path):

# Index frames by time
ts = video[10].time
frame = video.by_timestamp[ts]
frame = video.by_container_timestamps[ts]
assert frame.time == ts and frame.index == 10

frames = video.by_timestamp[ts : ts + 10]
frames = video.by_container_timestamps[ts : ts + 10]
assert frames[0].time == ts and frames[0] == 10

# Read video properties
Expand All @@ -44,18 +44,6 @@ def main(video_path: Path):
num_frames = len(video)
print(f"Number of frames: {num_frames}")

# Use external timestamps for indexing
timestamps = np.arange(num_frames) + 100
with plv.Reader(video_path, timestamps=timestamps) as video:
frame = video[10]
assert frame.time == 10 + 100

frame = video.by_timestamp[10 + 100]
assert frame.time == 10 + 100

frame = video.by_timestamp[10 + 100 : 20 + 100]
assert frame[0].time == 10 + 100


if __name__ == "__main__":
if len(sys.argv) < 2:
Expand Down
6 changes: 4 additions & 2 deletions src/pupil_labs/video/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""pupil_labs.video"""

from pupil_labs.video.array_like import ArrayLike
from pupil_labs.video.frame import AudioFrame, PixelFormat, VideoFrame
from pupil_labs.video.frame import AudioFrame, PixelFormat, ReaderFrameType, VideoFrame
from pupil_labs.video.indexing import Indexer
from pupil_labs.video.multi_reader import MultiReader
from pupil_labs.video.multi_reader import MultiReader, ReaderLike
from pupil_labs.video.reader import Reader
from pupil_labs.video.writer import Writer

Expand All @@ -16,4 +16,6 @@
"Reader",
"VideoFrame",
"Writer",
"ReaderLike",
"ReaderFrameType",
]
4 changes: 3 additions & 1 deletion src/pupil_labs/video/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,6 @@ def av_frame_to_ndarray_fast(
return result


ReaderFrameType = TypeVar("ReaderFrameType", BaseFrame, VideoFrame, AudioFrame)
ReaderFrameType = TypeVar(
"ReaderFrameType", BaseFrame, VideoFrame, AudioFrame, covariant=True
)
2 changes: 1 addition & 1 deletion src/pupil_labs/video/indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from pupil_labs.video.array_like import ArrayLike

IndexerValue = TypeVar("IndexerValue")
IndexerValue = TypeVar("IndexerValue", covariant=True)
IndexerKey = np.uint32 | np.int32 | np.uint64 | np.int64 | np.float64 | int | float
IndexerKeys = npt.NDArray[np.float64 | np.int64] | list[int | float]

Expand Down
38 changes: 17 additions & 21 deletions src/pupil_labs/video/multi_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
from functools import cached_property
from pathlib import Path
from types import TracebackType
from typing import Generic, Iterator, Sequence, overload
from typing import Generic, Iterator, Sequence, cast, overload

import numpy as np

from pupil_labs.video.constants import LAZY_FRAME_SLICE_LIMIT
from pupil_labs.video.frame import AudioFrame, ReaderFrameType, VideoFrame
from pupil_labs.video.frame import AudioFrame, ReaderFrameType
from pupil_labs.video.frame_slice import FrameSlice
from pupil_labs.video.indexing import Indexer, index_key_to_absolute_indices
from pupil_labs.video.reader import Reader, Timestamps
from pupil_labs.video.reader import ContainerTimestamps, Reader

ReaderLike = str | Path | Reader[ReaderFrameType]

Expand Down Expand Up @@ -48,11 +48,11 @@ def __init__(self, *readers: ReaderLike | Sequence[ReaderLike]) -> None:
self.lazy_frame_slice_limit = LAZY_FRAME_SLICE_LIMIT

@cached_property
def timestamps(self) -> Timestamps:
def container_timestamps(self) -> ContainerTimestamps:
all_times = []
for reader_start_time, reader in zip(self.reader_start_times, self.readers):
timestamps = np.array(reader.timestamps) + reader_start_time
all_times.append(timestamps)
for reader_start_time, reader in zip(self._reader_start_times, self.readers):
video_time = np.array(reader.container_timestamps) + reader_start_time
all_times.append(video_time)
return np.concatenate(all_times, dtype=np.float64)

def __len__(self) -> int:
Expand Down Expand Up @@ -82,36 +82,32 @@ def __getitem__(
raise TypeError(f"key must be int or slice, not {type(key)}") from e

try:
reader_slice = next(self.reader_slices_for_key(key))
reader_slice = next(self._reader_slices_for_key(key))
except StopIteration as e:
raise IndexError(f"{key} not found") from e

reader_index = reader_slice.index
reader = self.readers[reader_index]
frame: ReaderFrameType = reader[reader_slice.slice.start]
frame = reader[cast(int, reader_slice.slice.start)]
frame_type = type(frame)
frame_index = frame.index + reader_slice.offset

# if not reader._times_were_provided and reader_index > 0:
# frame_time = frame.time + self.reader_start_times[reader_index]
frame_time = frame.time + self.reader_start_times[reader_index]
frame_time = frame.time + self._reader_start_times[reader_index]

# frame.av_frame.pts = int(frame_time / frame.av_frame.time_base)
output_frame: ReaderFrameType = {
VideoFrame: VideoFrame,
AudioFrame: AudioFrame,
}[type(frame)](
output_frame = frame_type(
av_frame=frame.av_frame,
time=frame_time,
index=frame_index,
source=frame,
)

return output_frame

@cached_property
def reader_start_times(self) -> Timestamps:
def _reader_start_times(self) -> ContainerTimestamps:
return np.cumsum([[0] + [reader.duration for reader in self.readers]])

def reader_slices_for_key(self, key: int | slice) -> Iterator[MultiReaderSlice]:
def _reader_slices_for_key(self, key: int | slice) -> Iterator[MultiReaderSlice]:
start, stop, _ = index_key_to_absolute_indices(key, self) # TODO: handle step
offset = 0
for reader_index, reader in enumerate(self.readers):
Expand All @@ -129,8 +125,8 @@ def reader_slices_for_key(self, key: int | slice) -> Iterator[MultiReaderSlice]:
break

@cached_property
def by_time(self) -> Indexer[ReaderFrameType]:
return Indexer(self.timestamps, self)
def by_container_timestamps(self) -> Indexer[ReaderFrameType]:
return Indexer(self.container_timestamps, self)

def __enter__(self) -> "MultiReader":
return self
Expand Down
Loading
Loading