@camstack/addon-pipeline 1.1.56 → 1.1.58

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.
Files changed (42) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/detection-pipeline/index.js +71 -74
  4. package/dist/detection-pipeline/index.mjs +70 -73
  5. package/dist/{dist-ClE6_i0p.js → dist-Bn6Uq4vq.js} +4750 -4644
  6. package/dist/{dist-CrjLGrkj.mjs → dist-CnwwDzX3.mjs} +4751 -4645
  7. package/dist/motion-wasm/index.js +9 -41
  8. package/dist/motion-wasm/index.mjs +9 -41
  9. package/dist/pipeline-runner/index.js +238 -656
  10. package/dist/pipeline-runner/index.mjs +227 -647
  11. package/dist/recorder/index.js +2 -2
  12. package/dist/recorder/index.mjs +2 -2
  13. package/dist/remote-restream-BeHi78PZ.mjs +25 -0
  14. package/dist/remote-restream-CO36Sr30.js +36 -0
  15. package/dist/session-decode/decode-worker-child.js +97 -2
  16. package/dist/session-decode/decode-worker-child.mjs +97 -2
  17. package/dist/{step-definitions-C9kUXgC7.js → step-definitions-D7NOrnzz.js} +44 -2
  18. package/dist/{step-definitions-dCghpdRo.mjs → step-definitions-DhEe57Wn.mjs} +44 -2
  19. package/dist/stream-broker/_stub.js +282 -222
  20. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D4xH4Tm-.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B8FQ1e0a.mjs} +3 -3
  21. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D2z_vRMH.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Ba7Eyd47.mjs} +1 -1
  22. package/dist/stream-broker/{hostInit-Di9WUNlQ.mjs → hostInit-D1Vdyu-4.mjs} +3 -3
  23. package/dist/stream-broker/index.js +881 -40
  24. package/dist/stream-broker/index.mjs +878 -37
  25. package/dist/stream-broker/remoteEntry.js +1 -1
  26. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-Ca93F1js.js → MaskShapeCanvas-DI4BY7W2-CG5EE0W4.js} +1 -1
  27. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-CDqgb-WA.js → MotionZonesSettings-NcxxQN8r-BFeLUNOl.js} +1 -1
  28. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BlxPhY_w.js → PrivacyMaskSettings-APgPLF7p-pQQunq1F.js} +1 -1
  29. package/embed-dist/assets/index-BxqQ931j.js +81 -0
  30. package/embed-dist/index.html +1 -1
  31. package/package.json +7 -5
  32. package/python/inference_pool.py +172 -28
  33. package/python/postprocessors/__init__.py +2 -0
  34. package/python/postprocessors/ssd.py +128 -0
  35. package/python/requirements-edgetpu.txt +23 -0
  36. package/python/test_inference_pool_layout.py +16 -1
  37. package/python/test_inference_pool_preprocess.py +65 -0
  38. package/dist/remote-source-plane-B28iGi7h.mjs +0 -1001
  39. package/dist/remote-source-plane-BAqEHSgm.js +0 -1030
  40. package/embed-dist/assets/index-B23riUw8.js +0 -81
  41. package/dist/{model-download-service-C-IHWnXx-BPy6aoAx.mjs → model-download-service-C-IHWnXx-3Mmeob3l.mjs} +1 -1
  42. package/dist/{model-download-service-C-IHWnXx-BLXzxpRB.js → model-download-service-C-IHWnXx-D326YNnt.js} +1 -1
@@ -1,7 +1,7 @@
1
1
  const require_chunk = require("../chunk-D6vf50IK.js");
2
- const require_dist = require("../dist-ClE6_i0p.js");
2
+ const require_dist = require("../dist-Bn6Uq4vq.js");
3
3
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
4
- const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BLXzxpRB.js");
4
+ const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-D326YNnt.js");
5
5
  let node_child_process = require("node:child_process");
6
6
  let node_fs = require("node:fs");
7
7
  let node_path = require("node:path");
@@ -1,6 +1,6 @@
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";
1
+ import { A as recordingCapability, B as DeviceType, D as nodePin, H as hydrateSchema, I as errMsg, J as selectAssignedProfileSlots, L as BaseAddon, M as storageEvictableCapability, T as migrateConfigToBands, at as string, it as record, s as EVENT_PAD_MS, st as EventCategory, u as RecordingConfigSchema } from "../dist-CnwwDzX3.mjs";
2
2
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
3
- import { t as createFileDataPlaneHandler } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
3
+ import { t as createFileDataPlaneHandler } from "../model-download-service-C-IHWnXx-3Mmeob3l.mjs";
4
4
  import { spawn } from "node:child_process";
