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