Cameras (esp. an NVR at one IP with many channels) are configured by a channel
number now: channel N → main /Streaming/Channels/{N*100+1}, sub {N*100+2}
(101/102 = ch1, 1101/1102 = ch11, 3201/3202 = ch32). The add/edit form and the
bulk-edit row use a 1-32 dropdown; the camera list shows "канал N". Frontend
derives main_path/sub_path from the channel, so the backend is unchanged.
VERSION 0.0.132.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"Изменить" previously replaced each camera row with a vertical .cam-edit-grid
form inside one colspan cell — effectively a different table. Now bulk edit
keeps the same columns and turns the cells into compact inline inputs:
Камера→name, IP→ip, Потоки→main/sub, Вкл/Запись toggles, Хранилище select,
and the Статус column becomes Логин/Пароль (header swaps via data-bulk-label).
Live per-row onchange is suppressed while bulk is active (changes collected
until Сохранить). Inputs are compact (22px) so row height stays dense.
VERSION 0.0.131.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-row "Хранилище" <select> inherited the base select padding (6px 8px,
~32px tall) and inflated every camera row. Scope a compact rule to .stor-sel
(22px height, matching the toggles) so row height stays dense. Edit/bulk-form
selects are unaffected.
VERSION 0.0.130.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Camera.storage_id: each camera chooses its storage ("" = active/default).
supervisor resolves per-camera (storage_by_id → active fallback); cameras
API (body/payload) + /toggle accept storage_id and repoint the recorder.
- Recordings now go to <root>/<cam>/<YYYY-MM-DD>/<file>.mp4. This ffmpeg build
lacks -strftime_mkdir, so recorder_task pre-creates today+tomorrow dirs at
start and the indexer re-ensures them each tick (survives midnight).
- indexer: scan date subdirs + legacy flat files; sort by basename so the
in-progress segment is detected correctly when both layouts coexist.
- frontend: per-camera "Хранилище" dropdown in the camera table (inline) and
in the add/edit + bulk forms. Move the active-storage summary widget from
the Система tab into the Хранилища tab.
VERSION 0.0.129.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the single fixed /records path with a list of storages and an
active selection (where new recordings go). Quota is now per-storage.
- config: StorageItem(id,name,type,path,quota_gb,nas-meta) + Storage keeps
global retention_days/segment_seconds; active + items. Migration from the
old single {path,quota_gb} schema → one local item.
- recorder: write to the active storage; restart_all() repoints on switch;
NAS-not-mounted guard (never write into the container overlay).
- indexer: scan ALL storage roots so recordings survive an active switch.
- retention: enforce quota PER storage via db.total_size_under /
oldest_recordings_under (path-prefix LIKE).
- api/storages: GET/POST/PUT/DELETE + activate + policy. NAS uses host-mount
(backend stores SMB params, generates the mount/fstab command, reports
ismount status). Retire old POST /api/storage.
- frontend: new "Хранилища" settings tab — table with inline per-row quota,
add form (local folder / NAS SMB), global policy (segment + retention).
- compose: add shared ./storages:/storages root for extra local folders and
host-mounted NAS shares.
VERSION 0.0.127.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a separate `record` field so a camera can be registered but fully
off, or on but not recording:
- enabled (Вкл): camera on/off. Disabled = no go2rtc stream (no live),
no recording; entry is just stored. build_streams/sync_all/reconcile
skip disabled cameras; live grid shows "камера выключена".
- record (REC): write segments to disk. Recorder runs only when
enabled AND record.
Unified POST /api/cameras/{id}/toggle {enabled?, record?} (replaces the
/record endpoint); go2rtc is touched only when `enabled` changes, so
flipping REC doesn't blip live. Camera table gets Вкл + Запись toggle
columns; add/edit forms get both switches.
load_config migrates the old schema (no `record` key): old `enabled`
meant "record" with live always on -> enabled=True, record=old enabled.
VERSION -> 0.0.126.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`enabled` already gated recording only (go2rtc/live work regardless), but
it was a buried "Вкл" checkbox. Make it an explicit REC control:
- New "Запись" column with an inline REC switch in each camera row.
- Relabel the add/edit form toggle "Вкл" -> "REC" + tooltip.
- New POST /api/cameras/{id}/record {enabled} that flips recording through
the supervisor only (no go2rtc re-sync, so live view doesn't blip).
Off = view-only (live via go2rtc); on = record segments to disk.
VERSION -> 0.0.124.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The params row was a ragged flex-wrap and "Готовый профиль" was floated
with margin-left:auto, so controls didn't line up and the preset menu
dangled at an odd height. Split the row into a left grid (auto-fit
columns, controls bottom-aligned, full-width) and a separate right-side
"Готовый профиль" zone. Reorder fields logically: codec, H.264 profile,
preset, rate control, bitrate, QP, keyframe interval. Drop the now-dead
SOFT/MEDIUM/HARD button CSS.
VERSION -> 0.0.123.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The SOFT/MEDIUM/HARD selector lost its color coding when it became a
dropdown. Re-apply colors (SOFT red, MEDIUM yellow, HARD white) to the
options and to the select's shown value, and right-align the "Готовый
профиль" label (margin-left:auto) so it clearly sits on the right.
VERSION -> 0.0.122.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Double-clicking a grid cell now maximizes that camera ("solo") via a CSS
class on the grid instead of putting the tile into the browser Fullscreen
API. In fullscreen the soloed camera fills the screen; double-clicking
again returns to the grid (staying fullscreen). From windowed mode the
gesture also enters fullscreen so the camera fills the screen. ESC exits
fullscreen natively; on exit the solo state is cleared.
VERSION -> 0.0.121.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Style QP <input type=number> like the <select>s (it rendered with the
default white background -> stood out); add a disabled (greyed) style.
- QP vs bitrate are now mutually DISABLED instead of hidden: QP mode
greys the bitrate field, VBR/CBR greys the QP field. Hiding never
worked because .tr-row label{display:flex} overrode [hidden].
- Replace SOFT/MEDIUM/HARD preset buttons with a "Готовый профиль"
dropdown, placed last (rightmost) in the params row.
VERSION -> 0.0.120.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Player/transcoding settings gain encoder controls:
- Rate-control mode VBR / CBR / QP (constant quantizer). x264: VBR uses
-b:v/-maxrate/-bufsize, CBR adds -x264-params nal-hrd=cbr (min=max=b:v),
QP uses -qp N (no bitrate). VP9 maps QP -> -crf N -b:v 0, CBR -> min=max=b:v.
- Preset list expanded to ultrafast/superfast/veryfast/faster/fast/medium
(was only ultrafast/superfast); for VP9 the preset maps to cpu-used.
- New QP field in the UI, shown when rate control = QP, otherwise the
bitrate field is shown.
Both /play.mp4 (archive) and /live.mp4 accept rc + qp query params.
All three modes verified producing valid output on the live restream.
VERSION -> 0.0.119.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per request, move the account widget (current user line + logout button)
to the top of the "Пользователи" tab and remove the standalone Account
tab. Note: the Users tab is superadmin-only, so logout is now reachable
only by superadmin (it is the only logout in the app) -- confirmed
acceptable by the user, who operates as superadmin.
VERSION -> 0.0.118.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Система" tab was a catch-all (Recording, Player & transcoding,
Storage, System info, Account). Split out two dedicated tabs:
- "Плеер": per-browser playback/transcode profiles. Kept at operator+
(the transcode option spawns server-side ffmpeg, i.e. CPU load).
- "Учётная запись": current user + logout. Ungated, so any signed-in
user (incl. viewers) can log out -- previously it was buried inside
the operator-only "Система" tab.
.tabs gets flex-wrap so the now-7 tabs wrap instead of overflowing.
VERSION -> 0.0.117.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The built-in WASM player (frontend/static/vendor/hevcjs/hevc-core.js)
rendered washed-out, green/red-shifted video. Two causes, both fixed:
- VideoFrame path (Chrome): the I420 VideoFrame was created without a
colorSpace, so the browser defaulted to BT.601 limited -> hue shift,
and guessed the range -> washout. Set explicit BT.709, fullRange:false.
- WebGL fallback shader: used BT.709 FULL-range coefficients on
limited-range camera output -> washout/desaturation. Switched to
BT.709 limited-range conversion ((Y-16)*255/219, chroma centered 128).
VERSION -> 0.0.116.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>