Coverage for orchestr_ant_ion / streaming / generator.py: 0%
39 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-19 08:36 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-19 08:36 +0000
1"""Helpers for streaming encoded frames."""
3from __future__ import annotations
5import threading
6import time
7from typing import TYPE_CHECKING
9import cv2
10from loguru import logger
13if TYPE_CHECKING:
14 from collections.abc import Iterator
16 from orchestr_ant_ion.streaming.capture import FrameCapture
19_shutdown_event: threading.Event | None = None
22def init_shutdown_event() -> None:
23 """Initialize the global shutdown event for graceful termination."""
24 global _shutdown_event
25 _shutdown_event = threading.Event()
28def request_shutdown() -> None:
29 """Request shutdown of all active frame generators."""
30 global _shutdown_event
31 if _shutdown_event is not None:
32 _shutdown_event.set()
35def is_shutdown_requested() -> bool:
36 """Check if shutdown has been requested."""
37 global _shutdown_event
38 return _shutdown_event is not None and _shutdown_event.is_set()
41def gen_frames(
42 frame_capture: FrameCapture,
43 jpeg_quality: int = 30,
44 wait_for_frame: float = 0.1,
45 wait_on_empty: float = 0.5,
46) -> Iterator[bytes]:
47 """Yield MJPEG frame chunks suitable for multipart responses.
49 This generator will continue yielding frames until shutdown is requested
50 via request_shutdown() or the frame capture stops.
52 Args:
53 frame_capture: The frame capture instance to read from.
54 jpeg_quality: JPEG encoding quality (1-100).
55 wait_for_frame: Time to wait for initial frame in seconds.
56 wait_on_empty: Time to wait when no frame is available in seconds.
58 Yields:
59 MJPEG frame chunks ready for multipart responses.
60 """
61 logger.info("Starting video stream...")
63 init_shutdown_event()
65 while frame_capture.frame_queue.empty():
66 if is_shutdown_requested():
67 logger.info("Shutdown requested during initial frame wait")
68 return
69 logger.debug("Waiting for the first frame...")
70 time.sleep(wait_for_frame)
72 while not is_shutdown_requested():
73 if is_shutdown_requested():
74 logger.info("Shutdown requested, stopping stream")
75 break
77 frame = frame_capture.get_frame()
78 if frame is None:
79 logger.warning("No frame available; skipping frame.")
80 time.sleep(wait_on_empty)
81 continue
83 ret, buffer = cv2.imencode(
84 ".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, jpeg_quality]
85 )
86 if not ret:
87 logger.warning("Frame encoding failed; skipping frame...")
88 continue
89 frame_bytes = buffer.tobytes()
91 yield (
92 b"--frame\r\nContent-Type: image/jpeg\r\n\r\n" + frame_bytes + b"\r\n\r\n"
93 )
95 logger.info("Video stream stopped")