Phase 10/11 WIP — pool + motion-mode + 8×8 templates (rolled back)

Объединённое состояние работ:
  - Phase 10: source pool, motion-driven layout, Frigate motion_pulse,
    zone-filter в pool, --source / --motion-mode CLI
  - Phase 11 Steps 1-3c: 8×8 микро-сетка, JSON templates с asymmetric
    layouts (tpl_1/3/4/5/6/7/8), reload через ZMQ, auto-labels per camera

В прод отдеплоено и откачено: используем gx/cuframes-composer:0.10 как
baseline. Phase 11 продолжится в branch phase11b-cpp как C++ refactor
с ООП-моделью Cell/Layout/Decoration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 21:24:23 +01:00
parent 2d7fc1e640
commit f8e27b9e85
10 changed files with 1097 additions and 152 deletions
+78 -7
View File
@@ -128,6 +128,12 @@ int main(int argc, char **argv)
int frigate_mqtt_port = 1883;
const char *frigate_topic = "frigate/events";
const char *initial_layout = NULL; /* --layout NAME → set_layout после init */
int motion_mode = 0; /* --motion-mode */
int motion_ttl = 45000; /* --motion-ttl ms */
/* --source cuframes_key,frigate=camera_name,priority=N[,zones=z1:z2:...] */
typedef struct { char key[64], frigate[48], zones[128]; int priority; } source_spec_t;
source_spec_t sources[32] = { 0 };
int num_sources = 0;
/* --detection-cell key,camera,dx,dy,dw,dh,detect_w,detect_h[,zone1:zone2:...]
* key — символьное имя для логов (например "parking")
* camera — Frigate camera_key для MQTT match'а ("parking_overview")
@@ -169,10 +175,15 @@ int main(int argc, char **argv)
{"frigate-topic", required_argument, 0, 'T'},
{"detection-cell", required_argument, 0, 'D'},
{"layout", required_argument, 0, 'L'}, /* named layout (quad, single, ...) */
{"source", required_argument, 0, 'S'}, /* pool source: key,frigate=...,priority=N */
{"motion-mode", no_argument, 0, 'm'}, /* enable motion-driven auto layout */
{"motion-ttl", required_argument, 0, 'k'}, /* TTL ms (default 45000) */
{"templates", required_argument, 0, 'z'}, /* path to templates.json */
{0, 0, 0, 0},
};
const char *templates_path = NULL;
int c;
while ((c = getopt_long(argc, argv, "o:c:f:b:W:H:s:r:i:t:C:M:I:U:P:RF:A:G:T:D:L:", opts, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "o:c:f:b:W:H:s:r:i:t:C:M:I:U:P:RF:A:G:T:D:L:S:mk:z:", opts, NULL)) != -1) {
switch (c) {
case 'o': out_path = optarg; break;
case 'c':
@@ -227,6 +238,30 @@ int main(int argc, char **argv)
}
case 'T': frigate_topic = optarg; break;
case 'L': initial_layout = optarg; break;
case 'm': motion_mode = 1; break;
case 'k': motion_ttl = atoi(optarg); break;
case 'z': templates_path = optarg; break;
case 'S': {
if (num_sources >= 32) {
fprintf(stderr, "max 32 sources\n"); return 1;
}
/* Формат: key[,frigate=name][,priority=N]. */
char buf[256]; strncpy(buf, optarg, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
char *tok = strtok(buf, ",");
if (!tok) { fprintf(stderr, "bad --source\n"); return 1; }
strncpy(sources[num_sources].key, tok, sizeof(sources[num_sources].key) - 1);
while ((tok = strtok(NULL, ",")) != NULL) {
if (!strncmp(tok, "frigate=", 8)) {
strncpy(sources[num_sources].frigate, tok + 8,
sizeof(sources[num_sources].frigate) - 1);
} else if (!strncmp(tok, "priority=", 9)) {
sources[num_sources].priority = atoi(tok + 9);
}
}
num_sources++;
break;
}
case 'D': {
if (num_detcells >= MAX_CELLS) { fprintf(stderr, "max %d detcells\n", MAX_CELLS); return 1; }
char buf[512]; strncpy(buf, optarg, sizeof(buf) - 1); buf[sizeof(buf)-1] = '\0';
@@ -340,18 +375,39 @@ int main(int argc, char **argv)
default: return 1;
}
}
if (!out_path || num_cells == 0) {
if (!out_path || (num_cells == 0 && num_sources == 0)) {
fprintf(stderr,
"Использование: %s --out <file.h264> --cell key,x,y,w,h [--cell ...]\n"
" ИЛИ: %s --out <file.h264> --motion-mode --source ... [--source ...]\n"
" [--width 3840] [--height 2160] [--fps 25]\n"
" [--bitrate 10000] [--seconds N]\n",
argv[0]);
" [--bitrate 10000] [--seconds N]\n"
" --source cuframes_key[,frigate=name][,priority=N]\n",
argv[0], argv[0]);
return 1;
}
/* Motion-mode + только --source: создаём placeholder cell (motion_relayout
* перепишет его перед первым кадром). */
if (num_cells == 0 && num_sources > 0) {
strncpy(cell_keys[0], sources[0].key, 63);
cells[0].source_key = cell_keys[0];
cells[0].x = 0; cells[0].y = 0;
cells[0].w = out_w; cells[0].h = out_h;
num_cells = 1;
}
signal(SIGINT, on_sig);
signal(SIGTERM, on_sig);
/* Загружаем templates.json — если файла нет, остаются built-in. */
if (templates_path) {
int n = cfc_layout_load_file(templates_path);
if (n <= 0) {
fprintf(stderr, "[grid_record] templates %s: failed (rc=%d), using built-in\n",
templates_path, n);
}
}
/* CUDA primary context. */
CUresult cr = cuInit(0);
if (cr != CUDA_SUCCESS) { fprintf(stderr, "cuInit: %s\n", cu_err(cr)); return 1; }
@@ -378,9 +434,22 @@ int main(int argc, char **argv)
fprintf(stderr, "[grid_record] composer %dx%d, %d ячеек\n",
out_w, out_h, num_cells);
/* --source: добавить в pool до motion-mode init. Источники cuframes
* стартуют здесь же, до первого compose'а. */
for (int i = 0; i < num_sources; i++) {
cfc_composer_add_pool_source(comp, sources[i].key,
sources[i].frigate[0] ? sources[i].frigate : NULL,
sources[i].priority,
sources[i].zones[0] ? sources[i].zones : NULL);
}
if (motion_mode) {
cfc_composer_set_motion_mode(comp, 1, motion_ttl);
}
/* --layout NAME → applies named layout поверх --cell координат. Удобно
* как default для ONVIF PTZ-управляемого composer'а (старт в quad,
* далее set_layout через ZMQ). */
* далее set_layout через ZMQ). В motion-mode не работает (relayout
* перетирает на каждом кадре). */
if (initial_layout) {
if (cfc_composer_set_layout(comp, initial_layout) != 0) {
fprintf(stderr, "[grid_record] --layout '%s' unknown\n", initial_layout);
@@ -477,13 +546,15 @@ int main(int argc, char **argv)
fprintf(stderr, "\n");
}
/* Frigate MQTT subscriber (если задан --frigate-mqtt). */
/* Frigate MQTT subscriber: запускаем если есть detection-cells
* (overlay'ные bbox'ы) ИЛИ motion-mode (auto-layout drivers). */
cfc_frigate_mqtt_t *frigate = NULL;
if (frigate_mqtt_host && num_detcells > 0) {
if (frigate_mqtt_host && (num_detcells > 0 || motion_mode)) {
cfc_frigate_mqtt_config_t fc = {
.host = frigate_mqtt_host, .port = frigate_mqtt_port,
.username = mqtt_user, .password = mqtt_pass,
.topic = frigate_topic,
.composer = motion_mode ? comp : NULL, /* pulse'ы только в motion-mode */
};
if (cfc_frigate_mqtt_create(&fc, &frigate) == 0) {
for (int i = 0; i < num_detcells; i++) {