5
5
  import { promises } from "node:fs";
6
6
  import path from "node:path";
@@ -0,0 +1,25 @@
1
+ //#region src/pipeline-runner/remote-restream.ts
2
+ /**
3
+ * Runner-side mode selection (pure): whether an attach payload routes this
4
+ * camera through the remote-source leg. Absent `frameSource` (a pre-P2c
5
+ * payload) and `local-broker` both take the co-located broker path.
6
+ */
7
+ function isRemoteRestream(frameSource) {
8
+ return frameSource !== void 0 && frameSource.kind === "remote-restream";
9
+ }
10
+ /**
11
+ * Resolve WHICH broker profile a camStream id feeds for a device (pure).
12
+ *
13
+ * `getStreamWithCodec` targets a PROFILE (its assigned camStream), while the
14
+ * runner is dispatched with camStream ids (`motionStreamId` /
15
+ * `detectionStreamId`). The broker's profile slots carry the mapping
16
+ * (`sourceCamStreamId`); a stream no slot feeds returns `null` — the acquire
17
+ * fails fast and the caller's backoff retries (the assignment may still be
18
+ * propagating).
19
+ */
20
+ function profileForStreamId(slots, deviceId, streamId) {
21
+ for (const slot of slots) if (slot.deviceId === deviceId && slot.sourceCamStreamId === streamId) return slot.profile;
22
+ return null;
23
+ }
24
+ //#endregion
25
+ export { profileForStreamId as n, isRemoteRestream as t };
@@ -0,0 +1,36 @@
1
+ //#region src/pipeline-runner/remote-restream.ts
2
+ /**
3
+ * Runner-side mode selection (pure): whether an attach payload routes this
4
+ * camera through the remote-source leg. Absent `frameSource` (a pre-P2c
5
+ * payload) and `local-broker` both take the co-located broker path.
6
+ */
7
+ function isRemoteRestream(frameSource) {
8
+ return frameSource !== void 0 && frameSource.kind === "remote-restream";
9
+ }
10
+ /**
11
+ * Resolve WHICH broker profile a camStream id feeds for a device (pure).
12
+ *
13
+ * `getStreamWithCodec` targets a PROFILE (its assigned camStream), while the
14
+ * runner is dispatched with camStream ids (`motionStreamId` /
15
+ * `detectionStreamId`). The broker's profile slots carry the mapping
16
+ * (`sourceCamStreamId`); a stream no slot feeds returns `null` — the acquire
17
+ * fails fast and the caller's backoff retries (the assignment may still be
18
+ * propagating).
19
+ */
20
+ function profileForStreamId(slots, deviceId, streamId) {
21
+ for (const slot of slots) if (slot.deviceId === deviceId && slot.sourceCamStreamId === streamId) return slot.profile;
22
+ return null;
23
+ }
24
+ //#endregion
25
+ Object.defineProperty(exports, "isRemoteRestream", {
26
+ enumerable: true,
27
+ get: function() {
28
+ return isRemoteRestream;
29
+ }
30
+ });
31
+ Object.defineProperty(exports, "profileForStreamId", {
32
+ enumerable: true,
33
+ get: function() {
34
+ return profileForStreamId;
35
+ }
36
+ });
@@ -189,6 +189,66 @@ var FrameSlot = class {
189
189
  }
190
190
  };
191
191
  //#endregion
