@camstack/addon-pipeline 1.2.307 → 1.2.308

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 (31) 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-DgdiQtQb.js → default-detection-model-2rHeRf0Y.js} +1 -1
  4. package/dist/{default-detection-model-BV9bIuOo.mjs → default-detection-model-DATyir0t.mjs} +1 -1
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-CyJWk-_f.js → dist-CdRsXAov.js} +79 -1
  8. package/dist/{dist-DdVMFlJ6.mjs → dist-jy-7xoW5.mjs} +74 -2
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/{node-COu_tHy4.mjs → node-NCge1qZM.mjs} +1 -53
  12. package/dist/{node-HsWG5fGp.js → node-pWHpwpBa.js} +1 -53
  13. package/dist/pipeline-runner/index.js +3 -3
  14. package/dist/pipeline-runner/index.mjs +3 -3
  15. package/dist/{process-memory-Bz3oIMzl.mjs → process-memory-4K7QaQab.mjs} +1 -1
  16. package/dist/{process-memory-TQXG7qrJ.js → process-memory-C4ayPnww.js} +1 -1
  17. package/dist/recorder/index.js +82 -15
  18. package/dist/recorder/index.mjs +82 -15
  19. package/dist/{segment-demux-js-BrKa7tpr.js → segment-demux-js-0bM2Anej.js} +22 -4
  20. package/dist/{segment-demux-js-CEJn4BR0.mjs → segment-demux-js-BaI2VdPX.mjs} +17 -5
  21. package/dist/stream-broker/_stub.js +1 -1
  22. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DdYUVQSA.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CkxD__Uy.mjs} +1 -1
  23. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DEPTgXS_.mjs +26 -0
  24. package/dist/stream-broker/demux-worker-child.js +1 -1
  25. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  26. package/dist/stream-broker/{hostInit-D--a9rVM.mjs → hostInit-D56Gr-hD.mjs} +1 -1
  27. package/dist/stream-broker/index.js +305 -95
  28. package/dist/stream-broker/index.mjs +305 -95
  29. package/dist/stream-broker/remoteEntry.js +1 -1
  30. package/package.json +1 -1
  31. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DPHv0v3Q.mjs +0 -26
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-CyJWk-_f.js");
7
- const require_node = require("../node-HsWG5fGp.js");
6
+ const require_dist = require("../dist-CdRsXAov.js");
7
+ const require_node = require("../node-pWHpwpBa.js");
8
8
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
9
9
  const require_restream_intent = require("../restream-intent-7uDNqMKs.js");
10
10
  const require_retire_root_keys = require("../retire-root-keys-Csb8z2f3.js");
@@ -3450,7 +3450,20 @@ var StillFrameService = class {
3450
3450
  return null;
3451
3451
  }
