123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- """``scenedetect.video_stream`` Module
- This module contains the :class:`VideoStream` class, which provides a library agnostic
- interface for video input. To open a video by path, use :func:`scenedetect.open_video`:
- .. code:: python
- from scenedetect import open_video
- video = open_video('video.mp4')
- while True:
- frame = video.read()
- if frame is False:
- break
- print("Read %d frames" % video.frame_number)
- You can also optionally specify a framerate and a specific backend library to use. Unless specified,
- OpenCV will be used as the video backend. See :mod:`scenedetect.backends` for a detailed example.
- New :class:`VideoStream <scenedetect.video_stream.VideoStream>` implementations can be
- tested by adding it to the test suite in `tests/test_video_stream.py`.
- """
- from abc import ABC, abstractmethod
- from typing import Tuple, Optional, Union
- import numpy as np
- from scenedetect.frame_timecode import FrameTimecode
- class SeekError(Exception):
- """Either an unrecoverable error happened while attempting to seek, or the underlying
- stream is not seekable (additional information will be provided when possible).
- The stream is guaranteed to be left in a valid state, but the position may be reset."""
- class VideoOpenFailure(Exception):
- """Raised by a backend if opening a video fails."""
-
- def __init__(self, message: str = "Unknown backend error."):
- """
- Arguments:
- message: Additional context the backend can provide for the open failure.
- """
- super().__init__(message)
-
- class FrameRateUnavailable(VideoOpenFailure):
- """Exception instance to provide consistent error messaging across backends when the video frame
- rate is unavailable or cannot be calculated. Subclass of VideoOpenFailure."""
- def __init__(self):
- super().__init__('Unable to obtain video framerate! Specify `framerate` manually, or'
- ' re-encode/re-mux the video and try again.')
- class VideoStream(ABC):
- """ Interface which all video backends must implement. """
-
-
-
- @property
- def base_timecode(self) -> FrameTimecode:
- """FrameTimecode object to use as a time base."""
- return FrameTimecode(timecode=0, fps=self.frame_rate)
-
-
-
- @staticmethod
- @abstractmethod
- def BACKEND_NAME() -> str:
- """Unique name used to identify this backend. Should be a static property in derived
- classes (`BACKEND_NAME = 'backend_identifier'`)."""
- raise NotImplementedError
-
-
-
- @property
- @abstractmethod
- def path(self) -> Union[bytes, str]:
- """Video or device path."""
- raise NotImplementedError
- @property
- @abstractmethod
- def name(self) -> Union[bytes, str]:
- """Name of the video, without extension, or device."""
- raise NotImplementedError
- @property
- @abstractmethod
- def is_seekable(self) -> bool:
- """True if seek() is allowed, False otherwise."""
- raise NotImplementedError
- @property
- @abstractmethod
- def frame_rate(self) -> float:
- """Frame rate in frames/sec."""
- raise NotImplementedError
- @property
- @abstractmethod
- def duration(self) -> Optional[FrameTimecode]:
- """Duration of the stream as a FrameTimecode, or None if non terminating."""
- raise NotImplementedError
- @property
- @abstractmethod
- def frame_size(self) -> Tuple[int, int]:
- """Size of each video frame in pixels as a tuple of (width, height)."""
- raise NotImplementedError
- @property
- @abstractmethod
- def aspect_ratio(self) -> float:
- """Pixel aspect ratio as a float (1.0 represents square pixels)."""
- raise NotImplementedError
- @property
- @abstractmethod
- def position(self) -> FrameTimecode:
- """Current position within stream as FrameTimecode.
- This can be interpreted as presentation time stamp, thus frame 1 corresponds
- to the presentation time 0. Returns 0 even if `frame_number` is 1."""
- raise NotImplementedError
- @property
- @abstractmethod
- def position_ms(self) -> float:
- """Current position within stream as a float of the presentation time in
- milliseconds. The first frame has a PTS of 0."""
- raise NotImplementedError
- @property
- @abstractmethod
- def frame_number(self) -> int:
- """Current position within stream as the frame number.
- Will return 0 until the first frame is `read`."""
- raise NotImplementedError
-
-
-
- @abstractmethod
- def read(self, decode: bool = True, advance: bool = True) -> Union[np.ndarray, bool]:
- """Read and decode the next frame as a np.ndarray. Returns False when video ends.
- Arguments:
- decode: Decode and return the frame.
- advance: Seek to the next frame. If False, will return the current (last) frame.
- Returns:
- If decode = True, the decoded frame (np.ndarray), or False (bool) if end of video.
- If decode = False, a bool indicating if advancing to the the next frame succeeded.
- """
- raise NotImplementedError
- @abstractmethod
- def reset(self) -> None:
- """ Close and re-open the VideoStream (equivalent to seeking back to beginning). """
- raise NotImplementedError
- @abstractmethod
- def seek(self, target: Union[FrameTimecode, float, int]) -> None:
- """Seek to the given timecode. If given as a frame number, represents the current seek
- pointer (e.g. if seeking to 0, the next frame decoded will be the first frame of the video).
- For 1-based indices (first frame is frame #1), the target frame number needs to be converted
- to 0-based by subtracting one. For example, if we want to seek to the first frame, we call
- seek(0) followed by read(). If we want to seek to the 5th frame, we call seek(4) followed
- by read(), at which point frame_number will be 5.
- May not be supported on all backend types or inputs (e.g. cameras).
- Arguments:
- target: Target position in video stream to seek to.
- If float, interpreted as time in seconds.
- If int, interpreted as frame number.
- Raises:
- SeekError: An error occurs while seeking, or seeking is not supported.
- ValueError: `target` is not a valid value (i.e. it is negative).
- """
- raise NotImplementedError
|