192
+ //#region src/session-decode/lease-activity-gate.ts
193
+ /**
194
+ * Demand-driven gate for the native-frame lease capture (see
195
+ * `decode-worker-child.ts` `captureNativeFrame`).
196
+ *
197
+ * Eagerly downloading EVERY delivered frame from the GPU at native resolution
198
+ * is sustained memory-bandwidth burn a small host (N100) cannot afford when
199
+ * nothing consumes the leases — live 2026-07-16: dev 584 ran 24 native-4K
200
+ * downloads/s with `nativeCropHits:0` for hours, and two such sessions
201
+ * saturated the box (load ~9 on 4 cores) until delivered fps collapsed to 0.
202
+ *
203
+ * The gate keeps capture ON only while there is plausible demand:
204
+ * - `arm()` at dial start — the FIRST track of a fresh on-motion session gets
205
+ * native-quality media before any crop request has been seen;
206
+ * - `arm()` on every native-crop request — post-analysis snapshot crops and
207
+ * the detail plane re-arm it continuously while tracks are active;
208
+ * - after `windowMs` without demand it closes and per-frame downloads stop
209
+ * (a later crop request simply misses once — the caller's designed
210
+ * detection-frame-crop fallback — and re-arms the gate for the retry).
211
+ *
212
+ * `windowMs: 0` disables gating entirely (always active — the legacy eager
213
+ * behaviour).
214
+ */
215
+ var LeaseActivityGate = class {
216
+ windowMs;
217
+ now;
218
+ armedUntil = 0;
219
+ disabled = false;
220
+ constructor(windowMs, now = Date.now) {
221
+ this.windowMs = windowMs;
222
+ this.now = now;
223
+ }
224
+ /** Open (or slide) the demand window from now. A no-op while disabled. */
225
+ arm() {
226
+ if (this.windowMs === 0) return;
227
+ this.armedUntil = this.now() + this.windowMs;
228
+ }
229
+ /**
230
+ * Force the gate closed regardless of demand — for a download path that has
231
+ * PROVEN broken this dial (an add/receive failure). Rebuilding the vaapi
232
+ * filtergraph per failing frame both leaks (node-av reclaims hw pools only
233
+ * by process exit) and burns GPU churn, so a broken path stays off until
234
+ * {@link reset} (the next dial). Overrides `windowMs: 0` too.
235
+ */
236
+ disable() {
237
+ this.disabled = true;
238
+ }
239
+ /** New dial: clear the broken flag AND any stale demand (caller re-arms explicitly). */
240
+ reset() {
241
+ this.disabled = false;
242
+ this.armedUntil = 0;
243
+ }
244
+ /** Whether lease capture should run for the current frame. */
245
+ get active() {
246
+ if (this.disabled) return false;
247
+ if (this.windowMs === 0) return true;
248
+ return this.now() < this.armedUntil;
249
+ }
250
+ };
251
+ //#endregion
192
252
  //#region src/session-decode/native-frame-ring.ts
193
253
  /**
194
254
  * A hard-capped, insertion-ordered map from worker frameId → retained frame.
@@ -408,6 +468,16 @@ var NATIVE_LEASE_TTL_MS = (() => {
408
468
  return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 500;
409
469
  })();
410
470
  /**
471
+ * Demand window (ms) for the lease-capture {@link LeaseActivityGate}: eager
472
+ * per-frame native downloads run only within this window of the last
473
+ * native-crop request (or dial start). `0` disables gating (legacy always-on
474
+ * eager capture). `CAMSTACK_SESSION_NATIVE_LEASE_ACTIVITY_MS` (default 15000).
475
+ */
476
+ var NATIVE_LEASE_ACTIVITY_WINDOW_MS = (() => {
477
+ const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_LEASE_ACTIVITY_MS"]);
478
+ return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 15e3;
479
+ })();
480
+ /**
411
481
  * The libav GPU scale filter for a hwaccel backend, or `null` when none is
412
482
  * known here — those backends fall back to the software crop+scale path.
413
483
  * Mirrors `addon-decoder-ffmpeg/src/ffmpeg-args.ts` `gpuScaleFilterForBackend`.
@@ -660,6 +730,13 @@ var DecodeWorkerChild = class {
660
730
  ttlMs: NATIVE_LEASE_TTL_MS
661
731
  });
662
732
  /**
733
+ * Demand gate for lease capture — armed at dial start + on every native-crop
734
+ * request; while cold, {@link captureNativeFrame} declines retention so the
735
+ * per-frame native-res GPU→RAM download (the N100 saturation source) stops
736
+ * on cameras nothing is cropping. See `lease-activity-gate.ts`.
737
+ */
738
+ leaseGate = new LeaseActivityGate(NATIVE_LEASE_ACTIVITY_WINDOW_MS);
739
+ /**
663
740
  * DEDICATED full-frame GPU→system download filtergraph
664
741
  * (`scale_<be>=iw:ih,hwdownload,format=nv12,format=yuv420p`) used ONLY to
665
742
  * materialize a HW surface into a leasable software frame at NATIVE
@@ -670,6 +747,10 @@ var DecodeWorkerChild = class {
670
747
  */
