Wiki: Home, Architecture, Upload Guide, Highlights API, Encoding, Shortcuts
+43
@@ -0,0 +1,43 @@
|
||||
# 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.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Encoding
|
||||
|
||||
The C41 records HEVC video in AVI containers, which browsers cannot play.
|
||||
The player needs H.264/AAC MP4 copies.
|
||||
|
||||
## The Encoding menu
|
||||
|
||||
Open **Encoding** at the bottom of the page and press **Start**. The server
|
||||
walks the source folder, converts every AVI that has no MP4 yet into
|
||||
`media-mp4`, preserving dated folder structure, four files in parallel.
|
||||
The panel shows completed/skipped/failed counts and finishes with a summary.
|
||||
|
||||
- Existing MP4s are skipped, never re-encoded or overwritten.
|
||||
- Each file converts through a temporary name, so an interrupted encode
|
||||
never corrupts an existing clip or leaves a half-file in the index.
|
||||
- Originals are never modified.
|
||||
- You can keep using the player while encoding runs.
|
||||
|
||||
## What it runs
|
||||
|
||||
```sh
|
||||
ffmpeg -i input.avi -vf scale=854:-2 \
|
||||
-c:v libx264 -preset ultrafast -tune zerolatency -crf 32 \
|
||||
-pix_fmt yuv420p -c:a aac -b:a 64k -movflags +faststart output.mp4
|
||||
```
|
||||
|
||||
## On-the-fly playback
|
||||
|
||||
For a single AVI without an MP4 copy, the player transcodes on the fly at
|
||||
higher quality (CRF 28) when you play it. Fine for one clip, wasteful for
|
||||
hundreds. Use the Encoding menu for bulk.
|
||||
|
||||
## Uploading AVI
|
||||
|
||||
AVI uploads land in the source folder. After uploading a batch, run the
|
||||
Encoding menu once and everything becomes playable.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Highlights API
|
||||
|
||||
Highlights are named time periods saved to `media-mp4/highlights.json`.
|
||||
The API is deliberately simple so an AI agent or script can append
|
||||
detections without touching any video.
|
||||
|
||||
## List
|
||||
|
||||
```sh
|
||||
curl http://127.0.0.1:8765/api/highlights
|
||||
```
|
||||
|
||||
## Create
|
||||
|
||||
With clock times:
|
||||
|
||||
```sh
|
||||
curl -X POST http://127.0.0.1:8765/api/highlights \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"date":"2026-08-27","start_time":"09:26:40","end_time":"09:28:40","title":"Person at entrance"}'
|
||||
```
|
||||
|
||||
With seconds-from-midnight and a duration:
|
||||
|
||||
```sh
|
||||
curl -X POST http://127.0.0.1:8765/api/highlights \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"date":"20260827","start":34116,"duration":120,"title":"Delivery"}'
|
||||
```
|
||||
|
||||
Accepted fields: `date` (YYYY-MM-DD or YYYYMMDD), `start`/`start_time`,
|
||||
`end`/`end_time` or `duration`, `label`/`title` (max 120 chars).
|
||||
Responses return the stored highlight including its assigned `id`.
|
||||
|
||||
## Delete
|
||||
|
||||
```sh
|
||||
curl -X DELETE "http://127.0.0.1:8765/api/highlights?id=3"
|
||||
```
|
||||
|
||||
## In the UI
|
||||
|
||||
Press **Mark start**, then **Mark end** at the end of the interesting part,
|
||||
type a name, press **Add highlight**. Highlights show in yellow on the
|
||||
timeline; click a highlight or the legend label to jump to the next one.
|
||||
+38
-1
@@ -1 +1,38 @@
|
||||
Welcome to the Wiki.
|
||||
# C41 Recorder Player
|
||||
|
||||
A small local web player for CCTV recordings made by a C41 recorder. It turns
|
||||
the recorder's dated folder structure into a 24-hour timeline that is easy to
|
||||
browse, play, and annotate.
|
||||
|
||||
## Pages
|
||||
|
||||
- [[Architecture]] - how the server and UI work internally
|
||||
- [[Upload Guide]] - adding recordings to the archive through the web UI
|
||||
- [[Highlights API]] - marking and automating interesting moments
|
||||
- [[Encoding]] - converting AVI sources to playable MP4s
|
||||
- [[Keyboard Shortcuts]] - quick reference
|
||||
|
||||
## Quick start
|
||||
|
||||
```sh
|
||||
python3 server.py [output-media-folder] --source [source-avi-folder] --port 8765
|
||||
```
|
||||
|
||||
Then open <http://127.0.0.1:8765>.
|
||||
|
||||
## Folder layout
|
||||
|
||||
The recorder stores clips in dated folders:
|
||||
|
||||
```
|
||||
media/
|
||||
20260827/
|
||||
09.28.36.avi
|
||||
09.30.36.avi
|
||||
media-mp4/
|
||||
20260827/
|
||||
09.28.36.mp4
|
||||
```
|
||||
|
||||
Each clip filename is its start time. The timeline places clips by name, not
|
||||
by file metadata, so names must follow `HH.MM.SS` exactly.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Keyboard Shortcuts
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| `Space` | Play / pause |
|
||||
| `←` | Back 10 seconds (crosses clips and gaps) |
|
||||
| `→` | Forward 10 seconds (crosses clips and gaps) |
|
||||
| `M` | Mute / unmute |
|
||||
| `+` | Zoom timeline in |
|
||||
| `-` | Zoom timeline out |
|
||||
|
||||
Clicking the timeline seeks to that moment; clicking a highlight or system
|
||||
event bar jumps to it. The legend labels jump to the next recorded section,
|
||||
highlight, system event, or gap.
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# Upload Guide
|
||||
|
||||
The Upload panel (bottom of the page) adds recordings from your browser.
|
||||
|
||||
## Rules
|
||||
|
||||
- Filenames must be `HH.MM.SS.mp4` or `HH.MM.SS.avi`, e.g. `09.28.36.mp4`.
|
||||
`8.28.36.mp4` also works; it is padded to `08.28.36.mp4` automatically.
|
||||
- MP4 files go into the playable archive (`media-mp4`) and appear on the
|
||||
timeline immediately.
|
||||
- AVI files go into the source folder (`media`), matching where the recorder
|
||||
keeps originals. Run the Encoding menu afterwards to make them playable.
|
||||
- Files land under the date selected in the top-right dropdown. Pick the
|
||||
date first.
|
||||
- Duplicate timestamps are rejected with HTTP 409. Existing recordings are
|
||||
never overwritten.
|
||||
|
||||
## Bulk upload
|
||||
|
||||
Drag a whole batch of files onto the panel or select many in the file
|
||||
dialog. Up to three files upload in parallel, each with its own progress
|
||||
bar. Failed files stay in the queue for retry; successful ones are cleared
|
||||
when the whole batch finishes.
|
||||
|
||||
## API
|
||||
|
||||
For scripts and agents:
|
||||
|
||||
```sh
|
||||
curl -X POST "http://127.0.0.1:8765/api/upload?date=20260827&filename=09.28.36.mp4" \
|
||||
--data-binary @clip.mp4
|
||||
```
|
||||
|
||||
Responses: `200 {"ok": true, "file": "20260827/09.28.36.mp4"}`, `400` for a
|
||||
bad name/date, `409` if that timestamp already exists, `413` over 2 GiB.
|
||||
Reference in New Issue
Block a user