diff --git a/src/camera.py b/src/camera.py index eb27c21..5315bfd 100644 --- a/src/camera.py +++ b/src/camera.py @@ -1,4 +1,5 @@ import logging +import io import shutil import subprocess import threading @@ -23,22 +24,23 @@ SEGMENT_COUNT = 5 # segments to keep in playlist BITRATE = 2_000_000 # 2 Mbps — adjust for bandwidth needs -class PipeOutput: - """Accepts H.264 bytes from picamera2 and writes to a subprocess stdin pipe.""" +class PipeOutput(io.RawIOBase): + """Wraps ffmpeg stdin pipe as a BufferedIOBase-compatible stream.""" def __init__(self, proc: subprocess.Popen[bytes]) -> None: self._proc = proc - def write(self, data: bytes) -> None: - if self._proc.stdin: + def write(self, data: bytes) -> int: # type: ignore[override] + if self._proc.stdin and not self._proc.stdin.closed: try: self._proc.stdin.write(data) + return len(data) except BrokenPipeError: pass + return 0 - def close(self) -> None: - if self._proc.stdin: - self._proc.stdin.close() + def writable(self) -> bool: + return True class Camera: @@ -104,7 +106,8 @@ class Camera: ) self._picam.configure(config) self._encoder = H264Encoder(bitrate=BITRATE) - self._picam.start_recording(self._encoder, FileOutput(self._output)) + buffered = io.BufferedWriter(self._output) + self._picam.start_recording(self._encoder, FileOutput(buffered)) # watch for the playlist to appear — signals first segment is ready self._stop_event.clear() @@ -145,6 +148,9 @@ class Camera: if self._output: self._output.close() + if self._ffmpeg and self._ffmpeg.stdin: + self._ffmpeg.stdin.close() + if self._ffmpeg: try: self._ffmpeg.wait(timeout=5)