Cooked I Am

This commit is contained in:
lda committed 2026-01-15 23:21:37 +07:00
1 parent 7b2813be20
commit d57a48eec6
20 files changed
+665 -276

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
"""Stream sources for the pipeline."""
from pipeline.sources.file import FileStreamSource
from pipeline.sources.stream import ThreadedStreamSource
# Alias for backwards compatibility
ThreadedRTSPSource = ThreadedStreamSource
__all__ = [
"FileStreamSource",
"ThreadedStreamSource",
"ThreadedRTSPSource", # Alias
]
+63
View File
@@ -0,0 +1,63 @@
"""File-based video source."""
import glob
import logging
import time
from typing import Iterator
import cv2
from pipeline.protocols import StreamSource
from pipeline.types import Frame
logger = logging.getLogger(__name__)
class FileStreamSource(StreamSource):
"""Reads frames from a video file."""
def __init__(self, path: str):
self.path = path
self._fps = 30.0
# Probe for FPS if it's a single file
if "*" not in self.path:
try:
cap = cv2.VideoCapture(self.path)
if cap.isOpened():
self._fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
cap.release()
except Exception as e:
logger.warning("Could not probe FPS from %s: %s", path, e)
@property
def fps(self) -> float:
return self._fps
def stream(self) -> Iterator[Frame]:
# Handle glob patterns for image sequences
if "*" in self.path:
files = sorted(glob.glob(self.path))
logger.info("Loading %d images from pattern: %s", len(files), self.path)
for frame_id, file in enumerate(files):
img = cv2.imread(file)
if img is not None:
yield Frame(timestamp=time.time(), data=img, frame_id=frame_id)
return
logger.info("Opening video file: %s", self.path)
cap = cv2.VideoCapture(self.path)
frame_id = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret:
logger.info("End of video file reached.")
break
yield Frame(timestamp=time.time(), data=frame, frame_id=frame_id)
frame_id += 1
cap.release()
def stop(self):
"""No-op for file source (stream ends naturally)."""
pass
+92
View File
@@ -0,0 +1,92 @@
"""Threaded source for RTSP streams and webcams."""
import logging
import threading
import time
from queue import Empty, Queue
from typing import Iterator
import cv2
from pipeline.protocols import StreamSource
from pipeline.types import Frame
logger = logging.getLogger(__name__)
class ThreadedStreamSource(StreamSource):
"""
Reads from RTSP stream or webcam in a background thread.
Prevents buffer buildup and allows main processing loop to run
at its own pace without stalling the decoding.
"""
def __init__(self, source: str | int, queue_size: int = 5):
self.source = source
self.queue: Queue[Frame] = Queue(maxsize=queue_size)
self._fps = 30.0
# Probe FPS
try:
cap = cv2.VideoCapture(self.source)
if cap.isOpened():
self._fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
cap.release()
except cv2.error: # pylint: disable=catching-non-exception
logger.warning("Could not probe FPS from source.")
self.stopped = False
self.thread = threading.Thread(target=self._reader_loop, daemon=True)
self.thread.start()
logger.info("Initialized ThreadedStreamSource for %s", source)
@property
def fps(self) -> float:
return self._fps
def _reader_loop(self):
"""Background thread that continuously reads frames."""
logger.info("Reader thread started for %s", self.source)
cap = cv2.VideoCapture(self.source)
frame_id = 0
while not self.stopped:
if not cap.isOpened():
logger.warning("Stream not open, retrying...")
time.sleep(1)
cap = cv2.VideoCapture(self.source)
continue
ret, frame = cap.read()
if not ret:
logger.warning("Failed to read frame, retrying...")
time.sleep(0.1)
continue
# Drop oldest if queue full (keep latest frames)
if self.queue.full():
try:
self.queue.get_nowait()
except Empty:
pass
self.queue.put(Frame(timestamp=time.time(), data=frame, frame_id=frame_id))
frame_id += 1
cap.release()
logger.info("Reader thread stopped.")
def stream(self) -> Iterator[Frame]:
"""Yield frames from the queue."""
while not self.stopped:
try:
yield self.queue.get(timeout=1)
except Empty:
continue
def stop(self):
"""Stop the reader thread and release resources."""
self.stopped = True
if self.thread.is_alive():
self.thread.join(timeout=2.0)