@camstack/addon-pipeline 1.2.217 → 1.2.219

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 (123) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/{default-detection-model-BwNEV8Wl.mjs → default-detection-model-Dsk1a5pY.mjs} +1 -1
  4. package/dist/{default-detection-model-DzzuINe-.js → default-detection-model-d7-Q_LMo.js} +1 -1
  5. package/dist/detection-pipeline/index.js +5 -5
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-DgeXZfwn.mjs → dist-CKK0CBUW.mjs} +96 -5
  8. package/dist/{dist-D7hxiBnC.js → dist-wtNhtVVo.js} +96 -5
  9. package/dist/{lazy-sharp-BSkbiuz5.js → lazy-sharp-zoQ07I43.js} +1 -1
  10. package/dist/motion-wasm/index.js +2 -2
  11. package/dist/motion-wasm/index.mjs +1 -1
  12. package/dist/{node-DDhmPcOx.js → node-BduNi21Z.js} +1 -1
  13. package/dist/{node-C9GcBrwk.mjs → node-CrNBWFbA.mjs} +1 -1
  14. package/dist/pipeline-runner/index.js +13 -6
  15. package/dist/pipeline-runner/index.mjs +12 -5
  16. package/dist/{process-memory-Dw3CGS3D.mjs → process-memory-BRQOtDN1.mjs} +1 -1
  17. package/dist/{process-memory-ENqmiiis.js → process-memory-Dwl0MFPH.js} +1 -1
  18. package/dist/recorder/index.js +92 -14
  19. package/dist/recorder/index.mjs +92 -14
  20. package/dist/{segment-demux-js-sLs8mbZQ.js → segment-demux-js-CFOU3biG.js} +1 -1
  21. package/dist/{segment-demux-js-BGbKlotF.mjs → segment-demux-js-D2o9ShNL.mjs} +1 -1
  22. package/dist/session-decode/decode-worker-child.js +2 -2
  23. package/dist/session-decode/decode-worker-child.mjs +1 -1
  24. package/dist/stream-broker/_stub.js +1 -1
  25. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BULotDCb.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DN_IoShh.mjs} +3 -3
  26. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-yJ2CyixO.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-7hqrrd88.mjs} +1 -1
  27. package/dist/stream-broker/demux-worker-child.js +1 -1
  28. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  29. package/dist/stream-broker/{hostInit-_RQxF72h.mjs → hostInit-n7c9pNlt.mjs} +3 -3
  30. package/dist/stream-broker/index.js +3 -3
  31. package/dist/stream-broker/index.mjs +3 -3
  32. package/dist/stream-broker/remoteEntry.js +1 -1
  33. package/dist/{worker-protocol-SzmaLNnP.mjs → worker-protocol-Cx3Nqreq.mjs} +1 -1
  34. package/dist/{worker-protocol-DGql9R4g.js → worker-protocol-iWTFnnUM.js} +1 -1
  35. package/package.json +1 -1
  36. package/python/__pycache__/inference_pool.cpython-312.pyc +0 -0
  37. package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
  38. package/python/__pycache__/inference_pool.cpython-314.pyc +0 -0
  39. package/python/__pycache__/test_inference_pool_backpressure.cpython-312-pytest-9.1.1.pyc +0 -0
  40. package/python/__pycache__/test_inference_pool_backpressure.cpython-312.pyc +0 -0
  41. package/python/__pycache__/test_inference_pool_backpressure.cpython-314.pyc +0 -0
  42. package/python/__pycache__/test_inference_pool_coreml_cache.cpython-312-pytest-9.1.1.pyc +0 -0
  43. package/python/__pycache__/test_inference_pool_coreml_cache.cpython-312.pyc +0 -0
  44. package/python/__pycache__/test_inference_pool_coreml_cache.cpython-314.pyc +0 -0
  45. package/python/__pycache__/test_inference_pool_deadline.cpython-314.pyc +0 -0
  46. package/python/__pycache__/test_inference_pool_device_selection.cpython-312-pytest-9.1.1.pyc +0 -0
  47. package/python/__pycache__/test_inference_pool_device_selection.cpython-312.pyc +0 -0
  48. package/python/__pycache__/test_inference_pool_device_selection.cpython-314.pyc +0 -0
  49. package/python/__pycache__/test_inference_pool_layout.cpython-312-pytest-9.1.1.pyc +0 -0
  50. package/python/__pycache__/test_inference_pool_layout.cpython-314.pyc +0 -0
  51. package/python/__pycache__/test_inference_pool_memstats.cpython-312-pytest-9.1.1.pyc +0 -0
  52. package/python/__pycache__/test_inference_pool_memstats.cpython-312.pyc +0 -0
  53. package/python/__pycache__/test_inference_pool_memstats.cpython-314.pyc +0 -0
  54. package/python/__pycache__/test_inference_pool_ov_ppp.cpython-312-pytest-9.1.1.pyc +0 -0
  55. package/python/__pycache__/test_inference_pool_ov_ppp.cpython-312.pyc +0 -0
  56. package/python/__pycache__/test_inference_pool_ov_ppp.cpython-314.pyc +0 -0
  57. package/python/__pycache__/test_inference_pool_preprocess.cpython-312-pytest-9.1.1.pyc +0 -0
  58. package/python/__pycache__/test_inference_pool_preprocess.cpython-314.pyc +0 -0
  59. package/python/__pycache__/test_inference_pool_reconfigure.cpython-314.pyc +0 -0
  60. package/python/__pycache__/test_inference_pool_static_batch.cpython-312-pytest-9.1.1.pyc +0 -0
  61. package/python/__pycache__/test_inference_pool_static_batch.cpython-312.pyc +0 -0
  62. package/python/__pycache__/test_inference_pool_static_batch.cpython-314.pyc +0 -0
  63. package/python/__pycache__/yamnet_audio.cpython-314.pyc +0 -0
  64. package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
  65. package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
  66. package/python/postprocessors/__pycache__/__init__.cpython-314.pyc +0 -0
  67. package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
  68. package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
  69. package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
  70. package/python/postprocessors/__pycache__/arcface.cpython-314.pyc +0 -0
  71. package/python/postprocessors/__pycache__/clip.cpython-312.pyc +0 -0
  72. package/python/postprocessors/__pycache__/clip.cpython-313.pyc +0 -0
  73. package/python/postprocessors/__pycache__/clip.cpython-314.pyc +0 -0
  74. package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
  75. package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
  76. package/python/postprocessors/__pycache__/ctc.cpython-314.pyc +0 -0
  77. package/python/postprocessors/__pycache__/plate_slots.cpython-312.pyc +0 -0
  78. package/python/postprocessors/__pycache__/plate_slots.cpython-314.pyc +0 -0
  79. package/python/postprocessors/__pycache__/rfdetr.cpython-312.pyc +0 -0
  80. package/python/postprocessors/__pycache__/rfdetr.cpython-314.pyc +0 -0
  81. package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
  82. package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
  83. package/python/postprocessors/__pycache__/saliency.cpython-314.pyc +0 -0
  84. package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
  85. package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
  86. package/python/postprocessors/__pycache__/scrfd.cpython-314.pyc +0 -0
  87. package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
  88. package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
  89. package/python/postprocessors/__pycache__/softmax.cpython-314.pyc +0 -0
  90. package/python/postprocessors/__pycache__/ssd.cpython-312.pyc +0 -0
  91. package/python/postprocessors/__pycache__/ssd.cpython-313.pyc +0 -0
  92. package/python/postprocessors/__pycache__/ssd.cpython-314.pyc +0 -0
  93. package/python/postprocessors/__pycache__/test_clip.cpython-312-pytest-9.1.1.pyc +0 -0
  94. package/python/postprocessors/__pycache__/test_clip.cpython-313-pytest-9.1.1.pyc +0 -0
  95. package/python/postprocessors/__pycache__/test_ctc.cpython-312-pytest-9.1.1.pyc +0 -0
  96. package/python/postprocessors/__pycache__/test_ctc.cpython-312.pyc +0 -0
  97. package/python/postprocessors/__pycache__/test_ctc.cpython-313-pytest-9.1.1.pyc +0 -0
  98. package/python/postprocessors/__pycache__/test_ctc.cpython-313.pyc +0 -0
  99. package/python/postprocessors/__pycache__/test_ctc.cpython-314.pyc +0 -0
  100. package/python/postprocessors/__pycache__/test_plate_slots.cpython-312-pytest-9.1.1.pyc +0 -0
  101. package/python/postprocessors/__pycache__/test_rfdetr.cpython-312-pytest-9.1.1.pyc +0 -0
  102. package/python/postprocessors/__pycache__/test_rfdetr.cpython-314.pyc +0 -0
  103. package/python/postprocessors/__pycache__/test_saliency.cpython-312-pytest-9.1.1.pyc +0 -0
  104. package/python/postprocessors/__pycache__/test_saliency.cpython-313-pytest-9.1.1.pyc +0 -0
  105. package/python/postprocessors/__pycache__/test_saliency.cpython-314.pyc +0 -0
  106. package/python/postprocessors/__pycache__/test_softmax.cpython-312-pytest-9.1.1.pyc +0 -0
  107. package/python/postprocessors/__pycache__/test_softmax.cpython-314.pyc +0 -0
  108. package/python/postprocessors/__pycache__/test_yolo_nms.cpython-312-pytest-9.1.1.pyc +0 -0
  109. package/python/postprocessors/__pycache__/test_yolo_nms.cpython-314.pyc +0 -0
  110. package/python/postprocessors/__pycache__/test_yolo_nms.cpython-39.pyc +0 -0
  111. package/python/postprocessors/__pycache__/test_yolonas.cpython-312-pytest-9.1.1.pyc +0 -0
  112. package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
  113. package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
  114. package/python/postprocessors/__pycache__/yamnet.cpython-314.pyc +0 -0
  115. package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
  116. package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
  117. package/python/postprocessors/__pycache__/yolo.cpython-314.pyc +0 -0
  118. package/python/postprocessors/__pycache__/yolo.cpython-39.pyc +0 -0
  119. package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
  120. package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
  121. package/python/postprocessors/__pycache__/yolo_seg.cpython-314.pyc +0 -0
  122. package/python/postprocessors/__pycache__/yolonas.cpython-312.pyc +0 -0
  123. package/python/postprocessors/__pycache__/yolonas.cpython-314.pyc +0 -0
