Files
operating-room-monitor-server/app/services/video/rtsp_capture.py

57 lines
1.6 KiB
Python
Raw Normal View History

from __future__ import annotations
import time
from dataclasses import dataclass
from typing import Any
import cv2
import numpy as np
from loguru import logger
@dataclass
class RtspCapture:
"""Thin OpenCV RTSP wrapper (blocking). Use from asyncio via to_thread."""
url: str
open_timeout_sec: float
def __post_init__(self) -> None:
self._cap: cv2.VideoCapture | None = None
def open(self) -> None:
self._cap = cv2.VideoCapture(self.url, cv2.CAP_FFMPEG)
if not self._cap.isOpened():
raise RuntimeError(f"RTSP open failed (isOpened=False): {self.url!r}")
# Reduce internal buffering where supported
try:
self._cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
except Exception:
pass
deadline = time.monotonic() + self.open_timeout_sec
while time.monotonic() < deadline:
ok, frame = self._cap.read()
if ok and frame is not None:
return
time.sleep(0.05)
raise TimeoutError(
f"RTSP first frame timeout after {self.open_timeout_sec}s: {self.url!r}"
)
def read(self) -> tuple[bool, np.ndarray | None]:
if self._cap is None:
return False, None
return self._cap.read()
def release(self) -> None:
if self._cap is not None:
try:
self._cap.release()
except Exception as exc:
logger.debug("VideoCapture.release: {}", exc)
self._cap = None
@property
def cap(self) -> Any:
return self._cap