@camstack/addon-pipeline 1.2.7 → 1.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +2 -3
- package/dist/{chunk-D6vf50IK.js → chunk-emK7D4bc.js} +7 -0
- package/dist/detection-pipeline/index.js +104 -8
- package/dist/detection-pipeline/index.mjs +103 -8
- package/dist/{dist-CAT3VF7A.js → dist-DNjy_sr4.js} +1239 -1058
- package/dist/{dist-BwyGTnMu.mjs → dist-owmpaeIY.mjs} +867 -680
- package/dist/fmp4-keyframes-BzTPoPVn.js +6676 -0
- package/dist/fmp4-keyframes-qOgJBjMO.mjs +6669 -0
- package/dist/{sheet-geometry-DcpuGTCm.mjs → hub-hostname-DAJXlOgV.js} +6 -39
- package/dist/{sheet-geometry-BR-ip0CP.js → hub-hostname-cCknRYKj.mjs} +1 -56
- package/dist/{model-download-service-Cp9f4dk6-CwUb5v3Y.mjs → model-download-service-Cp9f4dk6-WO-foD7W.mjs} +26 -1
- package/dist/{model-download-service-Cp9f4dk6-Dv-oFwjN.js → model-download-service-Cp9f4dk6-fsdDExML.js} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +40 -126
- package/dist/pipeline-runner/index.mjs +38 -124
- package/dist/recorder/index.js +695 -974
- package/dist/recorder/index.mjs +694 -972
- package/dist/session-decode/decode-worker-child.js +9 -5
- package/dist/session-decode/decode-worker-child.mjs +8 -4
- package/dist/{step-definitions-BPEExRug.js → step-definitions-C2bNlww3.js} +1 -1
- package/dist/{step-definitions-DN1obXS7.mjs → step-definitions-CkvctwuS.mjs} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BtN6sZcL.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Bg8aaZzc.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DSfbOjdi.mjs +26 -0
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BdxY9zld.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js--zEZtV2N.mjs} +1 -1
- package/dist/stream-broker/{hostInit-BIZvt2bk.mjs → hostInit-BBLfhYBl.mjs} +3 -3
- package/dist/stream-broker/index.js +646 -10
- package/dist/stream-broker/index.mjs +645 -10
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-3uL2OmvC.js → MaskShapeCanvas-DI4BY7W2-CftzR7Pj.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-JkIkJTeI.js → MotionZonesSettings-NcxxQN8r-DPgVIdEn.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-AKGABxQk.js → PrivacyMaskSettings-APgPLF7p-D0HqiXHW.js} +1 -1
- package/embed-dist/assets/{index-DfcKIHhA.js → index-DRtbV_en.js} +17 -15
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/python/postprocessors/ctc.py +14 -5
- package/python/postprocessors/test_ctc.py +35 -0
- package/dist/chunk-BdkLduGY.mjs +0 -5
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D34ivtL2.mjs +0 -26
|
@@ -45,43 +45,10 @@ function extractHost(raw) {
|
|
|
45
45
|
const host = authority.split(":")[0] ?? "";
|
|
46
46
|
return host.length > 0 ? host : void 0;
|
|
47
47
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Compute the grid + sheet dimensions for `tileCount` tiles. `tileCount` must
|
|
50
|
-
* be ≥ 1 (an empty window produces no sheet). Columns = min(tileCount, maxCols)
|
|
51
|
-
* so a partial window packs tightly instead of leaving a full-width row.
|
|
52
|
-
*/
|
|
53
|
-
function computeSheetGeometry(tileCount, options = {}) {
|
|
54
|
-
if (!Number.isInteger(tileCount) || tileCount < 1) throw new Error(`computeSheetGeometry: tileCount must be a positive integer, got ${tileCount}`);
|
|
55
|
-
const tileWidth = options.tileWidth ?? 480;
|
|
56
|
-
const tileHeight = options.tileHeight ?? 270;
|
|
57
|
-
const maxCols = Math.max(1, options.maxCols ?? 10);
|
|
58
|
-
const cols = Math.min(tileCount, maxCols);
|
|
59
|
-
const rows = Math.ceil(tileCount / cols);
|
|
60
|
-
return {
|
|
61
|
-
cols,
|
|
62
|
-
rows,
|
|
63
|
-
tileWidth,
|
|
64
|
-
tileHeight,
|
|
65
|
-
sheetWidth: cols * tileWidth,
|
|
66
|
-
sheetHeight: rows * tileHeight
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
/** Pixel placement of tile `index` (0-based) in row-major order. */
|
|
70
|
-
function tilePlacement(index, geometry) {
|
|
71
|
-
if (!Number.isInteger(index) || index < 0) throw new Error(`tilePlacement: index must be a non-negative integer, got ${index}`);
|
|
72
|
-
const col = index % geometry.cols;
|
|
73
|
-
const row = Math.floor(index / geometry.cols);
|
|
74
|
-
return {
|
|
75
|
-
index,
|
|
76
|
-
x: col * geometry.tileWidth,
|
|
77
|
-
y: row * geometry.tileHeight
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
/** All tile placements for a full sheet, index 0..tileCount-1. */
|
|
81
|
-
function allTilePlacements(tileCount, geometry) {
|
|
82
|
-
const out = [];
|
|
83
|
-
for (let i = 0; i < tileCount; i += 1) out.push(tilePlacement(i, geometry));
|
|
84
|
-
return out;
|
|
85
|
-
}
|
|
86
48
|
//#endregion
|
|
87
|
-
|
|
49
|
+
Object.defineProperty(exports, "resolveHubHostname", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
get: function() {
|
|
52
|
+
return resolveHubHostname;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
@@ -45,60 +45,5 @@ function extractHost(raw) {
|
|
|
45
45
|
const host = authority.split(":")[0] ?? "";
|
|
46
46
|
return host.length > 0 ? host : void 0;
|
|
47
47
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Compute the grid + sheet dimensions for `tileCount` tiles. `tileCount` must
|
|
50
|
-
* be ≥ 1 (an empty window produces no sheet). Columns = min(tileCount, maxCols)
|
|
51
|
-
* so a partial window packs tightly instead of leaving a full-width row.
|
|
52
|
-
*/
|
|
53
|
-
function computeSheetGeometry(tileCount, options = {}) {
|
|
54
|
-
if (!Number.isInteger(tileCount) || tileCount < 1) throw new Error(`computeSheetGeometry: tileCount must be a positive integer, got ${tileCount}`);
|
|
55
|
-
const tileWidth = options.tileWidth ?? 480;
|
|
56
|
-
const tileHeight = options.tileHeight ?? 270;
|
|
57
|
-
const maxCols = Math.max(1, options.maxCols ?? 10);
|
|
58
|
-
const cols = Math.min(tileCount, maxCols);
|
|
59
|
-
const rows = Math.ceil(tileCount / cols);
|
|
60
|
-
return {
|
|
61
|
-
cols,
|
|
62
|
-
rows,
|
|
63
|
-
tileWidth,
|
|
64
|
-
tileHeight,
|
|
65
|
-
sheetWidth: cols * tileWidth,
|
|
66
|
-
sheetHeight: rows * tileHeight
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
/** Pixel placement of tile `index` (0-based) in row-major order. */
|
|
70
|
-
function tilePlacement(index, geometry) {
|
|
71
|
-
if (!Number.isInteger(index) || index < 0) throw new Error(`tilePlacement: index must be a non-negative integer, got ${index}`);
|
|
72
|
-
const col = index % geometry.cols;
|
|
73
|
-
const row = Math.floor(index / geometry.cols);
|
|
74
|
-
return {
|
|
75
|
-
index,
|
|
76
|
-
x: col * geometry.tileWidth,
|
|
77
|
-
y: row * geometry.tileHeight
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
/** All tile placements for a full sheet, index 0..tileCount-1. */
|
|
81
|
-
function allTilePlacements(tileCount, geometry) {
|
|
82
|
-
const out = [];
|
|
83
|
-
for (let i = 0; i < tileCount; i += 1) out.push(tilePlacement(i, geometry));
|
|
84
|
-
return out;
|
|
85
|
-
}
|
|
86
48
|
//#endregion
|
|
87
|
-
|
|
88
|
-
enumerable: true,
|
|
89
|
-
get: function() {
|
|
90
|
-
return allTilePlacements;
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
Object.defineProperty(exports, "computeSheetGeometry", {
|
|
94
|
-
enumerable: true,
|
|
95
|
-
get: function() {
|
|
96
|
-
return computeSheetGeometry;
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
Object.defineProperty(exports, "resolveHubHostname", {
|
|
100
|
-
enumerable: true,
|
|
101
|
-
get: function() {
|
|
102
|
-
return resolveHubHostname;
|
|
103
|
-
}
|
|
104
|
-
});
|
|
49
|
+
export { resolveHubHostname as t };
|
|
@@ -1,8 +1,33 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import { createReadStream, promises } from "node:fs";
|
|
3
4
|
import * as path$1 from "node:path";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import "node:crypto";
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
17
|
+
key = keys[i];
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
19
|
+
get: ((k) => from[k]).bind(null, key),
|
|
20
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
26
|
+
value: mod,
|
|
27
|
+
enumerable: true
|
|
28
|
+
}) : target, mod));
|
|
29
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
30
|
+
//#endregion
|
|
6
31
|
//#region ../system/dist/model-download-service-Cp9f4dk6.mjs
|
|
7
32
|
/**
|
|
8
33
|
* Map a rel path to one candidate absolute path PER root, keeping only roots the
|
|
@@ -328,4 +353,4 @@ function deleteModelFromDisk(modelsDir, entry, format) {
|
|
|
328
353
|
return true;
|
|
329
354
|
}
|
|
330
355
|
//#endregion
|
|
331
|
-
export { ensureModel as a, downloadFile as i, createFileDataPlaneHandler as n, isModelDownloaded as o, deleteModelFromDisk as r, parseRangeHeader as s, contentTypeFor as t };
|
|
356
|
+
export { ensureModel as a, __commonJSMin as c, downloadFile as i, __require as l, createFileDataPlaneHandler as n, isModelDownloaded as o, deleteModelFromDisk as r, parseRangeHeader as s, contentTypeFor as t, __toESM as u };
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-DNjy_sr4.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
//#region src/motion-wasm/wasm-motion-detector.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as motionDetectionCapability,
|
|
1
|
+
import { A as motionDetectionCapability, G as DeviceType, H as BaseAddon, T as evaluateZoneRules, q as hydrateSchema } from "../dist-owmpaeIY.mjs";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
//#region src/motion-wasm/wasm-motion-detector.ts
|
|
@@ -2,12 +2,12 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_chunk = require("../chunk-
|
|
6
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
+
const require_dist = require("../dist-DNjy_sr4.js");
|
|
7
7
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
8
|
-
const
|
|
8
|
+
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
9
9
|
const require_worker_protocol = require("../worker-protocol-DextwlTX.js");
|
|
10
|
-
const require_step_definitions = require("../step-definitions-
|
|
10
|
+
const require_step_definitions = require("../step-definitions-C2bNlww3.js");
|
|
11
11
|
let node_child_process = require("node:child_process");
|
|
12
12
|
let node_url = require("node:url");
|
|
13
13
|
let sharp = require("sharp");
|
|
@@ -1279,7 +1279,7 @@ function startGuardedSessionDecode(deps) {
|
|
|
1279
1279
|
/** {@link RETAINED_FRAME_BUDGET_MB} in bytes. */
|
|
1280
1280
|
var RETAINED_FRAME_BUDGET_BYTES = (() => {
|
|
1281
1281
|
const raw = Number(process.env["CAMSTACK_SESSION_RETAIN_MB"]);
|
|
1282
|
-
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) :
|
|
1282
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 160;
|
|
1283
1283
|
})() * 1024 * 1024;
|
|
1284
1284
|
/**
|
|
1285
1285
|
* How many of each device's MOST RECENT frames are protected from a busy
|
|
@@ -1299,6 +1299,24 @@ var RETAINED_FRAME_PER_DEVICE_FLOOR = (() => {
|
|
|
1299
1299
|
const raw = Number(process.env["CAMSTACK_SESSION_RETAIN_FLOOR"]);
|
|
1300
1300
|
return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 16;
|
|
1301
1301
|
})();
|
|
1302
|
+
/**
|
|
1303
|
+
* TIME-based per-device protection (2026-07-23 cold-window fix): a frame
|
|
1304
|
+
* YOUNGER than this survives budget eviction regardless of how many frames
|
|
1305
|
+
* its device holds. The COUNT floor above protects a fixed number of frames —
|
|
1306
|
+
* but the wall-clock window that buys depends on the delivered fps (16 frames
|
|
1307
|
+
* = 1.6 s @10 fps but only 0.64 s @25 fps, and the 4K street cams deliver
|
|
1308
|
+
* ~25). The late-capture horizon is TIME (session attach + enrichment +
|
|
1309
|
+
* round-trip ≈ 0.5–1.5 s, worst on cold on-motion dials), so the guarantee
|
|
1310
|
+
* must be time-denominated: 12/13 of the zero-media/missing-firstFrame tracks
|
|
1311
|
+
* in the 2026-07-23 audit were ≤6 s cold-session subjects whose handles were
|
|
1312
|
+
* recycled before their first captures ran. Bounded by the hard byte ceiling
|
|
1313
|
+
* (pass 2) exactly like the count floor — worst case ~26 MB per ACTIVE camera
|
|
1314
|
+
* @25 fps. `CAMSTACK_SESSION_RETAIN_FLOOR_MS`; `0` disables (count floor only).
|
|
1315
|
+
*/
|
|
1316
|
+
var RETAINED_FRAME_TIME_FLOOR_MS = (() => {
|
|
1317
|
+
const raw = Number(process.env["CAMSTACK_SESSION_RETAIN_FLOOR_MS"]);
|
|
1318
|
+
return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 3e3;
|
|
1319
|
+
})();
|
|
1302
1320
|
/** Opaque RAM-key prefix stamped into a retained frame's `FrameHandle.shmId`. */
|
|
1303
1321
|
var RETAINED_FRAME_KEY_PREFIX = "ram:";
|
|
1304
1322
|
/** A retention frame is uniquely identified by its key + slot + committed seq. */
|
|
@@ -1309,6 +1327,8 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
1309
1327
|
nodeId;
|
|
1310
1328
|
budgetBytes;
|
|
1311
1329
|
perDeviceFloor;
|
|
1330
|
+
timeFloorMs;
|
|
1331
|
+
now;
|
|
1312
1332
|
/** Insertion-ordered (Map) → globally oldest is `keys().next()`. */
|
|
1313
1333
|
entries = /* @__PURE__ */ new Map();
|
|
1314
1334
|
/**
|
|
@@ -1325,6 +1345,8 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
1325
1345
|
this.nodeId = options.nodeId;
|
|
1326
1346
|
this.budgetBytes = options.budgetBytes ?? RETAINED_FRAME_BUDGET_BYTES;
|
|
1327
1347
|
this.perDeviceFloor = options.perDeviceFloor ?? RETAINED_FRAME_PER_DEVICE_FLOOR;
|
|
1348
|
+
this.timeFloorMs = options.timeFloorMs ?? RETAINED_FRAME_TIME_FLOOR_MS;
|
|
1349
|
+
this.now = options.now ?? Date.now;
|
|
1328
1350
|
}
|
|
1329
1351
|
/** Number of retained frames (tests / metrics). */
|
|
1330
1352
|
get size() {
|
|
@@ -1360,7 +1382,8 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
1360
1382
|
const key = keyOf(handle);
|
|
1361
1383
|
this.entries.set(key, {
|
|
1362
1384
|
frame,
|
|
1363
|
-
deviceId
|
|
1385
|
+
deviceId,
|
|
1386
|
+
writtenAt: this.now()
|
|
1364
1387
|
});
|
|
1365
1388
|
const deviceKeys = this.perDeviceKeys.get(deviceId);
|
|
1366
1389
|
if (deviceKeys) deviceKeys.push(key);
|
|
@@ -1405,6 +1428,16 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
1405
1428
|
evictToBudget() {
|
|
1406
1429
|
if (this.bytes <= this.budgetBytes) return;
|
|
1407
1430
|
const oldestFirst = [...this.entries.keys()];
|
|
1431
|
+
const nowMs = this.now();
|
|
1432
|
+
for (const key of oldestFirst) {
|
|
1433
|
+
if (this.bytes <= this.budgetBytes || this.entries.size <= 1) break;
|
|
1434
|
+
const entry = this.entries.get(key);
|
|
1435
|
+
if (!entry) continue;
|
|
1436
|
+
if (this.timeFloorMs > 0 && nowMs - entry.writtenAt < this.timeFloorMs) continue;
|
|
1437
|
+
const deviceKeys = this.perDeviceKeys.get(entry.deviceId);
|
|
1438
|
+
if (deviceKeys && deviceKeys.length > this.perDeviceFloor) this.dropKey(key);
|
|
1439
|
+
}
|
|
1440
|
+
if (this.bytes <= this.budgetBytes) return;
|
|
1408
1441
|
for (const key of oldestFirst) {
|
|
1409
1442
|
if (this.bytes <= this.budgetBytes || this.entries.size <= 1) break;
|
|
1410
1443
|
const entry = this.entries.get(key);
|
|
@@ -1472,40 +1505,6 @@ function cropRgb(frame, bbox, maxWidth) {
|
|
|
1472
1505
|
height: outH
|
|
1473
1506
|
};
|
|
1474
1507
|
}
|
|
1475
|
-
var ThumbSamplerPolicy = class {
|
|
1476
|
-
intervalMs;
|
|
1477
|
-
minGapMs;
|
|
1478
|
-
now;
|
|
1479
|
-
/** Last accepted-sample wall clock per device. */
|
|
1480
|
-
lastAcceptedAt = /* @__PURE__ */ new Map();
|
|
1481
|
-
constructor(options) {
|
|
1482
|
-
this.intervalMs = Math.max(0, options.intervalMs);
|
|
1483
|
-
this.minGapMs = Math.max(0, options.minGapMs ?? 1e3);
|
|
1484
|
-
this.now = options.now ?? (() => Date.now());
|
|
1485
|
-
}
|
|
1486
|
-
/**
|
|
1487
|
-
* Decide whether to sample `deviceId` now. Returns true (and records the
|
|
1488
|
-
* acceptance) when the larger of `intervalMs` / `minGapMs` has elapsed since
|
|
1489
|
-
* the last accepted sample for this device; false otherwise. The FIRST call
|
|
1490
|
-
* for a device always samples (cold start).
|
|
1491
|
-
*/
|
|
1492
|
-
shouldSample(deviceId) {
|
|
1493
|
-
const now = this.now();
|
|
1494
|
-
const gate = Math.max(this.intervalMs, this.minGapMs);
|
|
1495
|
-
const last = this.lastAcceptedAt.get(deviceId);
|
|
1496
|
-
if (last !== void 0 && now - last < gate) return false;
|
|
1497
|
-
this.lastAcceptedAt.set(deviceId, now);
|
|
1498
|
-
return true;
|
|
1499
|
-
}
|
|
1500
|
-
/** Forget a device (detach / phase exit) so its next frame cold-starts. */
|
|
1501
|
-
forget(deviceId) {
|
|
1502
|
-
this.lastAcceptedAt.delete(deviceId);
|
|
1503
|
-
}
|
|
1504
|
-
/** Drop all per-device state (shutdown). */
|
|
1505
|
-
clear() {
|
|
1506
|
-
this.lastAcceptedAt.clear();
|
|
1507
|
-
}
|
|
1508
|
-
};
|
|
1509
1508
|
//#endregion
|
|
1510
1509
|
//#region src/session-decode/session-decode-coordinator.ts
|
|
1511
1510
|
/** How long to wait after `kill()` before escalating to `kill('SIGKILL')`. */
|
|
@@ -2364,8 +2363,6 @@ function resolveStepDevice(input) {
|
|
|
2364
2363
|
//#region src/pipeline-runner/index.ts
|
|
2365
2364
|
var DEFAULT_CONFIG = {
|
|
2366
2365
|
maxQueueDepth: 30,
|
|
2367
|
-
thumbnailsEnabled: true,
|
|
2368
|
-
thumbnailSampleMs: 5e3,
|
|
2369
2366
|
maxConcurrentInferences: 16,
|
|
2370
2367
|
targetLoadPercent: 80,
|
|
2371
2368
|
minThrottledFps: 1,
|
|
@@ -2581,7 +2578,7 @@ function resolveSessionDecodeOwnerNodeId(config) {
|
|
|
2581
2578
|
function resolveOwnerRestreamHost(frameSource, hubUrl) {
|
|
2582
2579
|
const reachable = frameSource.ownerReachableHost?.trim();
|
|
2583
2580
|
if (reachable !== void 0 && reachable.length > 0) return reachable;
|
|
2584
|
-
return
|
|
2581
|
+
return require_hub_hostname.resolveHubHostname(frameSource.hubHostnameOverride, hubUrl);
|
|
2585
2582
|
}
|
|
2586
2583
|
/**
|
|
2587
2584
|
* Acquire the broker's COMPRESSED passthrough restream for one camStream and
|
|
@@ -2687,12 +2684,6 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
2687
2684
|
* Initialised in {@link onInitialize} once `nodeId` is known.
|
|
2688
2685
|
*/
|
|
2689
2686
|
retainedFrames = null;
|
|
2690
|
-
/**
|
|
2691
|
-
* Scrub-thumbnail sampler policy (2026-07-16). Gates the per-camera JPEG
|
|
2692
|
-
* encode to ~1 / `thumbnailSampleMs` (with a hard 1 Hz ceiling) so the cost
|
|
2693
|
-
* stays a couple ms of sharp every few seconds. Null until `onInitialize`.
|
|
2694
|
-
*/
|
|
2695
|
-
thumbSampler = null;
|
|
2696
2687
|
attached = /* @__PURE__ */ new Map();
|
|
2697
2688
|
nodeId = "unknown";
|
|
2698
2689
|
metricsSnapshotTimer = null;
|
|
@@ -2820,7 +2811,6 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
2820
2811
|
nodeId: this.nodeId,
|
|
2821
2812
|
logger: this.ctx.logger.child("retained-frames")
|
|
2822
2813
|
});
|
|
2823
|
-
this.thumbSampler = new ThumbSamplerPolicy({ intervalMs: this.config.thumbnailSampleMs });
|
|
2824
2814
|
this.runner = new PipelineRunner({
|
|
2825
2815
|
maxQueueDepth: this.config.maxQueueDepth,
|
|
2826
2816
|
maxConcurrentInferences: this.config.maxConcurrentInferences,
|
|
@@ -2911,8 +2901,6 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
2911
2901
|
this.nativeCropRegistry.clear();
|
|
2912
2902
|
this.retainedFrames?.clear();
|
|
2913
2903
|
this.retainedFrames = null;
|
|
2914
|
-
this.thumbSampler?.clear();
|
|
2915
|
-
this.thumbSampler = null;
|
|
2916
2904
|
}
|
|
2917
2905
|
async cacheBenchFrame(input) {
|
|
2918
2906
|
const sharp$2 = (await import("sharp")).default;
|
|
@@ -3479,59 +3467,6 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3479
3467
|
frameSpace: denormalizeRect(paddedBbox, handle.width, handle.height)
|
|
3480
3468
|
};
|
|
3481
3469
|
}
|
|
3482
|
-
/**
|
|
3483
|
-
* Scrub-thumbnail sampler (2026-07-16). Rides the already-decoded rgb
|
|
3484
|
-
* detection frame: at most once per `thumbnailSampleMs` per camera (policy
|
|
3485
|
-
* enforces a 1 Hz hard ceiling), downscale to a ~320px JPEG and emit it as
|
|
3486
|
-
* `recording.thumb-sampled` telemetry. The recorder (single designated node)
|
|
3487
|
-
* packs 5-minute sprite sheets from these. Fire-and-forget — a failed encode
|
|
3488
|
-
* is logged at debug and never disturbs the decode/detect path. Zero extra
|
|
3489
|
-
* decode (frame exists); one sharp encode per cadence per camera (D9: a
|
|
3490
|
-
* compressed payload at 1/5 Hz, never raw pixels at frame-rate).
|
|
3491
|
-
*/
|
|
3492
|
-
maybeSampleThumbnail(deviceId, frame) {
|
|
3493
|
-
if (!this.config.thumbnailsEnabled) return;
|
|
3494
|
-
const sampler = this.thumbSampler;
|
|
3495
|
-
if (!sampler || frame.format !== "rgb") return;
|
|
3496
|
-
if (!sampler.shouldSample(deviceId)) return;
|
|
3497
|
-
const capturedAt = typeof frame.capturedAt === "number" && frame.capturedAt > 0 ? frame.capturedAt : Date.now();
|
|
3498
|
-
const width = frame.width;
|
|
3499
|
-
const height = frame.height;
|
|
3500
|
-
const data = frame.data;
|
|
3501
|
-
(async () => {
|
|
3502
|
-
try {
|
|
3503
|
-
const targetW = Math.min(480, width);
|
|
3504
|
-
const image = (0, sharp.default)(data, { raw: {
|
|
3505
|
-
width,
|
|
3506
|
-
height,
|
|
3507
|
-
channels: 3
|
|
3508
|
-
} });
|
|
3509
|
-
if (targetW < width) image.resize(targetW);
|
|
3510
|
-
const jpeg = await image.jpeg({ quality: 60 }).toBuffer();
|
|
3511
|
-
const meta = {
|
|
3512
|
-
width: targetW,
|
|
3513
|
-
height: Math.round(targetW * height / width)
|
|
3514
|
-
};
|
|
3515
|
-
this.ctxIfReady?.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.RecordingThumbSampled, {
|
|
3516
|
-
type: "pipeline",
|
|
3517
|
-
id: deviceId,
|
|
3518
|
-
nodeId: this.nodeId,
|
|
3519
|
-
deviceId
|
|
3520
|
-
}, {
|
|
3521
|
-
deviceId,
|
|
3522
|
-
capturedAt,
|
|
3523
|
-
width: meta.width,
|
|
3524
|
-
height: meta.height,
|
|
3525
|
-
jpeg: new Uint8Array(jpeg)
|
|
3526
|
-
}));
|
|
3527
|
-
} catch (err) {
|
|
3528
|
-
this.ctx?.logger.debug("thumbnail sample encode failed", {
|
|
3529
|
-
tags: { deviceId },
|
|
3530
|
-
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
3531
|
-
});
|
|
3532
|
-
}
|
|
3533
|
-
})();
|
|
3534
|
-
}
|
|
3535
3470
|
/** Real `runPipeline` dep: node-local `pipelineExecutor.runPipeline` on the `'full'` plane. */
|
|
3536
3471
|
async runDetailPipeline(steps, imageJpeg, deviceId, stepDeviceKey, nativeCropRef) {
|
|
3537
3472
|
const api = this.ctxIfReady?.api;
|
|
@@ -3612,7 +3547,6 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3612
3547
|
attachment.detectionUnsubscribe?.();
|
|
3613
3548
|
this.lastMotionFrameAt.delete(deviceId);
|
|
3614
3549
|
this.lastDecodeUnexpectedEndAt.delete(deviceId);
|
|
3615
|
-
this.thumbSampler?.forget(deviceId);
|
|
3616
3550
|
this.packageGovernor.forget(deviceId);
|
|
3617
3551
|
const warnPrefix = `${deviceId}:`;
|
|
3618
3552
|
for (const key of this.warnedNoJumpDevice) if (key.startsWith(warnPrefix)) this.warnedNoJumpDevice.delete(key);
|
|
@@ -3859,7 +3793,6 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3859
3793
|
const handle = retainedFrames?.write(config.deviceId, frame) ?? void 0;
|
|
3860
3794
|
if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop);
|
|
3861
3795
|
this.enqueueSharedDetectionFrame(config.deviceId, frame, handle);
|
|
3862
|
-
this.maybeSampleThumbnail(config.deviceId, frame);
|
|
3863
3796
|
}
|
|
3864
3797
|
});
|
|
3865
3798
|
}
|
|
@@ -4527,31 +4460,12 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
4527
4460
|
step: 1,
|
|
4528
4461
|
default: DEFAULT_CONFIG.maxConcurrentDecodeSpawns,
|
|
4529
4462
|
showValue: true
|
|
4530
|
-
},
|
|
4531
|
-
{
|
|
4532
|
-
type: "boolean",
|
|
4533
|
-
key: "thumbnailsEnabled",
|
|
4534
|
-
label: "Scrub thumbnails",
|
|
4535
|
-
description: "Sample the already-decoded detection frame into small JPEG scrub thumbnails (zero extra decode). The recorder packs them into 5-minute sprite sheets the viewer timeline scrubs. Disable to stop emitting thumbnail telemetry from this node.",
|
|
4536
|
-
default: DEFAULT_CONFIG.thumbnailsEnabled
|
|
4537
|
-
},
|
|
4538
|
-
{
|
|
4539
|
-
type: "slider",
|
|
4540
|
-
key: "thumbnailSampleMs",
|
|
4541
|
-
label: "Thumbnail cadence (ms)",
|
|
4542
|
-
description: "How often (ms) each camera contributes a scrub thumbnail. Lower = denser scrub preview + more storage; a hard 1 Hz ceiling always applies. Default 5000.",
|
|
4543
|
-
min: 1e3,
|
|
4544
|
-
max: 3e4,
|
|
4545
|
-
step: 1e3,
|
|
4546
|
-
default: DEFAULT_CONFIG.thumbnailSampleMs,
|
|
4547
|
-
showValue: true
|
|
4548
4463
|
}
|
|
4549
4464
|
]
|
|
4550
4465
|
}] });
|
|
4551
4466
|
}
|
|
4552
4467
|
async onConfigChanged() {
|
|
4553
4468
|
this.runner?.updateLimits(this.config);
|
|
4554
|
-
this.thumbSampler = new ThumbSamplerPolicy({ intervalMs: this.config.thumbnailSampleMs });
|
|
4555
4469
|
this.spawnGate?.resize(Math.max(1, this.config.maxConcurrentDecodeSpawns));
|
|
4556
4470
|
this.ctx.logger.info("pipeline-runner tuning updated", { meta: {
|
|
4557
4471
|
maxQueueDepth: this.config.maxQueueDepth,
|