3452
3452
  try {
3453
- const jpeg = await this.decodeFrame(abs, source.offsetSec);
3453
+ let bin;
3454
+ try {
3455
+ bin = await this.deps.resolveFfmpeg();
3456
+ } catch (err) {
3457
+ this.deps.logger.error("still: refused — no pinned ffmpeg on this node (D540)", {
3458
+ tags: { deviceId },
3459
+ meta: {
3460
+ epochMs,
3461
+ error: err instanceof Error ? err.message : String(err)
3462
+ }
3463
+ });
3464
+ return null;
3465
+ }
3466
+ const jpeg = await this.decodeFrame(bin, abs, source.offsetSec);
3454
3467
  if (jpeg === null || jpeg.byteLength === 0) return null;
3455
3468
  return {
3456
3469
  jpeg,
@@ -3461,7 +3474,7 @@ var StillFrameService = class {
3461
3474
  }
3462
3475
  }
3463
3476
  /** Run ONE ffmpeg input-seek + single-frame decode; JPEG bytes on stdout. */
3464
- decodeFrame(absPath, offsetSec) {
3477
+ decodeFrame(bin, absPath, offsetSec) {
3465
3478
  const args = [
3466
3479
  "-hide_banner",
3467
3480
  "-loglevel",
@@ -3477,7 +3490,6 @@ var StillFrameService = class {
3477
3490
  "pipe:1"
3478
3491
  ];
3479
3492
  const spawn = this.deps.spawn ?? node_child_process.spawn;
3480
- const bin = this.deps.ffmpegPath ?? "ffmpeg";
3481
3493
  return new Promise((resolve) => {
3482
3494
  let child;
3483
3495
  try {
@@ -7549,15 +7561,32 @@ var ExportEngine = class {
7549
7561
  } catch {}
7550
7562
  }
7551
7563
  }
7552
- runFfmpeg(id, deviceId, args, progress) {
7564
+ async runFfmpeg(id, deviceId, args, progress) {
7565
+ let ffmpegPath;
7566
+ try {
7567
+ ffmpegPath = await this.deps.resolveFfmpeg();
7568
+ } catch (err) {
7569
+ const reason = `no pinned ffmpeg on this node (D540): ${err instanceof Error ? err.message : String(err)}`;
7570
+ this.deps.logger.error("export render refused — no pinned ffmpeg on this node (D540)", {
7571
+ tags: { deviceId },
7572
+ meta: {
7573
+ exportId: id,
7574
+ error: reason
7575
+ }
7576
+ });
7577
+ return {
7578
+ code: null,
7579
+ stderrTail: [reason]
7580
+ };
7581
+ }
7553
7582
  this.deps.logger.debug("export ffmpeg argv", { meta: {
7554
7583
  exportId: id,
7555
7584
  args
7556
7585
  } });
7557
7586
  const stallMs = this.deps.stallMs ?? 9e4;
7558
7587
  const faststartStallMs = this.deps.faststartStallMs ?? 9e5;
7559
- return new Promise((resolve) => {
7560
- const proc = this.deps.spawn(this.deps.ffmpegPath ?? "ffmpeg", args);
7588
+ return await new Promise((resolve) => {
7589
+ const proc = this.deps.spawn(ffmpegPath, args);
7561
7590
  this.active.set(id, proc);
7562
7591
  const stderrTail = [];
7563
7592
  let stallTimer = null;
@@ -9459,6 +9488,17 @@ async function renderFootage(renderDeps, input, ext, buildArgs) {
9459
9488
  const toMs = input.aroundMs + input.postRollSec * 1e3;
9460
9489
  if (!Number.isFinite(fromMs) || !Number.isFinite(toMs) || toMs <= fromMs) throw new Error(`invalid ${ext} window [${fromMs}, ${toMs}) for device ${input.deviceId} — aroundMs=${input.aroundMs} preRollSec=${input.preRollSec} postRollSec=${input.postRollSec}`);
9461
9490
  const renderId = `${ext}-${now()}-${Math.floor(Math.random() * 1e6)}`;
9491
+ let ffmpegPath;
9492
+ try {
9493
+ ffmpegPath = await renderDeps.resolveFfmpeg();
9494
+ } catch (err) {
9495
+ const reason = err instanceof Error ? err.message : String(err);
9496
+ renderDeps.deps.logger.error(`${ext} render refused — no pinned ffmpeg on this node (D540)`, {
9497
+ tags: { deviceId: input.deviceId },
9498
+ meta: { error: reason }
9499
+ });
9500
+ throw new Error(`${ext} render refused for device ${input.deviceId}: no pinned ffmpeg — ${reason}`, { cause: err });
9501
+ }
9462
9502
  const availableEnd = latestFinalizedEnd(renderDeps, input.deviceId);
9463
9503
  if (availableEnd === null || availableEnd < toMs) throw new Error(`${FOOTAGE_NOT_READY}: [${fromMs}, ${toMs}) for device ${input.deviceId} — finalized footage ends at ${availableEnd ?? "never"}`);
9464
9504
  const window = {
@@ -9477,7 +9517,7 @@ async function renderFootage(renderDeps, input, ext, buildArgs) {
9477
9517
  if (playlist === null) throw new Error(`no footage covers [${window.fromMs}, ${window.toMs}) for device ${input.deviceId}`);
9478
9518
  const outPath = node_path.default.join(node_path.default.dirname(playlist.path), `${renderId}.${ext}`);
9479
9519
  try {
9480
- await runFfmpeg(renderDeps, buildArgs(playlist.path, outPath), ext);
9520
+ await runFfmpeg(renderDeps, ffmpegPath, buildArgs(playlist.path, outPath), ext);
9481
9521
  const bytes = await node_fs.promises.readFile(outPath);
9482
9522
  if (bytes.byteLength === 0) throw new Error(`${ext} render produced an empty file`);
9483
9523
  return {
@@ -9514,9 +9554,9 @@ function latestFinalizedEnd(renderDeps, deviceId) {
9514
9554
  * throws ESRCH, which is swallowed, so the cost of being wrong is nothing while
9515
9555
  * the cost of NOT killing is a zombie per render.
9516
9556
  */
9517
- function runFfmpeg(renderDeps, args, ext) {
9557
+ function runFfmpeg(renderDeps, ffmpegPath, args, ext) {
9518
9558
  return new Promise((resolve, reject) => {
9519
- const proc = renderDeps.spawn(renderDeps.ffmpegPath ?? "ffmpeg", args);
9559
+ const proc = renderDeps.spawn(ffmpegPath, args);
9520
9560
  let done = false;
9521
9561
  let timer = null;
9522
9562
  const settle = (err) => {
@@ -15748,7 +15788,7 @@ var SegmentWriter = class {
15748
15788
  start() {
15749
15789
  if (this.stopped || this.proc) return;
15750
15790
  const args = buildPassthroughArgs(this.cfg);
15751
- const proc = this.deps.spawn(this.deps.ffmpegPath ?? "ffmpeg", args);
15791
+ const proc = this.deps.spawn(this.deps.ffmpegPath, args);
15752
15792
  this.proc = proc;
15753
15793
  this.exitWait = new Promise((resolve) => {
15754
15794
  this.resolveExit = resolve;
@@ -17495,6 +17535,19 @@ var RecordingController = class {
17495
17535
  */
17496
17536
  async attachProfile(deviceId, profile, segmentSeconds, audio) {
17497
17537
  const attachProfileStartMs = this.now();
17538
+ let ffmpegPath;
17539
+ try {
17540
+ ffmpegPath = await this.deps.resolveFfmpeg();
17541
+ } catch (err) {
17542
+ this.deps.logger.error("recorder: writer refused — no pinned ffmpeg on this node (D540)", {
17543
+ tags: { deviceId },
17544
+ meta: {
17545
+ profile,
17546
+ error: require_dist.errMsg(err)
17547
+ }
17548
+ });
17549
+ throw new Error(`recorder: no pinned ffmpeg for device ${deviceId}/${profile}`);
17550
+ }
17498
17551
  const placement = await this.resolvePlacement(deviceId, profile);
17499
17552
  const placementDoneMs = this.now();
17500
17553
  const outDir = node_path.default.join(placement.root, STAGING_DIR_NAME, String(deviceId), profile);
@@ -17530,6 +17583,7 @@ var RecordingController = class {
17530
17583
  consumerTag
17531
17584
  }, {
17532
17585
  spawn: this.deps.spawn,
17586
+ ffmpegPath,
17533
17587
  logger: deviceLog,
17534
17588
  deviceId,
17535
17589
  claimCost: (pid) => this.deps.costRegistry?.claim({
@@ -20823,6 +20877,14 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
20823
20877
  * because those children were this process's children.
20824
20878
  */
20825
20879
  costRegistry = new require_node.ChildCostRegistry();
20880
+ /**
20881
+ * This node's PINNED ffmpeg (D540), asked once and shared by every child the
20882
+ * recorder spawns: segment writers, exports, gif/clip renders and stills.
20883
+ * Each of those used to take an optional path that no one passed, and fell
20884
+ * back to a bare `'ffmpeg'` — the HOST's binary. It rejects rather than
20885
+ * naming PATH; each site logs its refusal per camera.
20886
+ */
20887
+ pinnedFfmpeg = require_dist.memoizePinnedFfmpeg(() => this.ctx.deps.ensureFfmpeg());
20826
20888
  /** True when THIS node is the designated recording node (`recordingNodeId`).
20827
20889
  * Every other node stays fully inert — registers no providers, serves no
20828
20890
  * playback, runs no controller. Resolved in `onInitialize`. */
@@ -21216,6 +21278,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
21216
21278
  now: () => Date.now()
21217
21279
  });
21218
21280
  this.controller = new RecordingController({
21281
+ resolveFfmpeg: this.pinnedFfmpeg,
21219
21282
  ...this.stagingGuard !== null ? { stagingGuard: this.stagingGuard } : {},
21220
21283
  recoverStagingDirectory: (input) => this.recoverReapedDirectory(input),
21221
21284
  heapProbe: this.heapProbe,
@@ -21363,12 +21426,14 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
21363
21426
  gifRender: (input) => renderGif({
21364
21427
  deps: recordingDeps,
21365
21428
  exportsDir: this.exportsDirOrThrow(),
21366
- spawn: node_child_process.spawn
21429
+ spawn: node_child_process.spawn,
21430
+ resolveFfmpeg: this.pinnedFfmpeg
21367
21431
  }, input),
21368
21432
  clipRender: (input) => renderClip({
21369
21433
  deps: recordingDeps,
21370
21434
  exportsDir: this.exportsDirOrThrow(),
21371
- spawn: node_child_process.spawn
21435
+ spawn: node_child_process.spawn,
21436
+ resolveFfmpeg: this.pinnedFfmpeg
21372
21437
  }, input),
21373
21438
  locations: () => this.resolvedLocations,
21374
21439
  refreshLocations: () => this.refreshLocations(),
@@ -21497,6 +21562,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
21497
21562
  };
21498
21563
  this.exportEngine = new ExportEngine({
21499
21564
  spawn: node_child_process.spawn,
21565
+ resolveFfmpeg: this.pinnedFfmpeg,
21500
21566
  logger: this.ctx.logger.child("export"),
21501
21567
  state: exportsState,
21502
21568
  resolvePlaylist: async (rec) => {
@@ -21649,7 +21715,8 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
21649
21715
  const stillService = new StillFrameService({
21650
21716
  lowSegmentsAt: (deviceId, epochMs) => this.index.segmentsOverlapping(deviceId, "low", epochMs, epochMs + 1),
21651
21717
  resolveAbsPath: (row) => this.resolveSegmentAbsPath(row),
21652
- logger: this.ctx.logger.child("Still")
21718
+ logger: this.ctx.logger.child("Still"),
21719
+ resolveFfmpeg: this.pinnedFfmpeg
21653
21720
  });
21654
21721
  const served = await this.ctx.dataPlane?.serve({
21655
21722
  prefix: STILL_PREFIX,
@@ -1,5 +1,5 @@
1
- import { $t as videoclipsCapability, An as array, B as VISIT_MERGE_GAP_MS, Ct as mayReadLocation, Dn as selectAssignedProfileSlots, Et as modeMayWrite, F as RECORDING_SOURCE_CAMSTACK_ADDON, Fn as number, Gt as resolveRecordingProfiles, I as RecordingConfigSchema, In as object, It as recordingArchiveCapability, J as clipExportFailure, Jt as storageEvictableCapability, K as batteryCapability, L as RecordingSignalStatusSchema, Ln as preprocess, Lt as recordingCapability, N as RECORDING_EXPORT_MAX_READ_BYTES, On as sleep$1, P as RECORDING_SOURCE_CAMSTACK, Rn as record, Rt as recordingExportCapability, S as ExportRecordSchema, Tt as migrateLegacyDeviceSignalConfig, Ut as resolveLocationMode, Vn as EventCategory, Vt as resolveCreateExportSubject, Y as clipExportFailureOf, Yt as storageOccupancyCapability, Zt as stripRetiredBandPreBufferSec, _n as customAction, a as BatteryStatusSchema, an as evaluateSensorEdge, b as EVENT_PAD_MS, bn as hydrateSchema, cn as DeviceFeature, ct as evictionPolicyForMode, et as deriveBatteryPresence, fn as BaseAddon, g as DEFAULT_EVENTS_BAND_BUFFER_SEC, jn as boolean, k as OpsLogEntrySchema, kn as _enum, ln as DeviceType, lt as evictionPolicyOfLocation, nt as deriveRecordingMode, o as CLIP_BROKER_PACED_RATES, pt as gbToBytes, un as errMsg$1, ut as exportSubjectOf, vn as defineCustomActions, vt as isFirstLevelMacroClass, wn as nodePin, wt as mayWriteToLocation, y as EVENT_OWNER_TYPES, yt as loadContributionCapability, z as VIDEOCLIPS_MAX_OFFER_BYTES, zn as string, zt as recordingSignalCapability } from "../dist-DdVMFlJ6.mjs";
2
- import { a as physicalRootOf, i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-COu_tHy4.mjs";
1
+ import { An as _enum, B as VISIT_MERGE_GAP_MS, Bn as string, Bt as recordingSignalCapability, Ct as mayReadLocation, Dt as modeMayWrite, Et as migrateLegacyDeviceSignalConfig, F as RECORDING_SOURCE_CAMSTACK_ADDON, Hn as EventCategory, Ht as resolveCreateExportSubject, I as RecordingConfigSchema, In as number, J as clipExportFailure, K as batteryCapability, Kt as resolveRecordingProfiles, L as RecordingSignalStatusSchema, Ln as object, Lt as recordingArchiveCapability, Mn as boolean, N as RECORDING_EXPORT_MAX_READ_BYTES, On as selectAssignedProfileSlots, P as RECORDING_SOURCE_CAMSTACK, Qt as stripRetiredBandPreBufferSec, Rn as preprocess, Rt as recordingCapability, S as ExportRecordSchema, Tn as nodePin, Tt as memoizePinnedFfmpeg, Wt as resolveLocationMode, Xt as storageOccupancyCapability, Y as clipExportFailureOf, Yt as storageEvictableCapability, a as BatteryStatusSchema, b as EVENT_PAD_MS, ct as evictionPolicyForMode, dn as errMsg$1, en as videoclipsCapability, et as deriveBatteryPresence, g as DEFAULT_EVENTS_BAND_BUFFER_SEC, jn as array, k as OpsLogEntrySchema, kn as sleep$1, ln as DeviceFeature, lt as evictionPolicyOfLocation, nt as deriveRecordingMode, o as CLIP_BROKER_PACED_RATES, on as evaluateSensorEdge, pn as BaseAddon, pt as gbToBytes, un as DeviceType, ut as exportSubjectOf, vn as customAction, vt as isFirstLevelMacroClass, wt as mayWriteToLocation, xn as hydrateSchema, y as EVENT_OWNER_TYPES, yn as defineCustomActions, yt as loadContributionCapability, z as VIDEOCLIPS_MAX_OFFER_BYTES, zn as record, zt as recordingExportCapability } from "../dist-jy-7xoW5.mjs";
2
+ import { a as physicalRootOf, i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-NCge1qZM.mjs";
3
3
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
4
4
  import { d as PREBUFFER_MAX_MS, l as withRecordingIntent, r as RECORDING_PRE_ROLL_REQUEST_MS } from "../restream-intent-DyGmLAU9.mjs";
5
5
  import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DZvlso2l.mjs";
@@ -3443,7 +3443,20 @@ var StillFrameService = class {
3443
3443
  return null;
3444
3444
  }
3445
3445
  try {
3446
- const jpeg = await this.decodeFrame(abs, source.offsetSec);
3446
+ let bin;
3447
+ try {
3448
+ bin = await this.deps.resolveFfmpeg();
3449
+ } catch (err) {
3450
+ this.deps.logger.error("still: refused — no pinned ffmpeg on this node (D540)", {
3451
+ tags: { deviceId },
3452
+ meta: {
3453
+ epochMs,
3454
+ error: err instanceof Error ? err.message : String(err)
3455
+ }
3456
+ });
3457
+ return null;
3458
+ }
3459
+ const jpeg = await this.decodeFrame(bin, abs, source.offsetSec);
3447
3460
  if (jpeg === null || jpeg.byteLength === 0) return null;
3448
3461
  return {
3449
3462
  jpeg,
@@ -3454,7 +3467,7 @@ var StillFrameService = class {
3454
3467
  }
3455
3468
  }
3456
3469
  /** Run ONE ffmpeg input-seek + single-frame decode; JPEG bytes on stdout. */
3457
- decodeFrame(absPath, offsetSec) {
3470
+ decodeFrame(bin, absPath, offsetSec) {
3458
3471
  const args = [
3459
3472
  "-hide_banner",
3460
3473
  "-loglevel",
@@ -3470,7 +3483,6 @@ var StillFrameService = class {
3470
3483
  "pipe:1"
3471
3484
  ];
3472
3485
  const spawn$1 = this.deps.spawn ?? spawn;
3473
- const bin = this.deps.ffmpegPath ?? "ffmpeg";
3474
3486
  return new Promise((resolve) => {
3475
3487
  let child;
3476
3488
  try {
@@ -7541,15 +7553,32 @@ var ExportEngine = class {
7541
7553
  } catch {}
7542
7554
  }
7543
7555
  }
7544
- runFfmpeg(id, deviceId, args, progress) {
7556
+ async runFfmpeg(id, deviceId, args, progress) {
7557
+ let ffmpegPath;
7558
+ try {
7559
+ ffmpegPath = await this.deps.resolveFfmpeg();
7560
+ } catch (err) {
7561
+ const reason = `no pinned ffmpeg on this node (D540): ${err instanceof Error ? err.message : String(err)}`;
7562
+ this.deps.logger.error("export render refused — no pinned ffmpeg on this node (D540)", {
7563
+ tags: { deviceId },
7564
+ meta: {
7565
+ exportId: id,
7566
+ error: reason
7567
+ }
7568
+ });
7569
+ return {
7570
+ code: null,
7571
+ stderrTail: [reason]
7572
+ };
7573
+ }
7545
7574
  this.deps.logger.debug("export ffmpeg argv", { meta: {
7546
7575
  exportId: id,
7547
7576
  args
7548
7577
  } });
7549
7578
  const stallMs = this.deps.stallMs ?? 9e4;
7550
7579
  const faststartStallMs = this.deps.faststartStallMs ?? 9e5;
7551
- return new Promise((resolve) => {
7552
- const proc = this.deps.spawn(this.deps.ffmpegPath ?? "ffmpeg", args);
7580
+ return await new Promise((resolve) => {
7581
+ const proc = this.deps.spawn(ffmpegPath, args);
7553
7582
  this.active.set(id, proc);
7554
7583
  const stderrTail = [];
7555
7584
  let stallTimer = null;
@@ -9451,6 +9480,17 @@ async function renderFootage(renderDeps, input, ext, buildArgs) {
9451
9480
  const toMs = input.aroundMs + input.postRollSec * 1e3;
9452
9481
  if (!Number.isFinite(fromMs) || !Number.isFinite(toMs) || toMs <= fromMs) throw new Error(`invalid ${ext} window [${fromMs}, ${toMs}) for device ${input.deviceId} — aroundMs=${input.aroundMs} preRollSec=${input.preRollSec} postRollSec=${input.postRollSec}`);
9453
9482
  const renderId = `${ext}-${now()}-${Math.floor(Math.random() * 1e6)}`;
9483
+ let ffmpegPath;
9484
+ try {
9485
+ ffmpegPath = await renderDeps.resolveFfmpeg();
9486
+ } catch (err) {
9487
+ const reason = err instanceof Error ? err.message : String(err);
9488
+ renderDeps.deps.logger.error(`${ext} render refused — no pinned ffmpeg on this node (D540)`, {
9489
+ tags: { deviceId: input.deviceId },
9490
+ meta: { error: reason }
9491
+ });
9492
+ throw new Error(`${ext} render refused for device ${input.deviceId}: no pinned ffmpeg — ${reason}`, { cause: err });
9493
+ }
9454
9494
  const availableEnd = latestFinalizedEnd(renderDeps, input.deviceId);
9455
9495
  if (availableEnd === null || availableEnd < toMs) throw new Error(`${FOOTAGE_NOT_READY}: [${fromMs}, ${toMs}) for device ${input.deviceId} — finalized footage ends at ${availableEnd ?? "never"}`);
9456
9496
  const window = {
@@ -9469,7 +9509,7 @@ async function renderFootage(renderDeps, input, ext, buildArgs) {
9469
9509
  if (playlist === null) throw new Error(`no footage covers [${window.fromMs}, ${window.toMs}) for device ${input.deviceId}`);
9470
9510
  const outPath = path.join(path.dirname(playlist.path), `${renderId}.${ext}`);
9471
9511
  try {
9472
- await runFfmpeg(renderDeps, buildArgs(playlist.path, outPath), ext);
9512
+ await runFfmpeg(renderDeps, ffmpegPath, buildArgs(playlist.path, outPath), ext);
9473
9513
  const bytes = await promises.readFile(outPath);
9474
9514
  if (bytes.byteLength === 0) throw new Error(`${ext} render produced an empty file`);
9475
9515
  return {
@@ -9506,9 +9546,9 @@ function latestFinalizedEnd(renderDeps, deviceId) {
9506
9546
  * throws ESRCH, which is swallowed, so the cost of being wrong is nothing while
9507
9547
  * the cost of NOT killing is a zombie per render.
9508
9548
  */
9509
- function runFfmpeg(renderDeps, args, ext) {
9549
+ function runFfmpeg(renderDeps, ffmpegPath, args, ext) {
9510
9550
  return new Promise((resolve, reject) => {
9511
- const proc = renderDeps.spawn(renderDeps.ffmpegPath ?? "ffmpeg", args);
9551
+ const proc = renderDeps.spawn(ffmpegPath, args);
9512
9552
  let done = false;
9513
9553
  let timer = null;
9514
9554
  const settle = (err) => {
@@ -15740,7 +15780,7 @@ var SegmentWriter = class {
15740
15780
  start() {
15741
15781
  if (this.stopped || this.proc) return;
15742
15782
  const args = buildPassthroughArgs(this.cfg);
15743
- const proc = this.deps.spawn(this.deps.ffmpegPath ?? "ffmpeg", args);
15783
+ const proc = this.deps.spawn(this.deps.ffmpegPath, args);
15744
15784
  this.proc = proc;
15745
15785
  this.exitWait = new Promise((resolve) => {
15746
15786
  this.resolveExit = resolve;
@@ -17487,6 +17527,19 @@ var RecordingController = class {
17487
17527
  */
17488
17528
  async attachProfile(deviceId, profile, segmentSeconds, audio) {
17489
17529
  const attachProfileStartMs = this.now();
17530
+ let ffmpegPath;
17531
+ try {
17532
+ ffmpegPath = await this.deps.resolveFfmpeg();
17533
+ } catch (err) {
17534
+ this.deps.logger.error("recorder: writer refused — no pinned ffmpeg on this node (D540)", {
17535
+ tags: { deviceId },
17536
+ meta: {
17537
+ profile,
17538
+ error: errMsg$1(err)
17539
+ }
17540
+ });
17541
+ throw new Error(`recorder: no pinned ffmpeg for device ${deviceId}/${profile}`);
17542
+ }
17490
17543
  const placement = await this.resolvePlacement(deviceId, profile);
17491
17544
  const placementDoneMs = this.now();
17492
17545
  const outDir = path.join(placement.root, STAGING_DIR_NAME, String(deviceId), profile);
@@ -17522,6 +17575,7 @@ var RecordingController = class {
17522
17575
  consumerTag
17523
17576
  }, {
17524
17577
  spawn: this.deps.spawn,
17578
+ ffmpegPath,
17525
17579
  logger: deviceLog,
17526
17580
  deviceId,
17527
17581
  claimCost: (pid) => this.deps.costRegistry?.claim({
@@ -20815,6 +20869,14 @@ var RecorderV2Addon = class extends BaseAddon {
20815
20869
  * because those children were this process's children.
20816
20870
  */
20817
20871
  costRegistry = new ChildCostRegistry();
20872
+ /**
20873
+ * This node's PINNED ffmpeg (D540), asked once and shared by every child the
20874
+ * recorder spawns: segment writers, exports, gif/clip renders and stills.
20875
+ * Each of those used to take an optional path that no one passed, and fell
20876
+ * back to a bare `'ffmpeg'` — the HOST's binary. It rejects rather than
20877
+ * naming PATH; each site logs its refusal per camera.
20878
+ */
20879
+ pinnedFfmpeg = memoizePinnedFfmpeg(() => this.ctx.deps.ensureFfmpeg());
20818
20880
  /** True when THIS node is the designated recording node (`recordingNodeId`).
20819
20881
  * Every other node stays fully inert — registers no providers, serves no
20820
20882
  * playback, runs no controller. Resolved in `onInitialize`. */
@@ -21208,6 +21270,7 @@ var RecorderV2Addon = class extends BaseAddon {
21208
21270
  now: () => Date.now()
21209
21271
  });
21210
21272
  this.controller = new RecordingController({
21273
+ resolveFfmpeg: this.pinnedFfmpeg,
21211
21274
  ...this.stagingGuard !== null ? { stagingGuard: this.stagingGuard } : {},
21212
21275
  recoverStagingDirectory: (input) => this.recoverReapedDirectory(input),
21213
21276
  heapProbe: this.heapProbe,
@@ -21355,12 +21418,14 @@ var RecorderV2Addon = class extends BaseAddon {
21355
21418
  gifRender: (input) => renderGif({
21356
21419
  deps: recordingDeps,
21357
21420
  exportsDir: this.exportsDirOrThrow(),
21358
- spawn
21421
+ spawn,
21422
+ resolveFfmpeg: this.pinnedFfmpeg
21359
21423
  }, input),
21360
21424
  clipRender: (input) => renderClip({
21361
21425
  deps: recordingDeps,
21362
21426
  exportsDir: this.exportsDirOrThrow(),
21363
- spawn
21427
+ spawn,
21428
+ resolveFfmpeg: this.pinnedFfmpeg
21364
21429
  }, input),
21365
21430
  locations: () => this.resolvedLocations,
21366
21431
  refreshLocations: () => this.refreshLocations(),
@@ -21489,6 +21554,7 @@ var RecorderV2Addon = class extends BaseAddon {
21489
21554
  };
21490
21555
  this.exportEngine = new ExportEngine({
21491
21556
  spawn,
21557
+ resolveFfmpeg: this.pinnedFfmpeg,
21492
21558
  logger: this.ctx.logger.child("export"),
21493
21559
  state: exportsState,
21494
21560
  resolvePlaylist: async (rec) => {
@@ -21641,7 +21707,8 @@ var RecorderV2Addon = class extends BaseAddon {
21641
21707
  const stillService = new StillFrameService({
21642
21708
  lowSegmentsAt: (deviceId, epochMs) => this.index.segmentsOverlapping(deviceId, "low", epochMs, epochMs + 1),
21643
21709
  resolveAbsPath: (row) => this.resolveSegmentAbsPath(row),
21644
- logger: this.ctx.logger.child("Still")
21710
+ logger: this.ctx.logger.child("Still"),
21711
+ resolveFfmpeg: this.pinnedFfmpeg
21645
21712
  });
21646
21713
  const served = await this.ctx.dataPlane?.serve({
21647
21714
  prefix: STILL_PREFIX,
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-emK7D4bc.js");
2
- const require_dist = require("./dist-CyJWk-_f.js");
2
+ const require_dist = require("./dist-CdRsXAov.js");
3
3
  let node_child_process = require("node:child_process");
4
4
  //#region src/stream-broker/recorded/avcc-annexb.ts
5
5
  /**
@@ -7185,10 +7185,10 @@ var SEGMENT_DEMUX_FFMPEG_ARGS = [
7185
7185
  * of the bytes. Throws on a non-H.264/H.265 stream or a failed conversion.
7186
7186
  *
7187
7187
  * @param bytes - The full `.m4s` fragment (one moov + one IDR-led GOP).
7188
- * @param ffmpegPath - ffmpeg binary path (defaults to PATH `ffmpeg`).
7188
+ * @param ffmpegPath - The node's pinned ffmpeg (D540). Required: no PATH default.
7189
7189
  * @returns Access units in source presentation order, each tagged with its codec.
7190
7190
  */
7191
- async function demuxSegmentToAnnexB(bytes, ffmpegPath = "ffmpeg") {
7191
+ async function demuxSegmentToAnnexB(bytes, ffmpegPath) {
7192
7192
  const { codec, accessUnits } = demuxMpegTsToAnnexB(await runFfmpegOnce({
7193
7193
  ffmpegPath,
7194
7194
  args: SEGMENT_DEMUX_FFMPEG_ARGS,
@@ -7201,6 +7201,17 @@ async function demuxSegmentToAnnexB(bytes, ffmpegPath = "ffmpeg") {
7201
7201
  codec
7202
7202
  }));
7203
7203
  }
7204
+ /**
7205
+ * The ffmpeg fallback was needed and the node has no pinned binary to run it
7206
+ * with. Thrown rather than spawning a PATH `ffmpeg` (D540); the session that
7207
+ * asked names it once, per camera.
7208
+ */
7209
+ var FfmpegFallbackRefusedError = class extends Error {
7210
+ constructor(what) {
7211
+ super(`${what}: ffmpeg fallback refused — no pinned ffmpeg on this node (D540)`);
7212
+ this.name = "FfmpegFallbackRefusedError";
7213
+ }
7214
+ };
7204
7215
  //#endregion
7205
7216
  //#region src/stream-broker/recorded/segment-demux-js.ts
7206
7217
  /**
@@ -7265,7 +7276,7 @@ async function demuxSegmentFullJs(bytes) {
7265
7276
  * unit or throws on malformed bytes. Preserves the exact {@link AccessUnit}
7266
7277
  * contract either way, so it is a drop-in for `RecordedFeederDeps.demux`.
7267
7278
  */
7268
- async function demuxSegmentPreferJs(bytes, opts = {}) {
7279
+ async function demuxSegmentPreferJs(bytes, opts) {
7269
7280
  try {
7270
7281
  const js = await (opts.jsParse ?? demuxSegmentFullJs)(bytes);
7271
7282
  if (js && js.length > 0) {
@@ -7282,9 +7293,16 @@ async function demuxSegmentPreferJs(bytes, opts = {}) {
7282
7293
  reason: `mp4box parse threw: ${String(err)}`
7283
7294
  });
7284
7295
  }
7296
+ if (opts.ffmpegPath === null) throw new FfmpegFallbackRefusedError("recorded video demux");
7285
7297
  return demuxSegmentToAnnexB(bytes, opts.ffmpegPath);
7286
7298
  }
7287
7299
  //#endregion
7300
+ Object.defineProperty(exports, "FfmpegFallbackRefusedError", {
7301
+ enumerable: true,
7302
+ get: function() {
7303
+ return FfmpegFallbackRefusedError;
7304
+ }
7305
+ });
7288
7306
  Object.defineProperty(exports, "avccSampleToAnnexB", {
7289
7307
  enumerable: true,
7290
7308
  get: function() {
@@ -1,5 +1,5 @@
1
1
  import { r as __toESM, t as __commonJSMin } from "./chunk-DnnnRqeS.mjs";
2
- import { un as errMsg } from "./dist-DdVMFlJ6.mjs";
2
+ import { dn as errMsg } from "./dist-jy-7xoW5.mjs";
3
3
  import { spawn } from "node:child_process";
4
4
  //#region src/stream-broker/recorded/avcc-annexb.ts
5
5
  /**
@@ -7185,10 +7185,10 @@ var SEGMENT_DEMUX_FFMPEG_ARGS = [
7185
7185
  * of the bytes. Throws on a non-H.264/H.265 stream or a failed conversion.
7186
7186
  *
7187
7187
  * @param bytes - The full `.m4s` fragment (one moov + one IDR-led GOP).
7188
- * @param ffmpegPath - ffmpeg binary path (defaults to PATH `ffmpeg`).
7188
+ * @param ffmpegPath - The node's pinned ffmpeg (D540). Required: no PATH default.
7189
7189
  * @returns Access units in source presentation order, each tagged with its codec.
7190
7190
  */
7191
- async function demuxSegmentToAnnexB(bytes, ffmpegPath = "ffmpeg") {
7191
+ async function demuxSegmentToAnnexB(bytes, ffmpegPath) {
7192
7192
  const { codec, accessUnits } = demuxMpegTsToAnnexB(await runFfmpegOnce({
7193
7193
  ffmpegPath,
7194
7194
  args: SEGMENT_DEMUX_FFMPEG_ARGS,
@@ -7201,6 +7201,17 @@ async function demuxSegmentToAnnexB(bytes, ffmpegPath = "ffmpeg") {
7201
7201
  codec
7202
7202
  }));
7203
7203
  }
7204
+ /**
7205
+ * The ffmpeg fallback was needed and the node has no pinned binary to run it
7206
+ * with. Thrown rather than spawning a PATH `ffmpeg` (D540); the session that
7207
+ * asked names it once, per camera.
7208
+ */
7209
+ var FfmpegFallbackRefusedError = class extends Error {
7210
+ constructor(what) {
7211
+ super(`${what}: ffmpeg fallback refused — no pinned ffmpeg on this node (D540)`);
7212
+ this.name = "FfmpegFallbackRefusedError";
7213
+ }
7214
+ };
7204
7215
  //#endregion
7205
7216
  //#region src/stream-broker/recorded/segment-demux-js.ts
7206
7217
  /**
@@ -7265,7 +7276,7 @@ async function demuxSegmentFullJs(bytes) {
7265
7276
  * unit or throws on malformed bytes. Preserves the exact {@link AccessUnit}
7266
7277
  * contract either way, so it is a drop-in for `RecordedFeederDeps.demux`.
7267
7278
  */
7268
- async function demuxSegmentPreferJs(bytes, opts = {}) {
7279
+ async function demuxSegmentPreferJs(bytes, opts) {
7269
7280
  try {
7270
7281
  const js = await (opts.jsParse ?? demuxSegmentFullJs)(bytes);
7271
7282
  if (js && js.length > 0) {
@@ -7282,7 +7293,8 @@ async function demuxSegmentPreferJs(bytes, opts = {}) {
7282
7293
  reason: `mp4box parse threw: ${String(err)}`
7283
7294
  });
7284
7295
  }
7296
+ if (opts.ffmpegPath === null) throw new FfmpegFallbackRefusedError("recorded video demux");
7285
7297
  return demuxSegmentToAnnexB(bytes, opts.ffmpegPath);
7286
7298
  }
7287
7299
  //#endregion
7288
- export { avccSampleToAnnexB as a, require_mp4box_all as i, demuxSegmentPreferJs as n, buildKeyframeAnnexB as o, runFfmpegOnce as r, parseNalLengthSize as s, demuxSegmentFullJs as t };
7300
+ export { require_mp4box_all as a, parseNalLengthSize as c, runFfmpegOnce as i, demuxSegmentPreferJs as n, avccSampleToAnnexB as o, FfmpegFallbackRefusedError as r, buildKeyframeAnnexB as s, demuxSegmentFullJs as t };
@@ -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-CdKbuxqG.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-DPHv0v3Q.mjs";
4
+ import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DEPTgXS_.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/mergeClasses.js
7
7
  var T = (...e) => e.filter((e, t, n) => !!e && e.trim() !== "" && n.indexOf(e) === t).join(" ").trim(), E = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), D = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), O = (e) => {
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.274",
21
+ version: "1.2.275",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_stream_broker_widgets",