From 475c7b097b19d36b7f0846618e7773f2efd7d49f Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sun, 5 Apr 2026 13:11:54 -0400 Subject: [PATCH] docs(specs): camera stream simulator design Dev-only tooling to loop phone-recorded mp4s as local RTSP streams (MediaMTX + ffmpeg) so the real camera pipeline can be exercised without physical cameras. Co-Authored-By: Claude Opus 4.6 (1M context) --- ...26-04-05-camera-stream-simulator-design.md | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-05-camera-stream-simulator-design.md diff --git a/docs/superpowers/specs/2026-04-05-camera-stream-simulator-design.md b/docs/superpowers/specs/2026-04-05-camera-stream-simulator-design.md new file mode 100644 index 0000000..81be523 --- /dev/null +++ b/docs/superpowers/specs/2026-04-05-camera-stream-simulator-design.md @@ -0,0 +1,164 @@ +# Camera Stream Simulator — Design + +**Status:** Approved +**Date:** 2026-04-05 +**Scope:** Development tooling only. Not shipped to operators. + +## Purpose + +Loop phone-recorded video files as local RTSP streams so Vigilar's real camera +pipeline (OpenCV capture, motion detection, HLS, recording, reconnect logic) +can be exercised end-to-end without physical cameras. The operator records +3–5 minutes of footage from each angle on a phone, drops the files into a +`videos/` directory, and runs a single script that serves them as +`rtsp://127.0.0.1:8554/`. + +Primary use case: manual dev testing with 4–5 real clips. Secondary use case: +stress testing with up to 32 distinct clips. + +## Non-Goals + +- Not a production feature. Never invoked in deployed installs. +- Not a pytest fixture. Manual workflow only. +- No per-stream jitter, audio, non-mp4 inputs, or CLI flags. +- No Python rewrite or `vigilar sim` subcommand. + +## Architecture + +One bash script, one helper binary (MediaMTX), one ffmpeg process per stream. + +``` +scripts/sim_cameras.sh + │ + ├── starts: mediamtx (127.0.0.1:8554) ← single static Go binary + ├── reads: config/vigilar.toml ← discovers camera ids + ├── for each [[cameras]] block: + │ expects videos/.mp4 + │ spawns: ffmpeg -stream_loop -1 -re -i videos/.mp4 \ + │ -c copy -f rtsp rtsp://127.0.0.1:8554/ + ├── writes: config/vigilar.sim.toml ← copy of real config, + │ rtsp_urls rewritten + └── foreground; Ctrl-C kills all children +``` + +Operator workflow: + +``` +$ ./scripts/sim_cameras.sh + → serving 5 streams on 127.0.0.1:8554 + → wrote config/vigilar.sim.toml +$ vigilar start --config config/vigilar.sim.toml # in another terminal +``` + +Strict 1:1 mapping — each simulated stream corresponds to exactly one video +file, named after the camera id in `vigilar.toml`. No file reuse, no cycling. + +## Components + +### `scripts/sim_cameras.sh` + +Bash, no Python dependencies. Responsibilities: + +1. Resolve paths: `$REPO/config/vigilar.toml`, `$REPO/videos/`, + `$REPO/.sim/mediamtx`, `$REPO/.sim/logs/`. +2. Check prerequisites: `ffmpeg` on PATH, `mediamtx` binary present, + `videos/` directory exists. +3. Parse camera ids from `vigilar.toml` by grepping `^id = "..."` lines + inside `[[cameras]]` blocks. No TOML parser — tolerable because the + config format is stable and this is dev tooling. +4. Verify `videos/.mp4` exists for every camera id. List **all** + missing files in one error message, then exit 1. +5. Launch `mediamtx` in the background, writing its log to + `.sim/logs/mediamtx.log`. Wait ~500ms, then verify the RTSP port + (8554) is listening (`ss -lnt` or `/dev/tcp`). +6. Launch one ffmpeg process per camera, backgrounded, with stdout/stderr + redirected to `.sim/logs/.log`. Track PIDs in a bash array. +7. Generate `config/vigilar.sim.toml`: copy the real TOML, rewrite each + `rtsp_url = "..."` line to `rtsp://127.0.0.1:8554/` using `sed` + scoped per camera block. +8. Print a summary: each served URL, the sim config path, and + "Press Ctrl-C to stop". +9. Install a `trap` on EXIT/INT/TERM that kills mediamtx and every ffmpeg + child PID, then removes `.sim/`. Idempotent. +10. `wait` on child PIDs so the script blocks until Ctrl-C. + +### `scripts/download_mediamtx.sh` + +Parallel to the existing `scripts/download_model.sh`. Downloads the +MediaMTX static binary for the current architecture from the official +GitHub release into `.sim/mediamtx`, verifies it is executable, and exits. +Run once per machine. + +### `videos/` (gitignored) + +Operator drops `.mp4` files matching camera ids in +`config/vigilar.toml`. Format is mp4 only; other extensions are rejected. + +### `.sim/` (gitignored) + +Working directory, recreated each run: +- `.sim/mediamtx` — the static binary (installed by `download_mediamtx.sh`) +- `.sim/logs/mediamtx.log` +- `.sim/logs/.log` + +### `config/vigilar.sim.toml` (gitignored) + +Generated artifact. Overwritten on every run. Never hand-edited. The real +`config/vigilar.toml` is read-only from the simulator's perspective. + +### MediaMTX + +Single static Go binary. Default configuration is sufficient — listens on +RTSP port 8554, accepts `rtsp://127.0.0.1:8554/` publishes from +ffmpeg. No custom yaml required. + +## Error Handling + +Fail loud and early. No silent fallbacks. + +| Condition | Behavior | +|---|---| +| `ffmpeg` not on PATH | Print install hint, exit 1 | +| `.sim/mediamtx` missing | Print "run scripts/download_mediamtx.sh", exit 1 | +| `videos/` missing or any `videos/.mp4` missing | List **all** missing files, exit 1 | +| MediaMTX fails to bind 8554 | Dump `.sim/logs/mediamtx.log` tail, exit 1 | +| ffmpeg child dies mid-run | Log to stderr, leave others running. Vigilar's reconnect logic handles the gap — this is a feature, not a bug. | +| Ctrl-C / script death | `trap` kills mediamtx + all ffmpeg PIDs, removes `.sim/` | + +## Acceptance Criteria + +Manual verification — this is dev tooling, not production code. + +1. Drop 5 mp4s matching camera ids into `videos/`, run + `./scripts/sim_cameras.sh`. 5 URLs print. +2. `ffprobe rtsp://127.0.0.1:8554/` succeeds for each stream. +3. In another terminal: + `vigilar start --config config/vigilar.sim.toml`. The web UI shows + 5 camera tiles with live HLS. Waving at phone footage triggers motion + events in the event log. +4. Kill one ffmpeg process manually. Vigilar marks that camera as + disconnected; reconnect loop engages. Restart the same ffmpeg + invocation → stream recovers in the UI. +5. Ctrl-C the sim script. `pgrep ffmpeg` and `pgrep mediamtx` both empty. + `.sim/` removed. +6. Scale test: populate 32 dummy mp4s matching 32 camera blocks. Script + starts cleanly; CPU remains within reason (subjective; no hard target). + +### Automated Check + +One light test: `tests/unit/test_sim_script.py` runs +`bash -n scripts/sim_cameras.sh` (syntax check) and, if `shellcheck` is +available, runs it as well. No integration tests — the simulator exists to +support manual testing. + +## File Inventory + +New files: +- `scripts/sim_cameras.sh` +- `scripts/download_mediamtx.sh` +- `tests/unit/test_sim_script.py` + +Modified files: +- `.gitignore` — add `videos/`, `.sim/`, `config/vigilar.sim.toml` + +No changes to `vigilar/` package code. The simulator is fully external.