Files
Kevin 04866559db feat: surgery pipeline API, video inference, voice confirm, and tests
- Add FastAPI routes for surgery start/end, results, pending confirmation (WAV upload), and health checks.
- Implement RTSP/Hikvision capture, consumable classification, session manager, MinIO/Baidu voice resolution, and DB persistence.
- Add documentation (client API, video backends, staging checklist) and sample camera/RTSP config.
- Add pytest suite (API contract, session manager, voice, repositories, pipeline persistence) and httpx dev dependency.
- Replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT.
- Fix SurgeryPipeline DB reads to use an explicit transaction with autobegin disabled.

Made-with: Cursor
2026-04-21 18:33:54 +08:00

57 lines
1.6 KiB
Python

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