Architecture
One Python file, no dependencies beyond ffmpeg. Intentionally.
server.py
ThreadingHTTPServer bound to 127.0.0.1. Each request is handled by
PlayerHandler:
GET / - serves index.html
GET /api/recordings?date=YYYYMMDD - clips for one day plus the list of all days
GET /video?date=...&name=...&offset=... - streams a clip
POST /api/upload?date=...&filename=... - raw body upload into the archive
POST /api/encode - starts the background encoder
GET /api/encode/status - encoder progress
GET/POST/DELETE /api/highlights - saved highlight periods
GET /api/system-highlights?date=... - events parsed from the recorder's fileindex.dat
The recording index lives in memory (server.recordings), built at startup
by scan_recordings() and updated when uploads arrive. Restarting the server
rescans the folder from disk, which is the only state that matters.
Video streaming
MP4 clips are served with HTTP Range support (send_range_file), so seeking
is instant. AVI clips are transcoded on the fly by an ffmpeg subprocess
(H.264/AAC, fragmented MP4 piped to the client) and killed when the client
disconnects.
Upload safety
Uploads stream to <target>.uploading.mp4 first, then atomically rename.
A dropped connection leaves a temp file, never a truncated clip in the
index. Duplicate timestamps get HTTP 409; nothing is ever overwritten.
index.html
Single file, no framework. A <video> element, a canvas timeline, and a
handful of panels. The timeline draws recorded minutes, highlights, and
system events as colored bars over a 24h axis, with zoom (up to 24x).
Clip transitions: seekBy() and the ended handler walk the clip list so
playback jumps across clip boundaries and empty gaps seamlessly.