feat(v1): compose temporal fall event pipeline
This commit is contained in:
+18
-5
@@ -18,6 +18,11 @@ class SourceStatus(str, Enum):
|
||||
CLOSED = "closed"
|
||||
|
||||
|
||||
class SourceMode(str, Enum):
|
||||
REPLAY = "replay"
|
||||
STREAM = "stream"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FramePacket:
|
||||
image: Optional[np.ndarray]
|
||||
@@ -35,7 +40,7 @@ class VideoSource:
|
||||
def __init__(
|
||||
self,
|
||||
source: Union[str, Path],
|
||||
reconnect: bool = True,
|
||||
mode: SourceMode,
|
||||
retry_initial_seconds: float = 1.0,
|
||||
retry_max_seconds: float = 16.0,
|
||||
capture_factory: Optional[CaptureFactory] = None,
|
||||
@@ -44,13 +49,15 @@ class VideoSource:
|
||||
raise ValueError("retry_initial_seconds must be positive")
|
||||
if retry_max_seconds < retry_initial_seconds:
|
||||
raise ValueError("retry_max_seconds must not be smaller than retry_initial_seconds")
|
||||
if not isinstance(mode, SourceMode):
|
||||
raise ValueError("mode must be a SourceMode")
|
||||
self._source = str(source)
|
||||
self._reconnect = reconnect
|
||||
self._mode = mode
|
||||
self._retry_initial_seconds = retry_initial_seconds
|
||||
self._retry_max_seconds = retry_max_seconds
|
||||
self._capture_factory = capture_factory or cv2.VideoCapture
|
||||
self._capture = None
|
||||
self._status = SourceStatus.RETRYING if reconnect else SourceStatus.ERROR
|
||||
self._status = SourceStatus.RETRYING if mode is SourceMode.STREAM else SourceStatus.ERROR
|
||||
self._retry_delay_seconds = retry_initial_seconds
|
||||
self._next_retry_at = 0.0
|
||||
self._last_timestamp: Optional[float] = None
|
||||
@@ -77,6 +84,9 @@ class VideoSource:
|
||||
if self._closed:
|
||||
return self._packet(timestamp, SourceStatus.CLOSED, "source is closed")
|
||||
|
||||
if self._mode is SourceMode.REPLAY and self._status is SourceStatus.EOF:
|
||||
return self._packet(timestamp, SourceStatus.EOF, self._last_error)
|
||||
|
||||
if self._capture is None:
|
||||
if self._status is SourceStatus.RETRYING and timestamp < self._next_retry_at:
|
||||
return self._packet(timestamp, SourceStatus.RETRYING, self._last_error)
|
||||
@@ -86,7 +96,7 @@ class VideoSource:
|
||||
success, image = self._capture.read()
|
||||
if not success or image is None:
|
||||
self._release_capture()
|
||||
if self._reconnect:
|
||||
if self._mode is SourceMode.STREAM:
|
||||
self._schedule_retry(timestamp, "frame read failed; retry scheduled")
|
||||
return self._packet(timestamp, SourceStatus.RETRYING, self._last_error)
|
||||
self._status = SourceStatus.EOF
|
||||
@@ -104,7 +114,7 @@ class VideoSource:
|
||||
if capture is None or not capture.isOpened():
|
||||
if capture is not None:
|
||||
capture.release()
|
||||
if self._reconnect:
|
||||
if self._mode is SourceMode.STREAM:
|
||||
self._schedule_retry(now, "unable to open source; retry scheduled")
|
||||
else:
|
||||
self._status = SourceStatus.ERROR
|
||||
@@ -115,6 +125,9 @@ class VideoSource:
|
||||
return True
|
||||
|
||||
def _frame_timestamp(self, fallback_now: float) -> float:
|
||||
if self._mode is SourceMode.STREAM:
|
||||
self._last_timestamp = fallback_now
|
||||
return fallback_now
|
||||
source_seconds = float(self._capture.get(cv2.CAP_PROP_POS_MSEC)) / 1000.0
|
||||
frame_seconds = self._timestamp_from_frame_index()
|
||||
if source_seconds < 0:
|
||||
|
||||
Reference in New Issue
Block a user