Web UI checkbox в Audio source карточке: «вывод аудио в стрим». Off →
TV получает silence + downstream audio chain isolated от live-audio RTSP
(нестабильный sidecar поток больше не блокирует video pipeline).
Изменения:
state.InstanceState: + audio_output_enabled (default True)
config.InstanceCfg:
+ audio_output_volume_target (default volume@output_audio)
+ audio_input_select_target (default astreamselect@audio_input)
dispatch.set_audio_output_enabled(enabled):
enabled=False → astreamselect map=1 (anullsrc) + volume=0
enabled=True → astreamselect map=0 (live-audio) + volume=1
Двойная команда: select decouples upstream, volume гарантирует тишину
на случай если в anullsrc что-то не так.
http_api: + GET /audio-output/{instance}, POST /audio-output/{instance}
static/index.html: + checkbox в Audio source header + loadAudioOut/toggleAudioOut
ZMQ smoke test OK. HTTP roundtrip OK.
Сопутствующий pipeline change: docker-compose.phase7.yml — amix заменён
на astreamselect@audio_input (см. localhost-infra commit).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sync с n7.1-vf-cuda-grid-phase7 filter rework (один cuda_grid + native
runtime layout switching). Изменения:
dispatch._set_layout:
target: layout_filter_target (default cuda_grid@cg, был streamselect@layout)
command: "set_layout <name>" (был "map <index>")
validation: PREDEFINED_LAYOUTS (был inst.layout_map)
dispatch.set_main_cam:
target: main_cam_filter_target (default cuda_grid@cg, был streamselect@main_cam)
command: "cell_map <cell> <pad>" × max_cells
cell 0 = main camera; cells 1..N-1 = identity rotation остальных pads
(исключая main чтобы не дублировать в preview cells)
dispatch.set_mpp_main:
alias к set_main_cam — main_plus_preview тоже использует cell 0 как main.
config.InstanceCfg:
layout_filter_target / main_cam_filter_target / mpp_main_filter_target
default = cuda_grid@cg (Phase 7 single filter)
new max_cells: int = 4 (соответствует filter max_cells option)
layout_map deprecated — оставлен для UI subset visibility (HTTP /layouts/{inst})
http_api.set_layout:
validation против PREDEFINED_LAYOUTS вместо layout_map
Frigate bridge не меняется — set_main_cam подпись та же (instance, cam_index).
Compile-test OK. Pipeline image: gx/cuda-grid-pipeline:phase7. Controller
image rebuild требуется для deploy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Issue: при layout switch overlays исчезали — каждый cuda_grid instance
(quad/single/mpp) имеет свой overlay state. add_overlay шёл только в один
target (cuda_grid@cg) → quad имел overlays, single/mpp без.
Fix:
InstanceCfg.overlay_filter_targets: list[str] (e.g. [cuda_grid@cg,
cuda_grid@cg_s, cuda_grid@cg_m]) — fallback к [filter_target] если empty.
Dispatcher._overlay_broadcast(cmd, arg) — sends к каждому target.
_overlay_add/remove/clear + _reload_icon now use broadcast.
Auto-layout debounce/hysteresis:
FrigateBridgeCfg.auto_hysteresis_sec (default 3.0)
_update_auto_layout schedules debounced apply через asyncio.Task;
каждый новый state event cancels pending timer (reset). Apply только
когда state стабилен N sec — short motion blips не дёргают layout.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Logic update в FrigateBridge._update_auto_layout:
0 active → quad
1 active → single, main_cam = active
2+ active → main_plus_preview, mpp_main = highest priority active
Dispatcher.set_mpp_main — ZMQ streamselect@mpp_main map <index>
Config.mpp_main_filter_target = "streamselect@mpp_main"
При каждом auto-layout change controller отправляет 3 ZMQ:
streamselect@main_cam map N (single layout main)
streamselect@mpp_main map N (mpp layout main, может быть тот же N)
streamselect@layout map L (final layout selector)
Preview cells в mpp остаются fixed mapping (cell1=cam1/front_yard, cell2=cam2/gate_lpr,
cell3=cam3/back_yard). Если main_cam = cam1/2/3 — preview slot этой cam visible duplicate.
Acceptable v2 trade-off (user warned).
Live verified: 4+ active cameras → mpp, gate_lpr в main slot (priority=10 highest).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
FrigateCameraMapping +priority +main_cam_index — для auto-layout decision.
FrigateBridgeCfg.auto_layout flag — toggle через REST.
Logic (FrigateBridge._update_auto_layout):
0 active cameras → quad (default overview)
1+ active → single, main_cam = highest priority active
Equal priority → first active wins (deterministic)
Dispatcher.set_main_cam — ZMQ streamselect@main_cam map <index>
Config.main_cam_filter_target = "streamselect@main_cam"
REST:
GET /auto-layout/{instance} — current toggle state
POST /auto-layout/{instance} — { enabled: bool }
при включении сразу применяет
UI:
+ checkbox "auto" в Layout card — toggleAuto() hits POST /auto-layout
Live verified: enable → immediately picks layout=single, main=gate_lpr
(priority=10, highest active). Visual confirms gate_lpr full screen.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
snapshot_history.py — async periodic capture per instance:
interval_sec / keep_last (FIFO eviction) / dir configurable
GET /snapshots/{instance}?limit=N → list metadata
GET /snapshots/{instance}/{filename} → image bytes
Persisted в /var/lib/cuda-grid/snapshots/{instance}/<ts>.png
layout_map / layout_filter_target в InstanceCfg — для будущей runtime switch
архитектуры (через streamselect либо filter rework — выбор за Phase 7).
Текущий _set_layout dispatches к layout_filter_target c index из map.
UI:
+ Layout buttons (quad/single/main_plus_preview placeholder)
+ Snapshot history grid с thumbnails (loaded /snapshots/{inst}?limit=24)
+ "Reload history" button
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
InstanceCfg.audio_zmq_endpoint (optional) — ZMQ адрес отдельного audio
sidecar ffmpeg. dispatcher._audio_set + _intercom_set теперь используют
_audio_client(inst) — separate ZMQ socket к sidecar.
Fallback: если audio_zmq_endpoint=null → реверт к video pipeline zmq
(Phase 5a single-source behaviour).
Зачем: multi-source audio chain в одном pipeline с video блокирует
video pacing (см. feedback_audio-chain-blocks-video). Split разделяет
A/V на 2 ffmpeg процесса; audio sidecar publish'нет к mediamtx, video
pipeline consume'нет audio как RTSP source + remux к combined output.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Filter использует sscanf("%s") который stops on whitespace — нужно
URL-encode string values (text="hello world" → text=hello%20world).
Filter inline decode'ит %xx.
Также:
tools/smoke_test_overlays.sh — integration test script (manual run
утром когда GPU свободна; сейчас прод-сервисы заняли всю VRAM)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>