@camstack/addon-pipeline 1.1.55 → 1.1.56
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 +1 -1
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +23 -16
- package/dist/detection-pipeline/index.mjs +23 -16
- package/dist/{dist-DI57FC8K.js → dist-ClE6_i0p.js} +942 -38
- package/dist/{dist-RWGGPwVx.mjs → dist-CrjLGrkj.mjs} +942 -38
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +119 -11
- package/dist/pipeline-runner/index.mjs +119 -11
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/{remote-source-plane-DU0aRSPv.mjs → remote-source-plane-B28iGi7h.mjs} +1 -1
- package/dist/{remote-source-plane-CHgvzzA6.js → remote-source-plane-BAqEHSgm.js} +1 -1
- package/dist/session-decode/decode-worker-child.js +5 -3
- package/dist/session-decode/decode-worker-child.mjs +5 -3
- package/dist/{step-definitions-CNBFKjZe.js → step-definitions-C9kUXgC7.js} +2 -2
- package/dist/{step-definitions-CP9kVSml.mjs → step-definitions-dCghpdRo.mjs} +2 -2
- package/dist/stream-broker/_stub.js +79 -79
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-mDCPzmT3.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D4xH4Tm-.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D2z_vRMH.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-Dn_pxzP-.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-TWCUWhCm.mjs} +2 -2
- package/dist/stream-broker/{hostInit-GM_CI22k.mjs → hostInit-Di9WUNlQ.mjs} +2 -2
- package/dist/stream-broker/index.js +60 -3
- package/dist/stream-broker/index.mjs +60 -3
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-BChW0ntM.js → MaskShapeCanvas-DI4BY7W2-Ca93F1js.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-DDzqEbSe.js → MotionZonesSettings-NcxxQN8r-CDqgb-WA.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-B879GXaf.js → PrivacyMaskSettings-APgPLF7p-BlxPhY_w.js} +1 -1
- package/embed-dist/assets/index-B23riUw8.js +81 -0
- package/embed-dist/assets/index-Bmpl9O1K.css +2 -0
- package/embed-dist/index.html +2 -2
- package/package.json +1 -1
- package/python/inference_pool.py +11 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B3gTdHEh.mjs +0 -26
- package/embed-dist/assets/index-CC06JBcl.css +0 -2
- package/embed-dist/assets/index-CNjQ5rAE.js +0 -81
|
@@ -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-ClE6_i0p.js");
|
|
6
6
|
let _camstack_shm_ring = require("@camstack/shm-ring");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as DeviceType, E as motionDetectionCapability, L as BaseAddon, U as hydrateSchema, x as evaluateZoneRules } from "../dist-
|
|
1
|
+
import { B as DeviceType, E as motionDetectionCapability, L as BaseAddon, U as hydrateSchema, x as evaluateZoneRules } from "../dist-CrjLGrkj.mjs";
|
|
2
2
|
import { FrameRingReaderCache } from "@camstack/shm-ring";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -2,11 +2,11 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
6
|
-
const require_remote_source_plane = require("../remote-source-plane-
|
|
5
|
+
const require_dist = require("../dist-ClE6_i0p.js");
|
|
6
|
+
const require_remote_source_plane = require("../remote-source-plane-BAqEHSgm.js");
|
|
7
7
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
8
8
|
const require_worker_protocol = require("../worker-protocol-PP4jKHHJ.js");
|
|
9
|
-
const require_step_definitions = require("../step-definitions-
|
|
9
|
+
const require_step_definitions = require("../step-definitions-C9kUXgC7.js");
|
|
10
10
|
let _camstack_shm_ring = require("@camstack/shm-ring");
|
|
11
11
|
let node_child_process = require("node:child_process");
|
|
12
12
|
let node_url = require("node:url");
|
|
@@ -1395,8 +1395,13 @@ function wrapChild(child) {
|
|
|
1395
1395
|
child.send(msg, () => void 0);
|
|
1396
1396
|
},
|
|
1397
1397
|
on: (ev, cb) => {
|
|
1398
|
-
if (ev === "exit")
|
|
1399
|
-
|
|
1398
|
+
if (ev === "exit") {
|
|
1399
|
+
const toInfo = (code, signal) => ({
|
|
1400
|
+
code,
|
|
1401
|
+
signal
|
|
1402
|
+
});
|
|
1403
|
+
child.on("exit", (code, signal) => cb(toInfo(code ?? null, signal ?? null)));
|
|
1404
|
+
} else child.on("message", (m) => cb(m));
|
|
1400
1405
|
},
|
|
1401
1406
|
kill: (signal) => {
|
|
1402
1407
|
if (signal === void 0) {
|
|
@@ -1604,6 +1609,11 @@ var SIGKILL_FALLBACK_MS = 1e3;
|
|
|
1604
1609
|
var DEFAULT_FIRST_FRAME_TIMEOUT_MS = 2e4;
|
|
1605
1610
|
/** Default timeout for every `pull` round-trip AFTER the first frame arrives. */
|
|
1606
1611
|
var DEFAULT_FRAME_TIMEOUT_MS = 1e4;
|
|
1612
|
+
/** Narrow an 'exit' payload to {@link WorkerExitInfo} (fakes may emit undefined). */
|
|
1613
|
+
function toWorkerExitInfo(v) {
|
|
1614
|
+
const isInfo = (x) => typeof x === "object" && x !== null && "code" in x && "signal" in x;
|
|
1615
|
+
return isInfo(v) ? v : void 0;
|
|
1616
|
+
}
|
|
1607
1617
|
function toError(err) {
|
|
1608
1618
|
return err instanceof Error ? err : new Error(String(err));
|
|
1609
1619
|
}
|
|
@@ -1624,6 +1634,8 @@ var SessionDecodeSession = class {
|
|
|
1624
1634
|
pendingNativeCrops = /* @__PURE__ */ new Map();
|
|
1625
1635
|
nextNativeCropId = 1;
|
|
1626
1636
|
exited = false;
|
|
1637
|
+
/** Set by teardown() so handleExit can tell an intentional stop from a death. */
|
|
1638
|
+
stopRequested = false;
|
|
1627
1639
|
sigkillTimer = null;
|
|
1628
1640
|
pullTimeoutTimer = null;
|
|
1629
1641
|
firstFrameReceived = false;
|
|
@@ -1633,7 +1645,7 @@ var SessionDecodeSession = class {
|
|
|
1633
1645
|
this.firstFrameTimeoutMs = firstFrameTimeoutMs;
|
|
1634
1646
|
this.frameTimeoutMs = frameTimeoutMs;
|
|
1635
1647
|
this.child.on("message", (m) => this.handleMessage(m));
|
|
1636
|
-
this.child.on("exit", () => this.handleExit());
|
|
1648
|
+
this.child.on("exit", (m) => this.handleExit(toWorkerExitInfo(m)));
|
|
1637
1649
|
}
|
|
1638
1650
|
start(source, opts) {
|
|
1639
1651
|
this.trySend({
|
|
@@ -1725,6 +1737,7 @@ var SessionDecodeSession = class {
|
|
|
1725
1737
|
}
|
|
1726
1738
|
/** Teardown: `stop`, `kill()`, arm the SIGKILL fallback unless already exited. */
|
|
1727
1739
|
teardown() {
|
|
1740
|
+
this.stopRequested = true;
|
|
1728
1741
|
this.clearPullTimeout();
|
|
1729
1742
|
this.trySend({ kind: "stop" });
|
|
1730
1743
|
this.tryKill(void 0);
|
|
@@ -1770,7 +1783,11 @@ var SessionDecodeSession = class {
|
|
|
1770
1783
|
return;
|
|
1771
1784
|
}
|
|
1772
1785
|
}
|
|
1773
|
-
handleExit() {
|
|
1786
|
+
handleExit(info) {
|
|
1787
|
+
if (!this.stopRequested && !this.exited) this.logger.warn("session-decode worker exited unexpectedly", { meta: {
|
|
1788
|
+
code: info?.code ?? null,
|
|
1789
|
+
signal: info?.signal ?? null
|
|
1790
|
+
} });
|
|
1774
1791
|
this.exited = true;
|
|
1775
1792
|
if (this.sigkillTimer) {
|
|
1776
1793
|
clearTimeout(this.sigkillTimer);
|
|
@@ -2097,6 +2114,7 @@ function startSessionDecodePump(deps) {
|
|
|
2097
2114
|
});
|
|
2098
2115
|
} finally {
|
|
2099
2116
|
releaseSpawnSlotOnce();
|
|
2117
|
+
if (!stopped) deps.onEnded?.();
|
|
2100
2118
|
}
|
|
2101
2119
|
};
|
|
2102
2120
|
pump();
|
|
@@ -2210,10 +2228,13 @@ function denormalizeRect(rect, frameWidth, frameHeight) {
|
|
|
2210
2228
|
* Walk `steps` (the attach config's tree) collecting every ENABLED step
|
|
2211
2229
|
* whose catalog definition's `inputClasses` includes `parentClassName` —
|
|
2212
2230
|
* these become the subtree's roots. A matched step's own nested `children`
|
|
2213
|
-
* ride along UNCHANGED (transitively included: `face-detection` pulls
|
|
2231
|
+
* ride along UNCHANGED here (transitively included: `face-detection` pulls
|
|
2214
2232
|
* `face-embedding`, `plate-detection` pulls `plate-ocr`) — recursion stops
|
|
2215
2233
|
* at a match rather than also searching inside it for further matches.
|
|
2216
|
-
* `filter`, when given, keeps only matches whose `addonId` is
|
|
2234
|
+
* `filter`, when given, keeps only the TOP-LEVEL matches whose `addonId` is
|
|
2235
|
+
* in the list — it does NOT prune a match's descendants. Strict per-`steps`
|
|
2236
|
+
* pruning of the descendant chain happens later in {@link runDetailSubtree}
|
|
2237
|
+
* (see {@link pruneChildStepsToRequested}); this function only selects roots.
|
|
2217
2238
|
*/
|
|
2218
2239
|
function findMatchingDetailSteps(steps, parentClassName, lookupStepDefinition, filter) {
|
|
2219
2240
|
const matches = [];
|
|
@@ -2231,6 +2252,26 @@ function findMatchingDetailSteps(steps, parentClassName, lookupStepDefinition, f
|
|
|
2231
2252
|
walk(steps);
|
|
2232
2253
|
return matches;
|
|
2233
2254
|
}
|
|
2255
|
+
/**
|
|
2256
|
+
* Prune a matched step's descendant tree to the caller's requested `steps`
|
|
2257
|
+
* set: keep a child ONLY when its `addonId` is in `requested`, recursing into
|
|
2258
|
+
* every kept child. Pure — returns a new step, never mutates the input.
|
|
2259
|
+
*
|
|
2260
|
+
* Applied ONLY on the DETAIL plane (see {@link runDetailSubtree}); the
|
|
2261
|
+
* per-frame executor path is untouched. This is what makes the dispatcher's
|
|
2262
|
+
* `steps` array authoritative: `['face-detection']` runs the detector geometry
|
|
2263
|
+
* ALONE (the `face-embedding` child is dropped — no ArcFace inference), while
|
|
2264
|
+
* `['face-detection','face-embedding']` keeps the child and reproduces the
|
|
2265
|
+
* full detect→embed chain exactly.
|
|
2266
|
+
*/
|
|
2267
|
+
function pruneChildStepsToRequested(step, requested) {
|
|
2268
|
+
if (!step.children || step.children.length === 0) return step;
|
|
2269
|
+
const children = step.children.filter((child) => requested.includes(child.addonId)).map((child) => pruneChildStepsToRequested(child, requested));
|
|
2270
|
+
return {
|
|
2271
|
+
...step,
|
|
2272
|
+
children
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2234
2275
|
/** Map a detection's CROP-pixel bbox back to FRAME-space pixels. */
|
|
2235
2276
|
function mapBboxToFrameSpace(bbox, crop) {
|
|
2236
2277
|
const scaleX = crop.frameSpace.w / crop.width;
|
|
@@ -2290,10 +2331,21 @@ async function resolveCrop(deps, input) {
|
|
|
2290
2331
|
* step's chain against it, and map every result bbox back to frame space.
|
|
2291
2332
|
* Returns `null` when the camera isn't attached, no configured child
|
|
2292
2333
|
* matches `input.parent.className`, or no crop source resolves.
|
|
2334
|
+
*
|
|
2335
|
+
* STRICT `steps` semantics: when the caller names an explicit `input.steps`
|
|
2336
|
+
* set, each matched root's descendant chain is pruned to exactly those ids
|
|
2337
|
+
* (see {@link pruneChildStepsToRequested}) — `resolveChildSteps`/`filter`
|
|
2338
|
+
* only selects TOP-LEVEL roots, so without this a matched `face-detection`
|
|
2339
|
+
* would always drag its `face-embedding` child along regardless of what was
|
|
2340
|
+
* asked for. When `input.steps` is omitted, the full configured chain runs
|
|
2341
|
+
* (today's behavior). This is the knob the dispatcher uses to run a periodic
|
|
2342
|
+
* geometry-only face refresh (`['face-detection']`) without re-running ArcFace.
|
|
2293
2343
|
*/
|
|
2294
2344
|
async function runDetailSubtree(deps, input) {
|
|
2295
|
-
const
|
|
2296
|
-
if (!
|
|
2345
|
+
const matched = deps.resolveChildSteps(input.deviceId, input.parent.className, input.steps);
|
|
2346
|
+
if (!matched || matched.length === 0) return null;
|
|
2347
|
+
const requested = input.steps;
|
|
2348
|
+
const steps = requested === void 0 ? matched : matched.map((step) => pruneChildStepsToRequested(step, requested));
|
|
2297
2349
|
const crop = await resolveCrop(deps, input);
|
|
2298
2350
|
if (!crop) return null;
|
|
2299
2351
|
const details = [];
|
|
@@ -2345,6 +2397,14 @@ var OCCUPANCY_BURST_SAFETY_TIMEOUT_MS = 8e3;
|
|
|
2345
2397
|
* to have been teed within this window before it will route to the tap path.
|
|
2346
2398
|
*/
|
|
2347
2399
|
var MOTION_FRESHNESS_WINDOW_MS = 5e3;
|
|
2400
|
+
/** Delay before re-dialing a detection decode whose worker died unexpectedly. */
|
|
2401
|
+
var DETECTION_REDIAL_DELAY_MS = 2e3;
|
|
2402
|
+
/**
|
|
2403
|
+
* Two unexpected decode deaths within this window → stop re-dialing and detach
|
|
2404
|
+
* (the orchestrator re-dispatches on the next motion signal). Bounds the loop
|
|
2405
|
+
* when the camera/source itself is broken.
|
|
2406
|
+
*/
|
|
2407
|
+
var DETECTION_REDIAL_GIVEUP_WINDOW_MS = 6e4;
|
|
2348
2408
|
/**
|
|
2349
2409
|
* Pure decision helper: returns `true` when a dynamic frame-diff analyzer
|
|
2350
2410
|
* subscription should be opened for an onboard-motion camera.
|
|
@@ -3295,6 +3355,52 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3295
3355
|
return null;
|
|
3296
3356
|
}
|
|
3297
3357
|
}
|
|
3358
|
+
/** Last unexpected detection-decode end per device — bounds the re-dial. */
|
|
3359
|
+
lastDecodeUnexpectedEndAt = /* @__PURE__ */ new Map();
|
|
3360
|
+
/**
|
|
3361
|
+
* The detection decode pump ended WITHOUT a teardown (worker death /
|
|
3362
|
+
* generator error) while the camera may still be attached. Previously this
|
|
3363
|
+
* zombied the session — phase stayed `active`, frames stopped, and nothing
|
|
3364
|
+
* re-dialed (the pre-fix Channel-closed runner crash used to mask it by
|
|
3365
|
+
* respawning the whole runner). Recovery, bounded:
|
|
3366
|
+
* - first death: clean up the dead subscription and re-dial once (2s);
|
|
3367
|
+
* - second death within 60s: give up re-dialing and DETACH — the
|
|
3368
|
+
* orchestrator re-dispatches cleanly on the next motion/occupancy signal
|
|
3369
|
+
* (the same recovery path validated manually on device 592).
|
|
3370
|
+
*/
|
|
3371
|
+
handleDetectionDecodeEnded(deviceId) {
|
|
3372
|
+
const attachment = this.attached.get(deviceId);
|
|
3373
|
+
if (!attachment) return;
|
|
3374
|
+
const log = this.ctx.logger.withTags({ deviceId });
|
|
3375
|
+
attachment.detectionUnsubscribe?.();
|
|
3376
|
+
attachment.detectionUnsubscribe = null;
|
|
3377
|
+
if (attachment.detectionKeepWarmTimer !== null) {
|
|
3378
|
+
clearTimeout(attachment.detectionKeepWarmTimer);
|
|
3379
|
+
attachment.detectionKeepWarmTimer = null;
|
|
3380
|
+
log.warn("detection decode worker died while kept warm — next open re-forks cold");
|
|
3381
|
+
return;
|
|
3382
|
+
}
|
|
3383
|
+
const now = Date.now();
|
|
3384
|
+
const last = this.lastDecodeUnexpectedEndAt.get(deviceId) ?? 0;
|
|
3385
|
+
this.lastDecodeUnexpectedEndAt.set(deviceId, now);
|
|
3386
|
+
if (now - last < DETECTION_REDIAL_GIVEUP_WINDOW_MS) {
|
|
3387
|
+
log.warn("detection decode died twice within the re-dial window — detaching for orchestrator re-dispatch");
|
|
3388
|
+
this.detachInternal(deviceId);
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
log.warn("detection decode ended unexpectedly while attached — re-dialing once");
|
|
3392
|
+
setTimeout(() => {
|
|
3393
|
+
const still = this.attached.get(deviceId);
|
|
3394
|
+
if (!still || still.detectionUnsubscribe !== null) return;
|
|
3395
|
+
this.subscribeDetectionFrames(still.config).then((unsub) => {
|
|
3396
|
+
if (!this.attached.has(deviceId)) {
|
|
3397
|
+
unsub?.();
|
|
3398
|
+
return;
|
|
3399
|
+
}
|
|
3400
|
+
still.detectionUnsubscribe = unsub;
|
|
3401
|
+
});
|
|
3402
|
+
}, DETECTION_REDIAL_DELAY_MS).unref?.();
|
|
3403
|
+
}
|
|
3298
3404
|
detachInternal(deviceId) {
|
|
3299
3405
|
const attachment = this.attached.get(deviceId);
|
|
3300
3406
|
if (!attachment) return;
|
|
@@ -3306,6 +3412,7 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3306
3412
|
attachment.motionUnsubscribe?.();
|
|
3307
3413
|
attachment.detectionUnsubscribe?.();
|
|
3308
3414
|
this.lastMotionFrameAt.delete(deviceId);
|
|
3415
|
+
this.lastDecodeUnexpectedEndAt.delete(deviceId);
|
|
3309
3416
|
const remotePlane = this.remotePlanes.get(deviceId);
|
|
3310
3417
|
if (remotePlane) {
|
|
3311
3418
|
this.remotePlanes.delete(deviceId);
|
|
@@ -3615,6 +3722,7 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3615
3722
|
format: "rgb"
|
|
3616
3723
|
},
|
|
3617
3724
|
acquireSpawnSlot: this.buildDecodeSpawnAcquire(log, "detect"),
|
|
3725
|
+
onEnded: () => this.handleDetectionDecodeEnded(config.deviceId),
|
|
3618
3726
|
onDecodedFrame: (frame, nativeCrop) => {
|
|
3619
3727
|
const handle = retentionSink.write(frame) ?? void 0;
|
|
3620
3728
|
if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as boolean, D as nodePin, H as createEvent, I as errMsg, K as makeSourceBrokerId, L as BaseAddon, Q as array, V as EventCategory, Z as _enum, _ as customAction, it as object, k as pipelineRunnerCapability, ot as string, rt as number, tt as lazy, y as defineCustomActions } from "../dist-
|
|
2
|
-
import { n as isRemoteRestream, r as profileForStreamId, t as RemoteSourcePlane } from "../remote-source-plane-
|
|
1
|
+
import { $ as boolean, D as nodePin, H as createEvent, I as errMsg, K as makeSourceBrokerId, L as BaseAddon, Q as array, V as EventCategory, Z as _enum, _ as customAction, it as object, k as pipelineRunnerCapability, ot as string, rt as number, tt as lazy, y as defineCustomActions } from "../dist-CrjLGrkj.mjs";
|
|
2
|
+
import { n as isRemoteRestream, r as profileForStreamId, t as RemoteSourcePlane } from "../remote-source-plane-B28iGi7h.mjs";
|
|
3
3
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
4
4
|
import { t as isWorkerReply } from "../worker-protocol-CyVJTZEO.mjs";
|
|
5
|
-
import { a as getStepDefinition } from "../step-definitions-
|
|
5
|
+
import { a as getStepDefinition } from "../step-definitions-dCghpdRo.mjs";
|
|
6
6
|
import { DecoderFrameRingSink, FrameRingReaderCache, unlinkSegment } from "@camstack/shm-ring";
|
|
7
7
|
import { fork } from "node:child_process";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
@@ -1391,8 +1391,13 @@ function wrapChild(child) {
|
|
|
1391
1391
|
child.send(msg, () => void 0);
|
|
1392
1392
|
},
|
|
1393
1393
|
on: (ev, cb) => {
|
|
1394
|
-
if (ev === "exit")
|
|
1395
|
-
|
|
1394
|
+
if (ev === "exit") {
|
|
1395
|
+
const toInfo = (code, signal) => ({
|
|
1396
|
+
code,
|
|
1397
|
+
signal
|
|
1398
|
+
});
|
|
1399
|
+
child.on("exit", (code, signal) => cb(toInfo(code ?? null, signal ?? null)));
|
|
1400
|
+
} else child.on("message", (m) => cb(m));
|
|
1396
1401
|
},
|
|
1397
1402
|
kill: (signal) => {
|
|
1398
1403
|
if (signal === void 0) {
|
|
@@ -1600,6 +1605,11 @@ var SIGKILL_FALLBACK_MS = 1e3;
|
|
|
1600
1605
|
var DEFAULT_FIRST_FRAME_TIMEOUT_MS = 2e4;
|
|
1601
1606
|
/** Default timeout for every `pull` round-trip AFTER the first frame arrives. */
|
|
1602
1607
|
var DEFAULT_FRAME_TIMEOUT_MS = 1e4;
|
|
1608
|
+
/** Narrow an 'exit' payload to {@link WorkerExitInfo} (fakes may emit undefined). */
|
|
1609
|
+
function toWorkerExitInfo(v) {
|
|
1610
|
+
const isInfo = (x) => typeof x === "object" && x !== null && "code" in x && "signal" in x;
|
|
1611
|
+
return isInfo(v) ? v : void 0;
|
|
1612
|
+
}
|
|
1603
1613
|
function toError(err) {
|
|
1604
1614
|
return err instanceof Error ? err : new Error(String(err));
|
|
1605
1615
|
}
|
|
@@ -1620,6 +1630,8 @@ var SessionDecodeSession = class {
|
|
|
1620
1630
|
pendingNativeCrops = /* @__PURE__ */ new Map();
|
|
1621
1631
|
nextNativeCropId = 1;
|
|
1622
1632
|
exited = false;
|
|
1633
|
+
/** Set by teardown() so handleExit can tell an intentional stop from a death. */
|
|
1634
|
+
stopRequested = false;
|
|
1623
1635
|
sigkillTimer = null;
|
|
1624
1636
|
pullTimeoutTimer = null;
|
|
1625
1637
|
firstFrameReceived = false;
|
|
@@ -1629,7 +1641,7 @@ var SessionDecodeSession = class {
|
|
|
1629
1641
|
this.firstFrameTimeoutMs = firstFrameTimeoutMs;
|
|
1630
1642
|
this.frameTimeoutMs = frameTimeoutMs;
|
|
1631
1643
|
this.child.on("message", (m) => this.handleMessage(m));
|
|
1632
|
-
this.child.on("exit", () => this.handleExit());
|
|
1644
|
+
this.child.on("exit", (m) => this.handleExit(toWorkerExitInfo(m)));
|
|
1633
1645
|
}
|
|
1634
1646
|
start(source, opts) {
|
|
1635
1647
|
this.trySend({
|
|
@@ -1721,6 +1733,7 @@ var SessionDecodeSession = class {
|
|
|
1721
1733
|
}
|
|
1722
1734
|
/** Teardown: `stop`, `kill()`, arm the SIGKILL fallback unless already exited. */
|
|
1723
1735
|
teardown() {
|
|
1736
|
+
this.stopRequested = true;
|
|
1724
1737
|
this.clearPullTimeout();
|
|
1725
1738
|
this.trySend({ kind: "stop" });
|
|
1726
1739
|
this.tryKill(void 0);
|
|
@@ -1766,7 +1779,11 @@ var SessionDecodeSession = class {
|
|
|
1766
1779
|
return;
|
|
1767
1780
|
}
|
|
1768
1781
|
}
|
|
1769
|
-
handleExit() {
|
|
1782
|
+
handleExit(info) {
|
|
1783
|
+
if (!this.stopRequested && !this.exited) this.logger.warn("session-decode worker exited unexpectedly", { meta: {
|
|
1784
|
+
code: info?.code ?? null,
|
|
1785
|
+
signal: info?.signal ?? null
|
|
1786
|
+
} });
|
|
1770
1787
|
this.exited = true;
|
|
1771
1788
|
if (this.sigkillTimer) {
|
|
1772
1789
|
clearTimeout(this.sigkillTimer);
|
|
@@ -2093,6 +2110,7 @@ function startSessionDecodePump(deps) {
|
|
|
2093
2110
|
});
|
|
2094
2111
|
} finally {
|
|
2095
2112
|
releaseSpawnSlotOnce();
|
|
2113
|
+
if (!stopped) deps.onEnded?.();
|
|
2096
2114
|
}
|
|
2097
2115
|
};
|
|
2098
2116
|
pump();
|
|
@@ -2206,10 +2224,13 @@ function denormalizeRect(rect, frameWidth, frameHeight) {
|
|
|
2206
2224
|
* Walk `steps` (the attach config's tree) collecting every ENABLED step
|
|
2207
2225
|
* whose catalog definition's `inputClasses` includes `parentClassName` —
|
|
2208
2226
|
* these become the subtree's roots. A matched step's own nested `children`
|
|
2209
|
-
* ride along UNCHANGED (transitively included: `face-detection` pulls
|
|
2227
|
+
* ride along UNCHANGED here (transitively included: `face-detection` pulls
|
|
2210
2228
|
* `face-embedding`, `plate-detection` pulls `plate-ocr`) — recursion stops
|
|
2211
2229
|
* at a match rather than also searching inside it for further matches.
|
|
2212
|
-
* `filter`, when given, keeps only matches whose `addonId` is
|
|
2230
|
+
* `filter`, when given, keeps only the TOP-LEVEL matches whose `addonId` is
|
|
2231
|
+
* in the list — it does NOT prune a match's descendants. Strict per-`steps`
|
|
2232
|
+
* pruning of the descendant chain happens later in {@link runDetailSubtree}
|
|
2233
|
+
* (see {@link pruneChildStepsToRequested}); this function only selects roots.
|
|
2213
2234
|
*/
|
|
2214
2235
|
function findMatchingDetailSteps(steps, parentClassName, lookupStepDefinition, filter) {
|
|
2215
2236
|
const matches = [];
|
|
@@ -2227,6 +2248,26 @@ function findMatchingDetailSteps(steps, parentClassName, lookupStepDefinition, f
|
|
|
2227
2248
|
walk(steps);
|
|
2228
2249
|
return matches;
|
|
2229
2250
|
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Prune a matched step's descendant tree to the caller's requested `steps`
|
|
2253
|
+
* set: keep a child ONLY when its `addonId` is in `requested`, recursing into
|
|
2254
|
+
* every kept child. Pure — returns a new step, never mutates the input.
|
|
2255
|
+
*
|
|
2256
|
+
* Applied ONLY on the DETAIL plane (see {@link runDetailSubtree}); the
|
|
2257
|
+
* per-frame executor path is untouched. This is what makes the dispatcher's
|
|
2258
|
+
* `steps` array authoritative: `['face-detection']` runs the detector geometry
|
|
2259
|
+
* ALONE (the `face-embedding` child is dropped — no ArcFace inference), while
|
|
2260
|
+
* `['face-detection','face-embedding']` keeps the child and reproduces the
|
|
2261
|
+
* full detect→embed chain exactly.
|
|
2262
|
+
*/
|
|
2263
|
+
function pruneChildStepsToRequested(step, requested) {
|
|
2264
|
+
if (!step.children || step.children.length === 0) return step;
|
|
2265
|
+
const children = step.children.filter((child) => requested.includes(child.addonId)).map((child) => pruneChildStepsToRequested(child, requested));
|
|
2266
|
+
return {
|
|
2267
|
+
...step,
|
|
2268
|
+
children
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2230
2271
|
/** Map a detection's CROP-pixel bbox back to FRAME-space pixels. */
|
|
2231
2272
|
function mapBboxToFrameSpace(bbox, crop) {
|
|
2232
2273
|
const scaleX = crop.frameSpace.w / crop.width;
|
|
@@ -2286,10 +2327,21 @@ async function resolveCrop(deps, input) {
|
|
|
2286
2327
|
* step's chain against it, and map every result bbox back to frame space.
|
|
2287
2328
|
* Returns `null` when the camera isn't attached, no configured child
|
|
2288
2329
|
* matches `input.parent.className`, or no crop source resolves.
|
|
2330
|
+
*
|
|
2331
|
+
* STRICT `steps` semantics: when the caller names an explicit `input.steps`
|
|
2332
|
+
* set, each matched root's descendant chain is pruned to exactly those ids
|
|
2333
|
+
* (see {@link pruneChildStepsToRequested}) — `resolveChildSteps`/`filter`
|
|
2334
|
+
* only selects TOP-LEVEL roots, so without this a matched `face-detection`
|
|
2335
|
+
* would always drag its `face-embedding` child along regardless of what was
|
|
2336
|
+
* asked for. When `input.steps` is omitted, the full configured chain runs
|
|
2337
|
+
* (today's behavior). This is the knob the dispatcher uses to run a periodic
|
|
2338
|
+
* geometry-only face refresh (`['face-detection']`) without re-running ArcFace.
|
|
2289
2339
|
*/
|
|
2290
2340
|
async function runDetailSubtree(deps, input) {
|
|
2291
|
-
const
|
|
2292
|
-
if (!
|
|
2341
|
+
const matched = deps.resolveChildSteps(input.deviceId, input.parent.className, input.steps);
|
|
2342
|
+
if (!matched || matched.length === 0) return null;
|
|
2343
|
+
const requested = input.steps;
|
|
2344
|
+
const steps = requested === void 0 ? matched : matched.map((step) => pruneChildStepsToRequested(step, requested));
|
|
2293
2345
|
const crop = await resolveCrop(deps, input);
|
|
2294
2346
|
if (!crop) return null;
|
|
2295
2347
|
const details = [];
|
|
@@ -2341,6 +2393,14 @@ var OCCUPANCY_BURST_SAFETY_TIMEOUT_MS = 8e3;
|
|
|
2341
2393
|
* to have been teed within this window before it will route to the tap path.
|
|
2342
2394
|
*/
|
|
2343
2395
|
var MOTION_FRESHNESS_WINDOW_MS = 5e3;
|
|
2396
|
+
/** Delay before re-dialing a detection decode whose worker died unexpectedly. */
|
|
2397
|
+
var DETECTION_REDIAL_DELAY_MS = 2e3;
|
|
2398
|
+
/**
|
|
2399
|
+
* Two unexpected decode deaths within this window → stop re-dialing and detach
|
|
2400
|
+
* (the orchestrator re-dispatches on the next motion signal). Bounds the loop
|
|
2401
|
+
* when the camera/source itself is broken.
|
|
2402
|
+
*/
|
|
2403
|
+
var DETECTION_REDIAL_GIVEUP_WINDOW_MS = 6e4;
|
|
2344
2404
|
/**
|
|
2345
2405
|
* Pure decision helper: returns `true` when a dynamic frame-diff analyzer
|
|
2346
2406
|
* subscription should be opened for an onboard-motion camera.
|
|
@@ -3291,6 +3351,52 @@ var PipelineRunnerAddon = class extends BaseAddon {
|
|
|
3291
3351
|
return null;
|
|
3292
3352
|
}
|
|
3293
3353
|
}
|
|
3354
|
+
/** Last unexpected detection-decode end per device — bounds the re-dial. */
|
|
3355
|
+
lastDecodeUnexpectedEndAt = /* @__PURE__ */ new Map();
|
|
3356
|
+
/**
|
|
3357
|
+
* The detection decode pump ended WITHOUT a teardown (worker death /
|
|
3358
|
+
* generator error) while the camera may still be attached. Previously this
|
|
3359
|
+
* zombied the session — phase stayed `active`, frames stopped, and nothing
|
|
3360
|
+
* re-dialed (the pre-fix Channel-closed runner crash used to mask it by
|
|
3361
|
+
* respawning the whole runner). Recovery, bounded:
|
|
3362
|
+
* - first death: clean up the dead subscription and re-dial once (2s);
|
|
3363
|
+
* - second death within 60s: give up re-dialing and DETACH — the
|
|
3364
|
+
* orchestrator re-dispatches cleanly on the next motion/occupancy signal
|
|
3365
|
+
* (the same recovery path validated manually on device 592).
|
|
3366
|
+
*/
|
|
3367
|
+
handleDetectionDecodeEnded(deviceId) {
|
|
3368
|
+
const attachment = this.attached.get(deviceId);
|
|
3369
|
+
if (!attachment) return;
|
|
3370
|
+
const log = this.ctx.logger.withTags({ deviceId });
|
|
3371
|
+
attachment.detectionUnsubscribe?.();
|
|
3372
|
+
attachment.detectionUnsubscribe = null;
|
|
3373
|
+
if (attachment.detectionKeepWarmTimer !== null) {
|
|
3374
|
+
clearTimeout(attachment.detectionKeepWarmTimer);
|
|
3375
|
+
attachment.detectionKeepWarmTimer = null;
|
|
3376
|
+
log.warn("detection decode worker died while kept warm — next open re-forks cold");
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
const now = Date.now();
|
|
3380
|
+
const last = this.lastDecodeUnexpectedEndAt.get(deviceId) ?? 0;
|
|
3381
|
+
this.lastDecodeUnexpectedEndAt.set(deviceId, now);
|
|
3382
|
+
if (now - last < DETECTION_REDIAL_GIVEUP_WINDOW_MS) {
|
|
3383
|
+
log.warn("detection decode died twice within the re-dial window — detaching for orchestrator re-dispatch");
|
|
3384
|
+
this.detachInternal(deviceId);
|
|
3385
|
+
return;
|
|
3386
|
+
}
|
|
3387
|
+
log.warn("detection decode ended unexpectedly while attached — re-dialing once");
|
|
3388
|
+
setTimeout(() => {
|
|
3389
|
+
const still = this.attached.get(deviceId);
|
|
3390
|
+
if (!still || still.detectionUnsubscribe !== null) return;
|
|
3391
|
+
this.subscribeDetectionFrames(still.config).then((unsub) => {
|
|
3392
|
+
if (!this.attached.has(deviceId)) {
|
|
3393
|
+
unsub?.();
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
3396
|
+
still.detectionUnsubscribe = unsub;
|
|
3397
|
+
});
|
|
3398
|
+
}, DETECTION_REDIAL_DELAY_MS).unref?.();
|
|
3399
|
+
}
|
|
3294
3400
|
detachInternal(deviceId) {
|
|
3295
3401
|
const attachment = this.attached.get(deviceId);
|
|
3296
3402
|
if (!attachment) return;
|
|
@@ -3302,6 +3408,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
|
|
|
3302
3408
|
attachment.motionUnsubscribe?.();
|
|
3303
3409
|
attachment.detectionUnsubscribe?.();
|
|
3304
3410
|
this.lastMotionFrameAt.delete(deviceId);
|
|
3411
|
+
this.lastDecodeUnexpectedEndAt.delete(deviceId);
|
|
3305
3412
|
const remotePlane = this.remotePlanes.get(deviceId);
|
|
3306
3413
|
if (remotePlane) {
|
|
3307
3414
|
this.remotePlanes.delete(deviceId);
|
|
@@ -3611,6 +3718,7 @@ var PipelineRunnerAddon = class extends BaseAddon {
|
|
|
3611
3718
|
format: "rgb"
|
|
3612
3719
|
},
|
|
3613
3720
|
acquireSpawnSlot: this.buildDecodeSpawnAcquire(log, "detect"),
|
|
3721
|
+
onEnded: () => this.handleDetectionDecodeEnded(config.deviceId),
|
|
3614
3722
|
onDecodedFrame: (frame, nativeCrop) => {
|
|
3615
3723
|
const handle = retentionSink.write(frame) ?? void 0;
|
|
3616
3724
|
if (handle && nativeCrop) this.nativeCropRegistry.register(handle, nativeCrop);
|
package/dist/recorder/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("../chunk-D6vf50IK.js");
|
|
2
|
-
const require_dist = require("../dist-
|
|
2
|
+
const require_dist = require("../dist-ClE6_i0p.js");
|
|
3
3
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
4
4
|
const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BLXzxpRB.js");
|
|
5
5
|
let node_child_process = require("node:child_process");
|
package/dist/recorder/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as recordingCapability, B as DeviceType, D as nodePin, I as errMsg, L as BaseAddon, M as storageEvictableCapability, T as migrateConfigToBands, U as hydrateSchema, V as EventCategory, Y as selectAssignedProfileSlots, at as record, ot as string, s as EVENT_PAD_MS, u as RecordingConfigSchema } from "../dist-
|
|
1
|
+
import { A as recordingCapability, B as DeviceType, D as nodePin, I as errMsg, L as BaseAddon, M as storageEvictableCapability, T as migrateConfigToBands, U as hydrateSchema, V as EventCategory, Y as selectAssignedProfileSlots, at as record, ot as string, s as EVENT_PAD_MS, u as RecordingConfigSchema } from "../dist-CrjLGrkj.mjs";
|
|
2
2
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
3
3
|
import { t as createFileDataPlaneHandler } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as nodePin, I as errMsg, d as RingBuffer } from "./dist-
|
|
1
|
+
import { D as nodePin, I as errMsg, d as RingBuffer } from "./dist-CrjLGrkj.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
//#region src/stream-broker/stream-broker/decoder-session-proxy.ts
|
|
4
4
|
/**
|
|
@@ -956,8 +956,9 @@ var DecodeWorkerChild = class {
|
|
|
956
956
|
const uSlice = uPlane.subarray(region.top / 2 * cStride + region.left / 2);
|
|
957
957
|
const vSlice = vPlane.subarray(region.top / 2 * cStride + region.left / 2);
|
|
958
958
|
const scaler = this.ensureNativeCropScaler(nav, C, region.width, region.height, target.width, target.height);
|
|
959
|
-
const
|
|
960
|
-
const
|
|
959
|
+
const tightStride = target.width * 3;
|
|
960
|
+
const dstStride = tightStride + 63 & -64;
|
|
961
|
+
const dstBuffer = Buffer.allocUnsafe(dstStride * target.height + 64);
|
|
961
962
|
const scaledHeight = scaler.scaleSync([
|
|
962
963
|
ySlice,
|
|
963
964
|
uSlice,
|
|
@@ -969,7 +970,8 @@ var DecodeWorkerChild = class {
|
|
|
969
970
|
], 0, region.height, [dstBuffer], [dstStride]);
|
|
970
971
|
if (scaledHeight < 0) throw new Error(`native crop sws_scale failed: ${scaledHeight}`);
|
|
971
972
|
if (scaledHeight !== target.height) throw new Error(`native crop scaler produced ${scaledHeight} of ${target.height} rows`);
|
|
972
|
-
return dstBuffer;
|
|
973
|
+
if (dstStride === tightStride) return dstBuffer.subarray(0, tightStride * target.height);
|
|
974
|
+
return packSinglePlane(dstBuffer, dstStride, target.width, target.height, 3);
|
|
973
975
|
}
|
|
974
976
|
/**
|
|
975
977
|
* Hardware native crop — run the retained GPU surface through a DEDICATED
|
|
@@ -952,8 +952,9 @@ var DecodeWorkerChild = class {
|
|
|
952
952
|
const uSlice = uPlane.subarray(region.top / 2 * cStride + region.left / 2);
|
|
953
953
|
const vSlice = vPlane.subarray(region.top / 2 * cStride + region.left / 2);
|
|
954
954
|
const scaler = this.ensureNativeCropScaler(nav, C, region.width, region.height, target.width, target.height);
|
|
955
|
-
const
|
|
956
|
-
const
|
|
955
|
+
const tightStride = target.width * 3;
|
|
956
|
+
const dstStride = tightStride + 63 & -64;
|
|
957
|
+
const dstBuffer = Buffer.allocUnsafe(dstStride * target.height + 64);
|
|
957
958
|
const scaledHeight = scaler.scaleSync([
|
|
958
959
|
ySlice,
|
|
959
960
|
uSlice,
|
|
@@ -965,7 +966,8 @@ var DecodeWorkerChild = class {
|
|
|
965
966
|
], 0, region.height, [dstBuffer], [dstStride]);
|
|
966
967
|
if (scaledHeight < 0) throw new Error(`native crop sws_scale failed: ${scaledHeight}`);
|
|
967
968
|
if (scaledHeight !== target.height) throw new Error(`native crop scaler produced ${scaledHeight} of ${target.height} rows`);
|
|
968
|
-
return dstBuffer;
|
|
969
|
+
if (dstStride === tightStride) return dstBuffer.subarray(0, tightStride * target.height);
|
|
970
|
+
return packSinglePlane(dstBuffer, dstStride, target.width, target.height, 3);
|
|
969
971
|
}
|
|
970
972
|
/**
|
|
971
973
|
* Hardware native crop — run the retained GPU surface through a DEDICATED
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_dist = require("./dist-
|
|
1
|
+
const require_dist = require("./dist-ClE6_i0p.js");
|
|
2
2
|
//#region src/detection-pipeline/registry/model-catalogs.ts
|
|
3
3
|
var HF_REPO = "camstack/camstack-models";
|
|
4
4
|
var HF_SCRYPTED = "scrypted/plugin-models";
|
|
@@ -1244,7 +1244,7 @@ var STEP_FACE_DETECTION = new PipelineStepBase({
|
|
|
1244
1244
|
defaultConfidence: .5,
|
|
1245
1245
|
defaultMinParentScore: .7,
|
|
1246
1246
|
cadence: {
|
|
1247
|
-
trigger: "
|
|
1247
|
+
trigger: "periodic",
|
|
1248
1248
|
minIntervalMs: 1e3
|
|
1249
1249
|
}
|
|
1250
1250
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as hfModelUrl, a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS } from "./dist-
|
|
1
|
+
import { S as hfModelUrl, a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS } from "./dist-CrjLGrkj.mjs";
|
|
2
2
|
//#region src/detection-pipeline/registry/model-catalogs.ts
|
|
3
3
|
var HF_REPO = "camstack/camstack-models";
|
|
4
4
|
var HF_SCRYPTED = "scrypted/plugin-models";
|
|
@@ -1244,7 +1244,7 @@ var STEP_FACE_DETECTION = new PipelineStepBase({
|
|
|
1244
1244
|
defaultConfidence: .5,
|
|
1245
1245
|
defaultMinParentScore: .7,
|
|
1246
1246
|
cadence: {
|
|
1247
|
-
trigger: "
|
|
1247
|
+
trigger: "periodic",
|
|
1248
1248
|
minIntervalMs: 1e3
|
|
1249
1249
|
}
|
|
1250
1250
|
});
|