adding to git.i0t.app

This commit is contained in:
Levi Woodard
2026-07-01 18:21:14 -05:00
commit 55a8ea4bee
26 changed files with 3534 additions and 0 deletions

202
README.md Normal file
View File

@@ -0,0 +1,202 @@
# rtsp-streamer
A full-screen RTSP video wall for UniFi Protect cameras, built for a Raspberry
Pi 4 (or any Linux box) plugged into a TV. No Chrome kiosk, no desktop — just
`mpv` tiles under a minimal Wayland compositor, driven by a single Go binary
you configure over SSH from the command line or an interactive TUI.
## Why this design
- **One `mpv` process per camera.** A dead or unreachable camera never takes
down the wall — the daemon restarts only the slot that failed, with backoff.
(Contrast with a single ffmpeg/`mpv` mosaic, where one stalled input can
freeze the whole picture.)
- **Compositor tiling, not fullscreen.** A tiny [sway](https://swaywm.org)
session hosts the windows; the daemon positions each one at an exact pixel
rectangle over sway's IPC. No X11, no browser, no GPU compositor tricks.
- **Hardware decode.** `mpv --hwdec=auto-safe` uses the Pi 4's V4L2/DRM H.264
decoder.
- **Stateful config.** A single hand-editable `config.yaml`, or edit it live
with `rtsp-streamer tui`. Switching layouts is instant and doesn't restart
anything you don't have to.
```
Pi 4 (headless, HDMI -> TV)
┌───────────────────────────────────────────────┐
│ tty1 autologin -> sway -> rtsp-streamer daemon │
│ ├─ reads active layout -> computes grid rects │
│ ├─ spawns 1 mpv per slot (--hwdec, IPC sock) │
│ ├─ swaymsg: float + position each to a cell │
│ └─ health-monitors mpv, restarts dead streams │
│ │
│ control socket ◀── rtsp-streamer layout set … │
│ ◀── rtsp-streamer tui (over SSH) │
└───────────────────────────────────────────────┘
▲ discover
UniFi Protect controller (login -> bootstrap -> RTSPS URLs)
```
## Requirements
On the display device:
- `sway` and `mpv`
- optional: a terminal like `foot` for the emergency keybinding
Debian / Raspberry Pi OS: `sudo apt install sway mpv foot`
Arch: `sudo pacman -S sway mpv foot`
To build: Go 1.23+ (only on your dev machine; the Pi just needs the binary).
## Build
```sh
make build # native binary -> bin/rtsp-streamer
make pi # Raspberry Pi 4, 64-bit OS (arm64) -> bin/rtsp-streamer-arm64
make pi32 # 32-bit Pi OS (armv7) -> bin/rtsp-streamer-armv7
make test
```
## Configure
```sh
rtsp-streamer config init # write a starter ~/.config/rtsp-streamer/config.yaml
export RTSP_STREAMER_PASSWORD=# controller password (kept out of the file)
$EDITOR "$(rtsp-streamer config path)" # set controller.host / username
rtsp-streamer discover # pull cameras + RTSPS URLs from UniFi Protect
rtsp-streamer tui # assign cameras to layout slots, pick active
```
See [`config.example.yaml`](config.example.yaml) for the full schema. The
config path is `$XDG_CONFIG_HOME/rtsp-streamer/config.yaml` (i.e.
`~/.config/rtsp-streamer/config.yaml`), overridable with `--config` or
`$RTSP_STREAMER_CONFIG`.
### Layouts (grid + spanning tiles)
A layout is a base grid (`COLSxROWS`, up to 8×8) on which each camera is a
**tile** that can span multiple cells. That covers a plain 2×2/3×3/4×4 as well
as security-wall arrangements — one big main view plus a column of small ones,
`2+6`, etc. A layout can show up to **16 cameras** (decoding more than that on a
Pi 4 isn't practical even with substreams). Tiles must stay inside the grid and
may not overlap. See `main-plus` in [`config.example.yaml`](config.example.yaml).
### The TUI
`rtsp-streamer tui` is a Bubble Tea configurator meant to be run over SSH:
- **Cameras** — review discovered cameras, `d` disable, `x` delete.
- **Layouts** — edit an arrangement on a live ASCII grid preview.
- **Set active layout** — choose what the wall shows.
- **Discover** / **Discover + enable RTSP (high)** — pull cameras from Protect.
- **Save** — writes the config and tells a running daemon to reload live.
In the **layout editor** the grid is drawn live as you edit:
```
+----------+----------+----------+----------+
|Front Door| · | · |Driveway |
|3x3 | · | · | |
+----------+----------+----------+----------+
| · | · | · |Back Yard |
...
```
- move cursor: arrows or `h`/`j`/`k`/`l`
- `enter` assign a camera to the cell (pick `(empty)` to clear) · `c` clear
- resize the tile under the cursor: `L`/`H` wider/narrower, `J`/`K` taller/shorter
- resize the base grid: `]`/`[` add/remove a column, `}`/`{` add/remove a row
- `esc` back
Elsewhere: arrows / `j`/`k` move, `enter` selects, `esc` goes back, `q` quits.
## Run
```sh
rtsp-streamer daemon # normally launched by sway, not by hand
rtsp-streamer status # slot health from the running daemon
rtsp-streamer layout ls # list layouts (* marks active)
rtsp-streamer layout set quad # switch live (persists the choice)
```
## Deploy to a Pi (boot straight to the wall)
```sh
make pi
scp bin/rtsp-streamer-arm64 pi@display:/tmp/
# on the Pi, put the binary where install.sh expects it and run the installer:
# mv /tmp/rtsp-streamer-arm64 <repo>/bin/rtsp-streamer
sudo ./deploy/install.sh kiosk
```
`install.sh` installs the binary, drops the kiosk sway config at
`/etc/rtsp-streamer/sway/config`, creates the `kiosk` user, enables tty1
autologin, and appends a launcher to the user's `~/.bash_profile` that starts
sway on the physical console only (SSH sessions still get a normal shell).
Reboot and the wall comes up.
Provide the controller password to the daemon by sourcing an env file from the
kiosk user's `~/.bash_profile` **before** the sway launcher, e.g.:
```sh
# ~/.bash_profile (kiosk user)
[ -f ~/.config/rtsp-streamer/env ] && . ~/.config/rtsp-streamer/env
# >>> rtsp-streamer kiosk launcher >>> (added by install.sh)
```
## Enabling RTSP in UniFi Protect
Protect does not expose RTSP until you turn it on **per camera**. You can do it
by hand (Protect → camera → Settings → Advanced → **RTSP**, enable a channel),
or let rtsp-streamer flip it on for you via the API:
```sh
rtsp-streamer discover --enable-rtsp=high # enable the High channel where missing
rtsp-streamer discover --enable-rtsp=low # or the Low substream
```
In the TUI, the menu item **"Discover + enable RTSP (high)"** does the same.
Enabling preserves each channel's other encoder settings — it only flips the
`isRtspEnabled` flag. Plain `discover` (no flag) never modifies your controller;
it just skips cameras with no RTSP-enabled channel and reports which ones.
Use `discover --low-res` (or `--enable-rtsp=low`) to prefer substreams for dense
grids (3x3+), which greatly cuts Pi decode load.
## Troubleshooting
- **Blank/black tile** — verify the stream directly:
`mpv --rtsp-transport=tcp 'rtsps://HOST:7441/ALIAS?enableSrtp'`. If that
fails, RTSP probably isn't enabled for that camera (see above).
- **`swaymsg not found` / windows not placed** — the daemon must run inside a
sway session (started by the kiosk launcher). Check `echo $SWAYSOCK`.
- **High CPU / dropped frames on a 3x3** — switch those cameras to substreams
(`discover --low-res`) and/or add `player.extra_args: ["--vf=fps=15"]`.
- **`login failed`** — use a *local* Protect account (not a Ubiquiti SSO
login) and confirm `controller.host` reaches the UniFi OS console.
## Caveats & scope
- **Unofficial API.** UniFi Protect has no public API; the local endpoints used
here (`/api/auth/login`, `/proxy/protect/api/bootstrap`) are the same stable
ones the Home Assistant integration relies on, but Ubiquiti could change them.
Manual RTSP URLs in `cameras:` work with any camera brand and need no
controller.
- **Linux/Wayland only.** "Other platforms" means other Linux devices (x86 mini
PCs, other SBCs) — anywhere sway + mpv run. It is not a macOS/Windows app.
- **First cut** ships preset grids. Auto-cycling/rotation and free-form
positioning are natural next steps (the daemon already re-tiles on demand).
## Layout
```
cmd/rtsp-streamer/ CLI (cobra): daemon, discover, layout, status, tui, config
internal/config/ YAML load/save/validate, schema, XDG paths
internal/protect/ UniFi Protect client (login, bootstrap, RTSPS URLs)
internal/player/ one supervised mpv per stream, JSON IPC, restart/backoff
internal/compositor/ sway control + NxM grid geometry
internal/daemon/ orchestrator + control socket + health loop
internal/ipc/ control-socket protocol shared by daemon and CLI/TUI
internal/tui/ Bubble Tea configurator
deploy/ sway kiosk config, autologin, install.sh
```