671
748
  nativeLeaseDownloadFilter = null;
672
749
  nativeLeaseDownloadFilterKey = "";
750
+ /** Root-cause instrumentation for the sporadic EIO/EINVAL add-frame failures (task: lease-filter EIO). */
751
+ leaseDownloadFilterUses = 0;
752
+ leaseDownloadFilterBuiltAt = 0;
753
+ dialStartedAt = 0;
673
754
  /** Throttled native-crop hit/miss counters, emitted with the throughput line. */
674
755
  nativeCropHits = 0;
675
756
  nativeCropMisses = 0;
@@ -838,6 +919,7 @@ var DecodeWorkerChild = class {
838
919
  * session to software on a GPU-filter error).
839
920
  */
840
921
  handleNativeCrop(requestId, frameId, bbox, maxWidth) {
922
+ this.leaseGate.arm();
841
923
  const frame = this.leaseStore.get(frameId)?.frame ?? this.resolveRetainedFrame(frameId);
842
924
  if (!frame) {
843
925
  this.nativeCropMisses++;
@@ -885,6 +967,7 @@ var DecodeWorkerChild = class {
885
967
  */
886
968
  captureNativeFrame(frameId, frame) {
887
969
  if (!this.leaseStore.enabled) return this.nativeRing.retain(frameId, frame);
970
+ if (!this.leaseGate.active) return false;
888
971
  if (frame.isHwFrame()) {
889
972
  const software = this.downloadToSoftware(frame);
890
973
  frame.free();
@@ -911,7 +994,11 @@ var DecodeWorkerChild = class {
911
994
  for (let i = 1; i < outputs.length; i++) outputs[i]?.free();
912
995
  return first;
913
996
  } catch (err) {
914
- this.emitStderr(`decode-worker-child: native lease download failed — ${errMessage(err)}\n`);
997
+ this.emitStderr(`decode-worker-child: native lease download failed — ${errMessage(err)} [forensics: frame=${frame.width}x${frame.height} fmt=${frame.format} hw=${frame.isHwFrame()} filterUses=${this.leaseDownloadFilterUses} filterAgeMs=${Date.now() - this.leaseDownloadFilterBuiltAt} dialAgeMs=${Date.now() - this.dialStartedAt} decoded=${this.framesDecoded}]\n`);
998
+ this.nativeLeaseDownloadFilter?.close();
999
+ this.nativeLeaseDownloadFilter = null;
1000
+ this.nativeLeaseDownloadFilterKey = "";
1001
+ this.leaseGate.disable();
915
1002
  return null;
916
1003
  }
917
1004
  }
@@ -919,7 +1006,10 @@ var DecodeWorkerChild = class {
919
1006
  ensureLeaseDownloadFilter(nav, scaleFilter, srcW, srcH) {
920
1007
  const key = `${srcW}x${srcH}`;
921
1008
  const cached = this.nativeLeaseDownloadFilter;
922
- if (cached && key === this.nativeLeaseDownloadFilterKey) return cached;
1009
+ if (cached && key === this.nativeLeaseDownloadFilterKey) {
1010
+ this.leaseDownloadFilterUses++;
1011
+ return cached;
1012
+ }
923
1013
  this.nativeLeaseDownloadFilter?.close();
924
1014
  this.nativeLeaseDownloadFilter = null;
925
1015
  this.nativeLeaseDownloadFilterKey = "";
@@ -927,6 +1017,8 @@ var DecodeWorkerChild = class {
927
1017
  const filter = nav.FilterAPI.create(description, { hardware: this.hwContext });
928
1018
  this.nativeLeaseDownloadFilter = filter;
929
1019
  this.nativeLeaseDownloadFilterKey = key;
1020
+ this.leaseDownloadFilterUses = 1;
1021
+ this.leaseDownloadFilterBuiltAt = Date.now();
930
1022
  return filter;
931
1023
  }
932
1024
  /** Map a normalized bbox to an even-aligned pixel crop + (optionally capped) target. */
@@ -1078,6 +1170,9 @@ var DecodeWorkerChild = class {
1078
1170
  * Same demuxer options + `exitOnError: false` bad-frame tolerance throughout.
1079
1171
  */
1080
1172
  async dialAndDecode(nav, C, url) {
1173
+ this.leaseGate.reset();
1174
+ this.leaseGate.arm();
1175
+ this.dialStartedAt = Date.now();
1081
1176
  this.abortController = new AbortController();
1082
1177
  const demuxer = await nav.Demuxer.open(url, {
1083
1178
  options: buildDemuxerOptions(),
@@ -185,6 +185,66 @@ var FrameSlot = class {
185
185
  }
186
186
  };
187
187
  //#endregion
188
+ //#region src/session-decode/lease-activity-gate.ts
189
+ /**
190
+ * Demand-driven gate for the native-frame lease capture (see
191
+ * `decode-worker-child.ts` `captureNativeFrame`).
192
+ *
193
+ * Eagerly downloading EVERY delivered frame from the GPU at native resolution
194
+ * is sustained memory-bandwidth burn a small host (N100) cannot afford when
195
+ * nothing consumes the leases — live 2026-07-16: dev 584 ran 24 native-4K
196
+ * downloads/s with `nativeCropHits:0` for hours, and two such sessions
197
+ * saturated the box (load ~9 on 4 cores) until delivered fps collapsed to 0.
198
+ *
199
+ * The gate keeps capture ON only while there is plausible demand:
200
+ * - `arm()` at dial start — the FIRST track of a fresh on-motion session gets
201
+ * native-quality media before any crop request has been seen;
202
+ * - `arm()` on every native-crop request — post-analysis snapshot crops and
203
+ * the detail plane re-arm it continuously while tracks are active;
204
+ * - after `windowMs` without demand it closes and per-frame downloads stop
205
+ * (a later crop request simply misses once — the caller's designed
206
+ * detection-frame-crop fallback — and re-arms the gate for the retry).
207
+ *
208
+ * `windowMs: 0` disables gating entirely (always active — the legacy eager
209
+ * behaviour).
210
+ */
211
+ var LeaseActivityGate = class {
212
+ windowMs;
213
+ now;
214
+ armedUntil = 0;
215
+ disabled = false;
216
+ constructor(windowMs, now = Date.now) {
217
+ this.windowMs = windowMs;
218
+ this.now = now;
219
+ }
220
+ /** Open (or slide) the demand window from now. A no-op while disabled. */
221
+ arm() {
222
+ if (this.windowMs === 0) return;
223
+ this.armedUntil = this.now() + this.windowMs;
224
+ }
225
+ /**
226
+ * Force the gate closed regardless of demand — for a download path that has
227
+ * PROVEN broken this dial (an add/receive failure). Rebuilding the vaapi
228
+ * filtergraph per failing frame both leaks (node-av reclaims hw pools only
229
+ * by process exit) and burns GPU churn, so a broken path stays off until
230
+ * {@link reset} (the next dial). Overrides `windowMs: 0` too.
231
+ */
232
+ disable() {
233
+ this.disabled = true;
234
+ }
235
+ /** New dial: clear the broken flag AND any stale demand (caller re-arms explicitly). */
236
+ reset() {
237
+ this.disabled = false;
238
+ this.armedUntil = 0;
239
+ }
240
+ /** Whether lease capture should run for the current frame. */
241
+ get active() {
242
+ if (this.disabled) return false;
243
+ if (this.windowMs === 0) return true;
244
+ return this.now() < this.armedUntil;
245
+ }
246
+ };
247
+ //#endregion
188
248
  //#region src/session-decode/native-frame-ring.ts
189
249
  /**
190
250
  * A hard-capped, insertion-ordered map from worker frameId → retained frame.
@@ -404,6 +464,16 @@ var NATIVE_LEASE_TTL_MS = (() => {
404
464
  return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 500;
405
465
  })();
406
466
  /**
467
+ * Demand window (ms) for the lease-capture {@link LeaseActivityGate}: eager
468
+ * per-frame native downloads run only within this window of the last
469
+ * native-crop request (or dial start). `0` disables gating (legacy always-on
470
+ * eager capture). `CAMSTACK_SESSION_NATIVE_LEASE_ACTIVITY_MS` (default 15000).
471
+ */
472
+ var NATIVE_LEASE_ACTIVITY_WINDOW_MS = (() => {
473
+ const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_LEASE_ACTIVITY_MS"]);
474
+ return Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 15e3;
475
+ })();
476
+ /**
407
477
  * The libav GPU scale filter for a hwaccel backend, or `null` when none is
408
478
  * known here — those backends fall back to the software crop+scale path.
409
479
  * Mirrors `addon-decoder-ffmpeg/src/ffmpeg-args.ts` `gpuScaleFilterForBackend`.
@@ -656,6 +726,13 @@ var DecodeWorkerChild = class {
656
726
  ttlMs: NATIVE_LEASE_TTL_MS
657
727
  });
658
728
  /**
729
+ * Demand gate for lease capture — armed at dial start + on every native-crop
730
+ * request; while cold, {@link captureNativeFrame} declines retention so the
731
+ * per-frame native-res GPU→RAM download (the N100 saturation source) stops
732
+ * on cameras nothing is cropping. See `lease-activity-gate.ts`.
733
+ */
734
+ leaseGate = new LeaseActivityGate(NATIVE_LEASE_ACTIVITY_WINDOW_MS);
735
+ /**
659
736
  * DEDICATED full-frame GPU→system download filtergraph
660
737
  * (`scale_<be>=iw:ih,hwdownload,format=nv12,format=yuv420p`) used ONLY to
661
738
  * materialize a HW surface into a leasable software frame at NATIVE
@@ -666,6 +743,10 @@ var DecodeWorkerChild = class {
666
743
  */
667
744
  nativeLeaseDownloadFilter = null;
668
745
  nativeLeaseDownloadFilterKey = "";
746
+ /** Root-cause instrumentation for the sporadic EIO/EINVAL add-frame failures (task: lease-filter EIO). */
747
+ leaseDownloadFilterUses = 0;
748
+ leaseDownloadFilterBuiltAt = 0;
749
+ dialStartedAt = 0;
669
750
  /** Throttled native-crop hit/miss counters, emitted with the throughput line. */
670
751
  nativeCropHits = 0;
671
752
  nativeCropMisses = 0;
@@ -834,6 +915,7 @@ var DecodeWorkerChild = class {
834
915
  * session to software on a GPU-filter error).
835
916
  */
836
917
  handleNativeCrop(requestId, frameId, bbox, maxWidth) {
918
+ this.leaseGate.arm();
837
919
  const frame = this.leaseStore.get(frameId)?.frame ?? this.resolveRetainedFrame(frameId);
838
920
  if (!frame) {
839
921
  this.nativeCropMisses++;
@@ -881,6 +963,7 @@ var DecodeWorkerChild = class {
881
963
  */
882
964
  captureNativeFrame(frameId, frame) {
883
965
  if (!this.leaseStore.enabled) return this.nativeRing.retain(frameId, frame);
966
+ if (!this.leaseGate.active) return false;
884
967
  if (frame.isHwFrame()) {
885
968
  const software = this.downloadToSoftware(frame);
886
969
  frame.free();
@@ -907,7 +990,11 @@ var DecodeWorkerChild = class {
907
990
  for (let i = 1; i < outputs.length; i++) outputs[i]?.free();
908
991
  return first;
909
992
  } catch (err) {
910
- this.emitStderr(`decode-worker-child: native lease download failed — ${errMessage(err)}\n`);
993
+ this.emitStderr(`decode-worker-child: native lease download failed — ${errMessage(err)} [forensics: frame=${frame.width}x${frame.height} fmt=${frame.format} hw=${frame.isHwFrame()} filterUses=${this.leaseDownloadFilterUses} filterAgeMs=${Date.now() - this.leaseDownloadFilterBuiltAt} dialAgeMs=${Date.now() - this.dialStartedAt} decoded=${this.framesDecoded}]\n`);
994
+ this.nativeLeaseDownloadFilter?.close();
995
+ this.nativeLeaseDownloadFilter = null;
996
+ this.nativeLeaseDownloadFilterKey = "";
997
+ this.leaseGate.disable();
911
998
  return null;
912
999
  }
913
1000
  }
@@ -915,7 +1002,10 @@ var DecodeWorkerChild = class {
915
1002
  ensureLeaseDownloadFilter(nav, scaleFilter, srcW, srcH) {
916
1003
  const key = `${srcW}x${srcH}`;
917
1004
  const cached = this.nativeLeaseDownloadFilter;
918
- if (cached && key === this.nativeLeaseDownloadFilterKey) return cached;
1005
+ if (cached && key === this.nativeLeaseDownloadFilterKey) {
1006
+ this.leaseDownloadFilterUses++;
1007
+ return cached;
1008
+ }
919
1009
  this.nativeLeaseDownloadFilter?.close();
920
1010
  this.nativeLeaseDownloadFilter = null;
921
1011
  this.nativeLeaseDownloadFilterKey = "";
@@ -923,6 +1013,8 @@ var DecodeWorkerChild = class {
923
1013
  const filter = nav.FilterAPI.create(description, { hardware: this.hwContext });
924
1014
  this.nativeLeaseDownloadFilter = filter;
925
1015
  this.nativeLeaseDownloadFilterKey = key;
1016
+ this.leaseDownloadFilterUses = 1;
1017
+ this.leaseDownloadFilterBuiltAt = Date.now();
926
1018
  return filter;
927
1019
  }
928
1020
  /** Map a normalized bbox to an even-aligned pixel crop + (optionally capped) target. */
@@ -1074,6 +1166,9 @@ var DecodeWorkerChild = class {
1074
1166
  * Same demuxer options + `exitOnError: false` bad-frame tolerance throughout.
1075
1167
  */
1076
1168
  async dialAndDecode(nav, C, url) {
1169
+ this.leaseGate.reset();
1170
+ this.leaseGate.arm();
1171
+ this.dialStartedAt = Date.now();
1077
1172
  this.abortController = new AbortController();
1078
1173
  const demuxer = await nav.Demuxer.open(url, {
1079
1174
  options: buildDemuxerOptions(),
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-ClE6_i0p.js");
1
+ const require_dist = require("./dist-Bn6Uq4vq.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";
@@ -341,6 +341,40 @@ var OBJECT_DETECTION_MODELS = [
341
341
  openvino: ovFormat(hf("objectDetection/yolo26/openvino/camstack-yolo26x.xml"), 213)
342
342
  }
343
343
  },
344
+ {
345
+ id: "ssd-mobilenet-v2-coco-edgetpu",
346
+ name: "SSD MobileNet V2 (Coral)",
347
+ description: "SSD MobileNet V2 COCO — EdgeTPU-compiled full-integer TFLite, 300×300; runs on the Coral USB Edge TPU (~11 ms/frame)",
348
+ inputSize: {
349
+ width: 300,
350
+ height: 300
351
+ },
352
+ labels: [],
353
+ preprocessMode: "letterbox",
354
+ postprocessor: "ssd",
355
+ formats: { tflite: {
356
+ url: "https://github.com/google-coral/test_data/raw/master/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite",
357
+ sizeMB: 6.6,
358
+ runtimes: ["python"]
359
+ } }
360
+ },
361
+ {
362
+ id: "efficientdet-lite0-edgetpu",
363
+ name: "EfficientDet-Lite0 (Coral)",
364
+ description: "EfficientDet-Lite0 COCO — EdgeTPU-compiled full-integer TFLite, 320×320; higher accuracy than SSD MobileNet on the Coral USB Edge TPU",
365
+ inputSize: {
366
+ width: 320,
367
+ height: 320
368
+ },
369
+ labels: [],
370
+ preprocessMode: "letterbox",
371
+ postprocessor: "ssd",
372
+ formats: { tflite: {
373
+ url: "https://github.com/google-coral/test_data/raw/master/efficientdet_lite0_320_ptq_edgetpu.tflite",
374
+ sizeMB: 5.7,
375
+ runtimes: ["python"]
376
+ } }
377
+ },
344
378
  {
345
379
  id: "scrypted-yolov9t-relu",
346
380
  legacy: true,
@@ -656,6 +690,7 @@ var PLATE_OCR_MODELS = [{
656
690
  width: 384,
657
691
  height: 64
658
692
  },
693
+ inputChannels: 1,
659
694
  labels: [{
660
695
  id: "text",
661
696
  name: "Plate Text"
@@ -1018,7 +1053,13 @@ var ANIMAL_10_LABELS = [
1018
1053
  "sheep",
1019
1054
  "spider"
1020
1055
  ];
1021
- var VGG_G2_CHARSET = ["", ..."0123456789!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("")];
1056
+ var VGG_G2_CHARSET = [
1057
+ "",
1058
+ ..."0123456789".split(""),
1059
+ ..."!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ ".split(""),
1060
+ " ",
1061
+ ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("")
1062
+ ];
1022
1063
  /**
1023
1064
  * Detail-subtree dispatch cadence a `PipelineStepBase` step gets when its
1024
1065
  * definition literal doesn't declare one explicitly. Conservative default
@@ -1056,6 +1097,7 @@ var ObjectDetectionStep = class {
1056
1097
  ],
1057
1098
  models: [...OBJECT_DETECTION_MODELS],
1058
1099
  defaultModelId: "yolo26n",
1100
+ defaultModelIdByFormat: { tflite: "ssd-mobilenet-v2-coco-edgetpu" },
1059
1101
  defaultConfidence: .5,
1060
1102
  labels: require_dist.COCO_80_LABELS.map((l) => l.id),
1061
1103
  classMap: require_dist.COCO_TO_MACRO
@@ -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-CrjLGrkj.mjs";
1
+ import { S as hfModelUrl, a as COCO_TO_MACRO, i as COCO_80_LABELS, r as AUDIO_MACRO_LABELS } from "./dist-CnwwDzX3.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";
@@ -341,6 +341,40 @@ var OBJECT_DETECTION_MODELS = [
341
341
  openvino: ovFormat(hf("objectDetection/yolo26/openvino/camstack-yolo26x.xml"), 213)
342
342
  }
343
343
  },
344
+ {
345
+ id: "ssd-mobilenet-v2-coco-edgetpu",
346
+ name: "SSD MobileNet V2 (Coral)",
347
+ description: "SSD MobileNet V2 COCO — EdgeTPU-compiled full-integer TFLite, 300×300; runs on the Coral USB Edge TPU (~11 ms/frame)",
348
+ inputSize: {
349
+ width: 300,
350
+ height: 300
351
+ },
352
+ labels: [],
353
+ preprocessMode: "letterbox",
354
+ postprocessor: "ssd",
355
+ formats: { tflite: {
356
+ url: "https://github.com/google-coral/test_data/raw/master/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite",
357
+ sizeMB: 6.6,
358
+ runtimes: ["python"]
359
+ } }
360
+ },
361
+ {
362
+ id: "efficientdet-lite0-edgetpu",
363
+ name: "EfficientDet-Lite0 (Coral)",
364
+ description: "EfficientDet-Lite0 COCO — EdgeTPU-compiled full-integer TFLite, 320×320; higher accuracy than SSD MobileNet on the Coral USB Edge TPU",
365
+ inputSize: {
366
+ width: 320,
367
+ height: 320
368
+ },
369
+ labels: [],
370
+ preprocessMode: "letterbox",
371
+ postprocessor: "ssd",
372
+ formats: { tflite: {
373
+ url: "https://github.com/google-coral/test_data/raw/master/efficientdet_lite0_320_ptq_edgetpu.tflite",
374
+ sizeMB: 5.7,
375
+ runtimes: ["python"]
376
+ } }
377
+ },
344
378
  {
345
379
  id: "scrypted-yolov9t-relu",
346
380
  legacy: true,
@@ -656,6 +690,7 @@ var PLATE_OCR_MODELS = [{
656
690
  width: 384,
657
691
  height: 64
658
692
  },
693
+ inputChannels: 1,
659
694
  labels: [{
660
695
  id: "text",
661
696
  name: "Plate Text"
@@ -1018,7 +1053,13 @@ var ANIMAL_10_LABELS = [
1018
1053
  "sheep",
1019
1054
  "spider"
1020
1055
  ];
1021
- var VGG_G2_CHARSET = ["", ..."0123456789!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("")];
1056
+ var VGG_G2_CHARSET = [
1057
+ "",
1058
+ ..."0123456789".split(""),
1059
+ ..."!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ ".split(""),
1060
+ " ",
1061
+ ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("")
1062
+ ];
1022
1063
  /**
1023
1064
  * Detail-subtree dispatch cadence a `PipelineStepBase` step gets when its
1024
1065
  * definition literal doesn't declare one explicitly. Conservative default
@@ -1056,6 +1097,7 @@ var ObjectDetectionStep = class {
1056
1097
  ],
1057
1098
  models: [...OBJECT_DETECTION_MODELS],
1058
1099
  defaultModelId: "yolo26n",
1100
+ defaultModelIdByFormat: { tflite: "ssd-mobilenet-v2-coco-edgetpu" },
1059
1101
  defaultConfidence: .5,
1060
1102
  labels: COCO_80_LABELS.map((l) => l.id),
1061
1103
  classMap: COCO_TO_MACRO