@@ -2,8 +2,8 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-D7hxiBnC.js");
6
- const require_node = require("../node-DDhmPcOx.js");
5
+ const require_dist = require("../dist-wtNhtVVo.js");
6
+ const require_node = require("../node-BduNi21Z.js");
7
7
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
8
8
  const require_restream_intent = require("../restream-intent-B1Difcnq.js");
9
9
  const require_retire_root_keys = require("../retire-root-keys-Csb8z2f3.js");
@@ -3817,6 +3817,40 @@ async function segmentsOfHour(root, deviceId, profile, hourStartMs) {
3817
3817
  * the directory route serve the profile the client asked for when it exists,
3818
3818
  * and fall back to the availability pick when it does not.
3819
3819
  */
3820
+ /**
3821
+ * EVERY profile this camera holds footage in, across every root.
3822
+ *
3823
+ * {@link availabilityProfile} deliberately returns ONE — the timeline is a
3824
+ * single bar. That is right for the bar and wrong for the ANSWER: on
3825
+ * 2026-09-10 `getAvailability` reported twenty `high` ranges for camera 615
3826
+ * and the conclusion drawn was that this deployment records no `low` at all,
3827
+ * while `/media-fast/recordingsLow/615/low/` held 14 GB on a different
3828
+ * location (D433).
3829
+ *
3830
+ * Unions across roots for exactly that reason: `high` and `low` do not share a
3831
+ * location here, so a per-root answer sees one of them.
3832
+ *
3833
+ * Non-emptiness, not existence — the same rule {@link availabilityProfile}
3834
+ * keeps, so a directory left behind by a config change is not offered. And
3835
+ * only {@link KNOWN_PROFILES}: the camera dir also hosts `events/` and the
3836
+ * retired `strips/` trees, which a walker that trusts readdir would present as
3837
+ * playable.
3838
+ *
3839
+ * `list` is injectable for the test; production passes the module's own
3840
+ * directory read.
3841
+ */
3842
+ async function profilesWithFootage(roots, deviceId, list = listDir) {
3843
+ const found = /* @__PURE__ */ new Set();
3844
+ for (const root of roots) {
3845
+ const entries = await list(node_path.default.join(root, String(deviceId)));
3846
+ for (const entry of entries) {
3847
+ if (!KNOWN_PROFILES$1.has(entry)) continue;
3848
+ if (found.has(entry)) continue;
3849
+ if ((await list(node_path.default.join(root, String(deviceId), entry))).length > 0) found.add(entry);
3850
+ }
3851
+ }
3852
+ return [...found].sort();
3853
+ }
3820
3854
  async function profileHasFootage(root, deviceId, profile) {
3821
3855
  return (await listDir(node_path.default.join(root, String(deviceId), profile))).length > 0;
3822
3856
  }
@@ -3938,6 +3972,8 @@ var CalendarIndex = class {
3938
3972
  segsReadAt = /* @__PURE__ */ new Map();
3939
3973
  /** The profile availability reads from, resolved once per device. */
3940
3974
  profileFor = /* @__PURE__ */ new Map();
3975
+ profilesFor = /* @__PURE__ */ new Map();
3976
+ profilesInFlight = /* @__PURE__ */ new Map();
3941
3977
  /** `deviceId:profile` → whether the device records that profile, resolved
3942
3978
  * once. Same stability trade as {@link profileFor}: a config change that
3943
3979
  * adds a profile mid-session is picked up on restart, not on a timer. */
@@ -4220,6 +4256,31 @@ var CalendarIndex = class {
4220
4256
  * reads at a time and they ALL ask for the profile before the first answer
4221
4257
  * lands, so a cache alone let the pick run eight times. Parallelism found it.
4222
4258
  */
4259
+ /**
4260
+ * Every profile this camera holds footage in, across every root — cached
4261
+ * like the pick, because it is the same shallow read (D433).
4262
+ *
4263
+ * {@link availabilityProfileFor} answers with ONE; this says what the one
4264
+ * was chosen FROM, so a caller cannot mistake the timeline's single bar for
4265
+ * the whole archive.
4266
+ */
4267
+ async profilesWithFootageFor(deviceId) {
4268
+ const cached = this.profilesFor.get(deviceId);
4269
+ if (cached !== void 0) return cached;
4270
+ const running = this.profilesInFlight.get(deviceId);
4271
+ if (running) return running;
4272
+ const resolve = (async () => {
4273
+ const found = await profilesWithFootage(this.deps.roots(), deviceId);
4274
+ this.profilesFor.set(deviceId, found);
4275
+ return found;
4276
+ })();
4277
+ this.profilesInFlight.set(deviceId, resolve);
4278
+ try {
4279
+ return await resolve;
4280
+ } finally {
4281
+ if (this.profilesInFlight.get(deviceId) === resolve) this.profilesInFlight.delete(deviceId);
4282
+ }
4283
+ }
4223
4284
  async availabilityProfileFor(deviceId) {
4224
4285
  const cached = this.profileFor.get(deviceId);
4225
4286
  if (cached !== void 0) return cached;
@@ -5864,6 +5925,8 @@ var TIME_RE = /time=(\d+):(\d+):(\d+(?:\.\d+)?)/;
5864
5925
  * whatever ffmpeg build is installed.
5865
5926
  */
5866
5927
  var EXPORT_MIN_PLAUSIBLE_BYTES = 1024;
5928
+ /** ffmpeg's own announcement of that pass — the only signal it gives. */
5929
+ var FASTSTART_RE = /Starting second pass: moving the moov atom/i;
5867
5930
  /** Expected OUTPUT duration (seconds) for progress scaling. */
5868
5931
  function expectedOutSeconds(rec) {
5869
5932
  const rangeSec = Math.max(.001, (rec.toMs - rec.fromMs) / 1e3);
@@ -6173,6 +6236,7 @@ var ExportEngine = class {
6173
6236
  args
6174
6237
  } });
6175
6238
  const stallMs = this.deps.stallMs ?? 9e4;
6239
+ const faststartStallMs = this.deps.faststartStallMs ?? 9e5;
6176
6240
  return new Promise((resolve) => {
6177
6241
  const proc = this.deps.spawn(this.deps.ffmpegPath ?? "ffmpeg", args);
6178
6242
  this.active.set(id, proc);
@@ -6184,14 +6248,16 @@ var ExportEngine = class {
6184
6248
  stallTimer = null;
6185
6249
  }
6186
6250
  };
6251
+ let budgetMs = stallMs;
6187
6252
  const armStall = () => {
6188
6253
  clearStall();
6254
+ const armedFor = budgetMs;
6189
6255
  stallTimer = setTimeout(() => {
6190
- stderrTail.push(`ffmpeg stalled: no progress for ${stallMs}ms`);
6256
+ stderrTail.push(`ffmpeg stalled: no progress for ${armedFor}ms`);
6191
6257
  try {
6192
6258
  proc.kill("SIGKILL");
6193
6259
  } catch {}
6194
- }, stallMs);
6260
+ }, armedFor);
6195
6261
  };
6196
6262
  armStall();
6197
6263
  proc.stderr?.on("data", (chunk) => {
@@ -6202,6 +6268,10 @@ var ExportEngine = class {
6202
6268
  stderrTail.push(trimmed);
6203
6269
  if (stderrTail.length > STDERR_TAIL_LINES$1) stderrTail.shift();
6204
6270
  }
6271
+ if (FASTSTART_RE.test(line)) {
6272
+ budgetMs = faststartStallMs;
6273
+ armStall();
6274
+ }
6205
6275
  const m = TIME_RE.exec(line);
6206
6276
  if (m) {
6207
6277
  armStall();
@@ -7379,13 +7449,15 @@ function buildRecordingProvider(deps) {
7379
7449
  * and by the batch alike — the batch collapses the TRANSPORT, and sharing the
7380
7450
  * function is what makes that literally true instead of aspirational.
7381
7451
  */
7382
- const availabilityFor = async (deviceId, fromMs, toMs) => {
7452
+ const availabilityFor = async (deviceId, fromMs, toMs, requestedProfile) => {
7383
7453
  const calendar = deps.calendar;
7384
7454
  if (calendar) {
7385
- const profile = await calendar.availabilityProfileFor(deviceId);
7455
+ const profilesWithFootage = await calendar.profilesWithFootageFor(deviceId);
7456
+ const profile = await calendar.servedProfileFor(deviceId, requestedProfile);
7386
7457
  if (profile === null) return {
7387
7458
  deviceId,
7388
- ranges: []
7459
+ ranges: [],
7460
+ profilesWithFootage: [...profilesWithFootage]
7389
7461
  };
7390
7462
  const ranges = [];
7391
7463
  for (const r of await calendar.rangesIn(deviceId, profile, fromMs, toMs)) ranges.push({
@@ -7395,16 +7467,20 @@ function buildRecordingProvider(deps) {
7395
7467
  });
7396
7468
  return {
7397
7469
  deviceId,
7398
- ranges
7470
+ ranges,
7471
+ profilesWithFootage: [...profilesWithFootage]
7399
7472
  };
7400
7473
  }
7474
+ const profiles = new Set(deps.index.segments(deviceId).map((s) => s.profile));
7475
+ const asked = requestedProfile !== void 0 && profiles.has(requestedProfile);
7401
7476
  return {
7402
7477
  deviceId,
7403
- ranges: [...new Set(deps.index.segments(deviceId).map((s) => s.profile))].flatMap((profile) => deps.index.ranges(deviceId, profile, fromMs, toMs, RANGE_MERGE_GAP_MS).map((r) => ({
7478
+ ranges: [...profiles].filter((p) => !asked || p === requestedProfile).flatMap((profile) => deps.index.ranges(deviceId, profile, fromMs, toMs, RANGE_MERGE_GAP_MS).map((r) => ({
7404
7479
  profile,
7405
7480
  startMs: r.startMs,
7406
7481
  endMs: r.endMs
7407
- })))
7482
+ }))),
7483
+ profilesWithFootage: [...profiles].sort()
7408
7484
  };
7409
7485
  };
7410
7486
  /** The per-camera day read. Same rule as {@link availabilityFor}: one body. */
@@ -7434,7 +7510,7 @@ function buildRecordingProvider(deps) {
7434
7510
  getStatus: async ({ deviceId }) => {
7435
7511
  return statusFor(deps, deviceId, await loadDeviceConfig(deps.configStore, deviceId));
7436
7512
  },
7437
- getAvailability: ({ deviceId, fromMs, toMs }) => availabilityFor(deviceId, fromMs, toMs),
7513
+ getAvailability: ({ deviceId, fromMs, toMs, profile }) => availabilityFor(deviceId, fromMs, toMs, profile),
7438
7514
  /**
7439
7515
  * `getAvailability` for a SET of cameras. Same body per camera, run
7440
7516
  * concurrently; only the transport collapses.
@@ -7448,11 +7524,12 @@ function buildRecordingProvider(deps) {
7448
7524
  refuseOverCap("getAvailabilityBatch", deviceIds.length);
7449
7525
  return Promise.all(deviceIds.map(async (deviceId) => {
7450
7526
  try {
7451
- const { ranges } = await availabilityFor(deviceId, fromMs, toMs);
7527
+ const { ranges, profilesWithFootage } = await availabilityFor(deviceId, fromMs, toMs);
7452
7528
  return {
7453
7529
  deviceId,
7454
7530
  read: "read",
7455
- ranges
7531
+ ranges,
7532
+ profilesWithFootage
7456
7533
  };
7457
7534
  } catch (err) {
7458
7535
  deps.logger.warn("recording availability could not be read", {
@@ -7466,7 +7543,8 @@ function buildRecordingProvider(deps) {
7466
7543
  return {
7467
7544
  deviceId,
7468
7545
  read: "unreadable",
7469
- ranges: []
7546
+ ranges: [],
7547
+ profilesWithFootage: []
7470
7548
  };
7471
7549
  }
7472
7550
  }));
@@ -1,5 +1,5 @@
1
- import { At as storageEvictableCapability, Ct as recordingSignalCapability, D as RecordingSignalStatusSchema, E as RecordingConfigSchema, Et as resolveLocationMode, Gt as DeviceFeature, H as deriveBatteryPresence, It as errMsg, Jt as hydrateSchema, Kt as DeviceType, Nt as stripRetiredBandPreBufferSec, Ot as resolveRecordingProfiles, P as batteryCapability, Q as evictionPolicyOfLocation, Qt as nodePin, R as customAction, St as recordingExportCapability, T as RECORDING_EXPORT_MAX_READ_BYTES, Ut as BaseAddon, V as defineCustomActions, W as deriveRecordingMode, Y as evaluateSensorEdge, Z as evictionPolicyForMode, an as boolean, ct as mayReadLocation, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, dn as preprocess, dt as modeMayWrite, et as gbToBytes, fn as record, g as ExportRecordSchema, hn as EventCategory, i as BatteryStatusSchema, in as array, it as loadContributionCapability, jt as storageOccupancyCapability, ln as number, lt as mayWriteToLocation, nn as sleep$1, pn as string, rn as _enum, rt as isFirstLevelMacroClass, tn as selectAssignedProfileSlots, un as object, ut as migrateLegacyDeviceSignalConfig, x as OpsLogEntrySchema, xt as recordingCapability } from "../dist-DgeXZfwn.mjs";
2
- import { a as physicalRootOf, i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-C9GcBrwk.mjs";
1
+ import { At as storageEvictableCapability, Ct as recordingSignalCapability, D as RecordingSignalStatusSchema, E as RecordingConfigSchema, Et as resolveLocationMode, Gt as DeviceFeature, H as deriveBatteryPresence, It as errMsg, Jt as hydrateSchema, Kt as DeviceType, Nt as stripRetiredBandPreBufferSec, Ot as resolveRecordingProfiles, P as batteryCapability, Q as evictionPolicyOfLocation, Qt as nodePin, R as customAction, St as recordingExportCapability, T as RECORDING_EXPORT_MAX_READ_BYTES, Ut as BaseAddon, V as defineCustomActions, W as deriveRecordingMode, Y as evaluateSensorEdge, Z as evictionPolicyForMode, an as boolean, ct as mayReadLocation, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, dn as preprocess, dt as modeMayWrite, et as gbToBytes, fn as record, g as ExportRecordSchema, hn as EventCategory, i as BatteryStatusSchema, in as array, it as loadContributionCapability, jt as storageOccupancyCapability, ln as number, lt as mayWriteToLocation, nn as sleep$1, pn as string, rn as _enum, rt as isFirstLevelMacroClass, tn as selectAssignedProfileSlots, un as object, ut as migrateLegacyDeviceSignalConfig, x as OpsLogEntrySchema, xt as recordingCapability } from "../dist-CKK0CBUW.mjs";
2
+ import { a as physicalRootOf, i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-CrNBWFbA.mjs";
3
3
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
4
4
  import { c as PREBUFFER_MAX_MS, r as RECORDING_PRE_ROLL_REQUEST_MS, s as withRecordingIntent } from "../restream-intent-DWe2kInY.mjs";
5
5
  import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DZvlso2l.mjs";
@@ -3812,6 +3812,40 @@ async function segmentsOfHour(root, deviceId, profile, hourStartMs) {
3812
3812
  * the directory route serve the profile the client asked for when it exists,
3813
3813
  * and fall back to the availability pick when it does not.
3814
3814
  */
3815
+ /**
3816
+ * EVERY profile this camera holds footage in, across every root.
3817
+ *
3818
+ * {@link availabilityProfile} deliberately returns ONE — the timeline is a
3819
+ * single bar. That is right for the bar and wrong for the ANSWER: on
3820
+ * 2026-09-10 `getAvailability` reported twenty `high` ranges for camera 615
3821
+ * and the conclusion drawn was that this deployment records no `low` at all,
3822
+ * while `/media-fast/recordingsLow/615/low/` held 14 GB on a different
3823
+ * location (D433).
3824
+ *
3825
+ * Unions across roots for exactly that reason: `high` and `low` do not share a
3826
+ * location here, so a per-root answer sees one of them.
3827
+ *
3828
+ * Non-emptiness, not existence — the same rule {@link availabilityProfile}
3829
+ * keeps, so a directory left behind by a config change is not offered. And
3830
+ * only {@link KNOWN_PROFILES}: the camera dir also hosts `events/` and the
3831
+ * retired `strips/` trees, which a walker that trusts readdir would present as
3832
+ * playable.
3833
+ *
3834
+ * `list` is injectable for the test; production passes the module's own
3835
+ * directory read.
3836
+ */
3837
+ async function profilesWithFootage(roots, deviceId, list = listDir) {
3838
+ const found = /* @__PURE__ */ new Set();
3839
+ for (const root of roots) {
3840
+ const entries = await list(path.join(root, String(deviceId)));
3841
+ for (const entry of entries) {
3842
+ if (!KNOWN_PROFILES$1.has(entry)) continue;
3843
+ if (found.has(entry)) continue;
3844
+ if ((await list(path.join(root, String(deviceId), entry))).length > 0) found.add(entry);
3845
+ }
3846
+ }
3847
+ return [...found].sort();
3848
+ }
3815
3849
  async function profileHasFootage(root, deviceId, profile) {
3816
3850
  return (await listDir(path.join(root, String(deviceId), profile))).length > 0;
3817
3851
  }
@@ -3933,6 +3967,8 @@ var CalendarIndex = class {
3933
3967
  segsReadAt = /* @__PURE__ */ new Map();
3934
3968
  /** The profile availability reads from, resolved once per device. */
3935
3969
  profileFor = /* @__PURE__ */ new Map();
3970
+ profilesFor = /* @__PURE__ */ new Map();
3971
+ profilesInFlight = /* @__PURE__ */ new Map();
3936
3972
  /** `deviceId:profile` → whether the device records that profile, resolved
3937
3973
  * once. Same stability trade as {@link profileFor}: a config change that
3938
3974
  * adds a profile mid-session is picked up on restart, not on a timer. */
@@ -4215,6 +4251,31 @@ var CalendarIndex = class {
4215
4251
  * reads at a time and they ALL ask for the profile before the first answer
4216
4252
  * lands, so a cache alone let the pick run eight times. Parallelism found it.
4217
4253
  */
4254
+ /**
4255
+ * Every profile this camera holds footage in, across every root — cached
4256
+ * like the pick, because it is the same shallow read (D433).
4257
+ *
4258
+ * {@link availabilityProfileFor} answers with ONE; this says what the one
4259
+ * was chosen FROM, so a caller cannot mistake the timeline's single bar for
4260
+ * the whole archive.
4261
+ */
4262
+ async profilesWithFootageFor(deviceId) {
4263
+ const cached = this.profilesFor.get(deviceId);
4264
+ if (cached !== void 0) return cached;
4265
+ const running = this.profilesInFlight.get(deviceId);
4266
+ if (running) return running;
4267
+ const resolve = (async () => {
4268
+ const found = await profilesWithFootage(this.deps.roots(), deviceId);
4269
+ this.profilesFor.set(deviceId, found);
4270
+ return found;
4271
+ })();
4272
+ this.profilesInFlight.set(deviceId, resolve);
4273
+ try {
4274
+ return await resolve;
4275
+ } finally {
4276
+ if (this.profilesInFlight.get(deviceId) === resolve) this.profilesInFlight.delete(deviceId);
4277
+ }
4278
+ }
4218
4279
  async availabilityProfileFor(deviceId) {
4219
4280
  const cached = this.profileFor.get(deviceId);
4220
4281
  if (cached !== void 0) return cached;
@@ -5858,6 +5919,8 @@ var TIME_RE = /time=(\d+):(\d+):(\d+(?:\.\d+)?)/;
5858
5919
  * whatever ffmpeg build is installed.
5859
5920
  */
5860
5921
  var EXPORT_MIN_PLAUSIBLE_BYTES = 1024;
5922
+ /** ffmpeg's own announcement of that pass — the only signal it gives. */
5923
+ var FASTSTART_RE = /Starting second pass: moving the moov atom/i;
5861
5924
  /** Expected OUTPUT duration (seconds) for progress scaling. */
5862
5925
  function expectedOutSeconds(rec) {
5863
5926
  const rangeSec = Math.max(.001, (rec.toMs - rec.fromMs) / 1e3);
@@ -6167,6 +6230,7 @@ var ExportEngine = class {
6167
6230
  args
6168
6231
  } });
6169
6232
  const stallMs = this.deps.stallMs ?? 9e4;
6233
+ const faststartStallMs = this.deps.faststartStallMs ?? 9e5;
6170
6234
  return new Promise((resolve) => {
6171
6235
  const proc = this.deps.spawn(this.deps.ffmpegPath ?? "ffmpeg", args);
6172
6236
  this.active.set(id, proc);
@@ -6178,14 +6242,16 @@ var ExportEngine = class {
6178
6242
  stallTimer = null;
6179
6243
  }
6180
6244
  };
6245
+ let budgetMs = stallMs;
6181
6246
  const armStall = () => {
6182
6247
  clearStall();
6248
+ const armedFor = budgetMs;
6183
6249
  stallTimer = setTimeout(() => {
6184
- stderrTail.push(`ffmpeg stalled: no progress for ${stallMs}ms`);
6250
+ stderrTail.push(`ffmpeg stalled: no progress for ${armedFor}ms`);
6185
6251
  try {
6186
6252
  proc.kill("SIGKILL");
6187
6253
  } catch {}
6188
- }, stallMs);
6254
+ }, armedFor);
6189
6255
  };
6190
6256
  armStall();
6191
6257
  proc.stderr?.on("data", (chunk) => {
@@ -6196,6 +6262,10 @@ var ExportEngine = class {
6196
6262
  stderrTail.push(trimmed);
6197
6263
  if (stderrTail.length > STDERR_TAIL_LINES$1) stderrTail.shift();
6198
6264
  }
6265
+ if (FASTSTART_RE.test(line)) {
6266
+ budgetMs = faststartStallMs;
6267
+ armStall();
6268
+ }
6199
6269
  const m = TIME_RE.exec(line);
6200
6270
  if (m) {
6201
6271
  armStall();
@@ -7373,13 +7443,15 @@ function buildRecordingProvider(deps) {
7373
7443
  * and by the batch alike — the batch collapses the TRANSPORT, and sharing the
7374
7444
  * function is what makes that literally true instead of aspirational.
7375
7445
  */
7376
- const availabilityFor = async (deviceId, fromMs, toMs) => {
7446
+ const availabilityFor = async (deviceId, fromMs, toMs, requestedProfile) => {
7377
7447
  const calendar = deps.calendar;
7378
7448
  if (calendar) {
7379
- const profile = await calendar.availabilityProfileFor(deviceId);
7449
+ const profilesWithFootage = await calendar.profilesWithFootageFor(deviceId);
7450
+ const profile = await calendar.servedProfileFor(deviceId, requestedProfile);
7380
7451
  if (profile === null) return {
7381
7452
  deviceId,
7382
- ranges: []
7453
+ ranges: [],
7454
+ profilesWithFootage: [...profilesWithFootage]
7383
7455
  };
7384
7456
  const ranges = [];
7385
7457
  for (const r of await calendar.rangesIn(deviceId, profile, fromMs, toMs)) ranges.push({
@@ -7389,16 +7461,20 @@ function buildRecordingProvider(deps) {
7389
7461
  });
7390
7462
  return {
7391
7463
  deviceId,
7392
- ranges
7464
+ ranges,
7465
+ profilesWithFootage: [...profilesWithFootage]
7393
7466
  };
7394
7467
  }
7468
+ const profiles = new Set(deps.index.segments(deviceId).map((s) => s.profile));
7469
+ const asked = requestedProfile !== void 0 && profiles.has(requestedProfile);
7395
7470
  return {
7396
7471
  deviceId,
7397
- ranges: [...new Set(deps.index.segments(deviceId).map((s) => s.profile))].flatMap((profile) => deps.index.ranges(deviceId, profile, fromMs, toMs, RANGE_MERGE_GAP_MS).map((r) => ({
7472
+ ranges: [...profiles].filter((p) => !asked || p === requestedProfile).flatMap((profile) => deps.index.ranges(deviceId, profile, fromMs, toMs, RANGE_MERGE_GAP_MS).map((r) => ({
7398
7473
  profile,
7399
7474
  startMs: r.startMs,
7400
7475
  endMs: r.endMs
7401
- })))
7476
+ }))),
7477
+ profilesWithFootage: [...profiles].sort()
7402
7478
  };
7403
7479
  };
7404
7480
  /** The per-camera day read. Same rule as {@link availabilityFor}: one body. */
@@ -7428,7 +7504,7 @@ function buildRecordingProvider(deps) {
7428
7504
  getStatus: async ({ deviceId }) => {
7429
7505
  return statusFor(deps, deviceId, await loadDeviceConfig(deps.configStore, deviceId));
7430
7506
  },
7431
- getAvailability: ({ deviceId, fromMs, toMs }) => availabilityFor(deviceId, fromMs, toMs),
7507
+ getAvailability: ({ deviceId, fromMs, toMs, profile }) => availabilityFor(deviceId, fromMs, toMs, profile),
7432
7508
  /**
7433
7509
  * `getAvailability` for a SET of cameras. Same body per camera, run
7434
7510
  * concurrently; only the transport collapses.
@@ -7442,11 +7518,12 @@ function buildRecordingProvider(deps) {
7442
7518
  refuseOverCap("getAvailabilityBatch", deviceIds.length);
7443
7519
  return Promise.all(deviceIds.map(async (deviceId) => {
7444
7520
  try {
7445
- const { ranges } = await availabilityFor(deviceId, fromMs, toMs);
7521
+ const { ranges, profilesWithFootage } = await availabilityFor(deviceId, fromMs, toMs);
7446
7522
  return {
7447
7523
  deviceId,
7448
7524
  read: "read",
7449
- ranges
7525
+ ranges,
7526
+ profilesWithFootage
7450
7527
  };
7451
7528
  } catch (err) {
7452
7529
  deps.logger.warn("recording availability could not be read", {
@@ -7460,7 +7537,8 @@ function buildRecordingProvider(deps) {
7460
7537
  return {
7461
7538
  deviceId,
7462
7539
  read: "unreadable",
7463
- ranges: []
7540
+ ranges: [],
7541
+ profilesWithFootage: []
7464
7542
  };
7465
7543
  }
7466
7544
  }));
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-D7hxiBnC.js");
1
+ const require_dist = require("./dist-wtNhtVVo.js");
2
2
  let node_child_process = require("node:child_process");
3
3
  //#region ../../node_modules/mp4box/dist/mp4box.all.js
4
4
  var require_mp4box_all = /* @__PURE__ */ require_dist.__commonJSMin(((exports) => {
@@ -1,5 +1,5 @@
1
1
  import { r as __toESM, t as __commonJSMin } from "./chunk-DnnnRqeS.mjs";
2
- import { It as errMsg } from "./dist-DgeXZfwn.mjs";
2
+ import { It as errMsg } from "./dist-CKK0CBUW.mjs";
3
3
  import { spawn } from "node:child_process";
4
4
  //#region ../../node_modules/mp4box/dist/mp4box.all.js
5
5
  var require_mp4box_all = /* @__PURE__ */ __commonJSMin(((exports) => {
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_worker_protocol = require("../worker-protocol-DGql9R4g.js");
3
- const require_lazy_sharp = require("../lazy-sharp-BSkbiuz5.js");
2
+ const require_worker_protocol = require("../worker-protocol-iWTFnnUM.js");
3
+ const require_lazy_sharp = require("../lazy-sharp-zoQ07I43.js");
4
4
  //#region src/session-decode/color-conversion.ts
5
5
  /**
6
6
  * Explicit YUV→RGB colorspace/range resolution for the session-decode worker.
@@ -1,4 +1,4 @@
1
- import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-SzmaLNnP.mjs";
1
+ import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-Cx3Nqreq.mjs";
2
2
  import { n as setSharpWarnSink, r as hostExternalEntryUrls, t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
3
3
  //#region src/session-decode/color-conversion.ts
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, p as c, r as l, s as u, t as d, u as f } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DICVQkNi.mjs";
2
2
  import { a as p, i as m, n as h, o as g, r as _, t as v } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.js-C9j-2lBe.mjs";
3
3
  import { n as y, r as b, t as x } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-XO0-Pyu6.mjs";
4
- import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-yJ2CyixO.mjs";
4
+ import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-7hqrrd88.mjs";
5
5
  import { n as C, t as w } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-BO7TIbJV.mjs";
6
6
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils.js
7
7
  var T = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), E = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), D = (e) => {
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
3
3
  var e = {
4
4
  "@camstack/sdk": {
5
5
  name: "@camstack/sdk",
6
- version: "1.2.95",
6
+ version: "1.2.97",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_stream_broker_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.175",
21
+ version: "1.2.177",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_stream_broker_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.143",
36
+ version: "1.2.145",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_stream_broker_widgets",