# Module: streaming_buffer ## Purpose File-like object backed by a temp file that supports concurrent append (write) and read+seek (read) from separate threads. Designed for true streaming video detection: the HTTP handler appends incoming chunks while the inference thread reads and decodes frames via PyAV — simultaneously, without buffering the entire file in memory. ## Public Interface ### Class: StreamingBuffer | Method | Signature | Description | |--------|-----------|-------------| | `__init__` | `(temp_dir: str \| None = None)` | Creates a temp file in `temp_dir`; opens separate write and read handles | | `append` | `(data: bytes) -> None` | Writes data to temp file, flushes, notifies waiting readers | | `close_writer` | `() -> None` | Signals EOF — wakes all blocked readers | | `read` | `(size: int = -1) -> bytes` | Reads up to `size` bytes; blocks if data not yet available; returns `b""` on EOF | | `seek` | `(offset: int, whence: int = 0) -> int` | Seeks reader position; SEEK_END blocks until EOF is signaled | | `tell` | `() -> int` | Returns current reader position | | `readable` | `() -> bool` | Always returns `True` | | `seekable` | `() -> bool` | Always returns `True` | | `writable` | `() -> bool` | Always returns `False` | | `close` | `() -> None` | Closes both file handles | ### Properties | Property | Type | Description | |----------|------|-------------| | `path` | `str` | Absolute path to the backing temp file | | `written` | `int` | Total bytes appended so far | ## Internal Logic ### Thread Coordination Uses `threading.Condition` to synchronize one writer (HTTP handler) and one reader (PyAV/inference thread): - **append()**: acquires lock → writes to file → flushes → increments `_written` → `notify_all()` → releases lock - **read(size)**: acquires lock → checks if data available → if not and not EOF, calls `wait()` (releases lock, sleeps) → woken by `notify_all()` → calculates bytes to read → releases lock → reads from file (outside lock) - **seek(0, 2)** (SEEK_END): acquires lock → if EOF not signaled, calls `wait()` in loop → once EOF, delegates to `_reader.seek(offset, 2)` The file read itself happens **outside** the lock to avoid holding the lock during I/O. ### File Handle Separation Two independent file descriptors on the same temp file: - `_writer` opened with `"wb"` — append-only, used by the HTTP handler - `_reader` opened with `"rb"` — seekable, used by PyAV On POSIX systems, writes flushed by one fd are immediately visible to reads on another fd of the same inode (shared kernel page cache). `os.rename()` on the path while the reader fd is open is safe — the fd retains access to the underlying inode. ### SEEK_END Behavior When PyAV tries to seek to the end of the file (e.g. to find MP4 moov atom), `seek(0, 2)` blocks until `close_writer()` is called. This provides graceful degradation for non-faststart MP4 files: the decoder waits for the full upload, then processes normally. For faststart MP4/MKV/WebM, SEEK_END is never called and frames are decoded immediately. ## Dependencies - **External**: `os`, `tempfile`, `threading` - **Internal**: none (leaf module) ## Consumers - `main` — creates `StreamingBuffer` in `POST /detect/video`, feeds chunks via `append()`, passes buffer to inference ## Data Models None. ## Configuration None. ## External Integrations None. ## Security None. Temp file permissions follow OS defaults (`tempfile.mkstemp`). ## Tests - `tests/test_az178_streaming_video.py::TestStreamingBuffer` — sequential write/read, blocking read, EOF, concurrent chunked read/write, seek set, seek end blocking, tell, file persistence, written property, seekable/readable flags