def read_frame(self, timestamp=None):
        """Reads the next frame or from timestamp.

        If no timestamp is provided this method just returns the next frame from
        the video. This is significantly (up to 10x) faster if the `video_loader`
        backend is available. If a timestamp is provided we first have to seek
        to the right position and then load the frame.

        Args:
            timestamp:
                Specific timestamp of frame in seconds or None (default: None)

        Returns:
            A PIL Image

        Raises:
            StopIteration:
                If end of video is reached and timestamp is None.
            ValueError:
                If provided timestamp is not in self.timestamps.
            VideoError:
                If the frame could not be loaded.

        """
        if not self.timestamps:
            raise EmptyVideoError(f"Cannot load frame from empty video {self.path}.")

        if timestamp is None:
            # Try to read next frame.
            if self.current_index is None:
                # Beginning of video.
                index = 0
                timestamp = self.timestamps[index]
            elif self.current_index >= len(self.timestamps):
                # Reached end of video.
                raise StopIteration()
            else:
                # Read next frame.
                index = self.current_index + 1
                timestamp = self.timestamps[index]
        elif (
            self.current_index is not None
            and self.current_index + 1 < len(self.timestamps)
            and timestamp == self.timestamps[self.current_index + 1]
        ):
            # Provided timestamp is timestamp of next frame.
            index = self.current_index + 1
        else:
            # Random timestamp, must find corresponding index.
            index = self.timestamps.index(timestamp)

        if self.reader:
            # Only seek if we cannot just call next(self.reader).
            if (
                self.current_index is None
                and index != 0
                or self.current_index is not None
                and index != self.current_index + 1
            ):
                self.reader.seek(timestamp)

            # Find next larger timestamp than the one we seek. Used to verify
            # that we did not seek too far in the video and that the correct
            # frame is returned.
            if index + 1 < len(self.timestamps):
                try:
                    next_timestamp = next(
                        ts for ts in self.timestamps[index + 1 :] if ts > timestamp
                    )
                except StopIteration:
                    # All timestamps of future frames are smaller.
                    next_timestamp = float("inf")
            else:
                # Want to load last frame in video.
                next_timestamp = float("inf")

            # Load the frame.
            try:
                while True:
                    frame_info = next(self.reader)
                    if frame_info["pts"] < timestamp - self.eps:
                        # Did not read far enough, let's continue reading more
                        # frames. This can happen due to decreasing timestamps.
                        frame_info = next(self.reader)
                    elif frame_info["pts"] >= next_timestamp:
                        # Accidentally read too far, let's seek back to the
                        # correct position and exit. This can happen due to
                        # imprecise seek.
                        self.reader.seek(timestamp)
                        frame_info = next(self.reader)
                        break
                    else:
                        break
            except StopIteration:
                # Accidentally reached the end of the video, let's seek back to
                # the correction position. This can happen due to imprecise seek.
                self.reader.seek(timestamp)
                try:
                    frame_info = next(self.reader)
                except StopIteration as ex:
                    # Seeking to this timestamp simply doesn't work.
                    raise UnseekableTimestampError(
                        f"Cannot seek to frame with timestamp {float(timestamp)} "
                        f"in {self.path}."
                    ) from ex

            if (
                frame_info["pts"] < timestamp - self.eps
                or frame_info["pts"] >= next_timestamp
            ):
                # We accidentally loaded the wrong frame. This should only
                # happen if self.reader.seek(timestamp) does not seek to the
                # correct timestamp. In this case there is nothing we can do to
                # load the correct frame and we alert the user that something
                # went wrong.
                warnings.warn(
                    f"Loaded wrong frame in {self.path}! Tried to load frame "
                    f"with index {index} and timestamp {float(timestamp)} but "
                    f'could only find frame with timestamp {frame_info["pts"]}.'
                )

            # Make sure we have the tensor in correct shape (we want H x W x C)
            frame = frame_info["data"].permute(1, 2, 0)
            self.current_index = index

        else:  # fallback on pyav
            frame, _, _ = io.read_video(
                self.path,
                start_pts=timestamp,
                end_pts=timestamp,
                pts_unit=self.pts_unit,
            )
            self.current_index = index

        if len(frame.shape) < 3:
            raise FrameShapeError(
                f"Loaded frame has unexpected shape {frame.shape}. "
                f"Frames are expected to have 3 dimensions: (H, W, C)."
            )

        # sometimes torchvision returns multiple frames for one timestamp (bug?)
        if len(frame.shape) > 3 and frame.shape[0] > 1:
            frame = frame[0]

        # make sure we return a H x W x C tensor and not (1 x H x W x C)
        if len(frame.shape) == 4:
            frame = frame.squeeze()

        # convert to PIL image
        image = Image.fromarray(frame.numpy())
        return image
Reads the next frame or from timestamp.

        If no timestamp is provided this method just returns the next frame from
        the video. This is significantly (up to 10x) faster if the `video_loader`
        backend is available. If a timestamp is provided we first have to seek
        to the right position and then load the frame.

        Args:
            timestamp:
                Specific timestamp of frame in seconds or None (default: None)

        Returns:
            A PIL Image

        Raises:
            StopIteration:
                If end of video is reached and timestamp is None.
            ValueError:
                If provided timestamp is not in self.timestamps.
            VideoError:
                If the frame could not be loaded.

        
lightly/data/_video.py