@camstack/addon-pipeline 1.2.115 → 1.2.119

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 (32) hide show
  1. package/dist/{addon-utils-C0ht2KPO.js → addon-utils-RAHF2hmW.js} +1 -1
  2. package/dist/audio-analyzer/index.js +3 -3
  3. package/dist/audio-analyzer/index.mjs +2 -2
  4. package/dist/detection-pipeline/index.js +171 -167
  5. package/dist/detection-pipeline/index.mjs +169 -165
  6. package/dist/{dist-BqIbYW1A.js → dist-DIsM42Ex.js} +121 -11
  7. package/dist/{dist-DGQkkWc6.mjs → dist-Db664g2O.mjs} +116 -12
  8. package/dist/{event-loop-stall-monitor-ylMGpz48.js → event-loop-stall-monitor-CSpdFZb6.js} +1 -1
  9. package/dist/{event-loop-stall-monitor-D5jK5v4Z.mjs → event-loop-stall-monitor-DY6339bX.mjs} +1 -1
  10. package/dist/{lazy-sharp-cOsy7l_P.js → lazy-sharp-LSlEXQe_.js} +1 -1
  11. package/dist/motion-wasm/index.js +2 -2
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/pipeline-runner/index.js +12 -14
  14. package/dist/pipeline-runner/index.mjs +11 -13
  15. package/dist/{process-memory-BtT7WfbJ.mjs → process-memory-CPm5TMBl.mjs} +1 -1
  16. package/dist/{process-memory-45juuHpN.js → process-memory-DpSJQF-p.js} +1 -1
  17. package/dist/recorder/index.js +261 -32
  18. package/dist/recorder/index.mjs +260 -31
  19. package/dist/session-decode/decode-worker-child.js +34 -26
  20. package/dist/session-decode/decode-worker-child.mjs +33 -25
  21. package/dist/stream-broker/_stub.js +2 -2
  22. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C_SFFNGT.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CstvrzmK.mjs} +2 -2
  23. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BiNja8Hw.mjs +26 -0
  24. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BAMyzCWw.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-D-NFPV_g.mjs} +1 -1
  25. package/dist/stream-broker/{hostInit-CG28NvlV.mjs → hostInit-kHeLqEod.mjs} +2 -2
  26. package/dist/stream-broker/index.js +8 -6
  27. package/dist/stream-broker/index.mjs +8 -6
  28. package/dist/stream-broker/remoteEntry.js +1 -1
  29. package/dist/{worker-protocol-CIrqGbpG.js → worker-protocol-C1tr8WP3.js} +1 -1
  30. package/dist/{worker-protocol-DyocTIx_.mjs → worker-protocol-_zMPqSas.mjs} +1 -1
  31. package/package.json +1 -1
  32. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B1LDFHY9.mjs +0 -26
@@ -1,4 +1,4 @@
1
- import { At as nodePin, B as deriveRecordingMode, Ct as DeviceFeature, Et as hydrateSchema, Ht as object, Kt as EventCategory, M as batteryCapability, Nt as selectAssignedProfileSlots, R as deriveBatteryPresence, T as RecordingConfigSchema, Ut as record, Vt as number, Wt as string, at as recordingExportCapability, b as OpsLogEntrySchema, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, dt as storageEvictableCapability, g as ExportRecordSchema, ht as errMsg, i as BatteryStatusSchema, it as recordingCapability, lt as resolveRecordingProfiles, m as EVENT_PAD_MS, w as RECORDING_EXPORT_MAX_READ_BYTES, wt as DeviceType, xt as BaseAddon } from "../dist-DGQkkWc6.mjs";
1
+ import { B as deriveRecordingMode, Dt as hydrateSchema, Gt as string, Ht as number, M as batteryCapability, Pt as selectAssignedProfileSlots, R as deriveBatteryPresence, St as BaseAddon, T as RecordingConfigSchema, Tt as DeviceType, Ut as object, Wt as record, at as recordingCapability, b as OpsLogEntrySchema, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, ft as storageEvictableCapability, g as ExportRecordSchema, gt as errMsg, i as BatteryStatusSchema, jt as nodePin, m as EVENT_PAD_MS, ot as recordingExportCapability, qt as EventCategory, ut as resolveRecordingProfiles, w as RECORDING_EXPORT_MAX_READ_BYTES, wt as DeviceFeature } from "../dist-Db664g2O.mjs";
2
2
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
3
3
  import { r as withRecordingIntent } from "../restream-intent-B4BXZra7.mjs";
4
4
  import { n as createFileDataPlaneHandler, s as parseRangeHeader, t as contentTypeFor } from "../addon-utils-CgBCF-mE.mjs";
@@ -2983,11 +2983,23 @@ function concatFileDirective(uri) {
2983
2983
  function buildExportConcatList(segments) {
2984
2984
  return `ffconcat version 1.0\n${segments.map((s) => `${concatFileDirective(s.uri)}\nduration ${sec(s.durMs / 1e3)}`).map((l) => `${l}\n`).join("")}`;
2985
2985
  }
2986
+ /**
2987
+ * Concat list for already-finished MP4s. A `duration` line here would
2988
+ * TRUNCATE each hour to whatever dummy we wrote (the stream clock is
2989
+ * already inside the file).
2990
+ */
2991
+ function buildAssembleConcatList(uris) {
2992
+ return `ffconcat version 1.0\n${uris.map((u) => `${concatFileDirective(u)}\n`).join("")}`;
2993
+ }
2986
2994
  /** Pass 1 of a timelapse: remux the ffconcat list into one continuous file.
2987
2995
  * `-safe 0` because the list's paths are absolute — which is exactly what the
2988
2996
  * per-export source list contains (`export-source-playlist.ts`). */
2989
2997
  function buildConcatPassArgs(input) {
2990
2998
  return [
2999
+ "-fflags",
3000
+ "+discardcorrupt+genpts",
3001
+ "-err_detect",
3002
+ "ignore_err",
2991
3003
  "-f",
2992
3004
  "concat",
2993
3005
  "-safe",
@@ -3002,6 +3014,21 @@ function buildConcatPassArgs(input) {
3002
3014
  input.outPath
3003
3015
  ];
3004
3016
  }
3017
+ /** Final stitch of the surviving per-hour MP4s. Already selected + encoded. */
3018
+ function buildAssembleArgs(input) {
3019
+ return [
3020
+ "-f",
3021
+ "concat",
3022
+ "-safe",
3023
+ "0",
3024
+ "-i",
3025
+ input.inputConcatList,
3026
+ "-c",
3027
+ "copy",
3028
+ ...FASTSTART,
3029
+ input.outPath
3030
+ ];
3031
+ }
3005
3032
  /** Build the ffmpeg argv for one export render. */
3006
3033
  function buildExportArgs(input) {
3007
3034
  const { inputPlaylist, outPath, options } = input;
@@ -3047,6 +3074,65 @@ function buildExportArgs(input) {
3047
3074
  ];
3048
3075
  }
3049
3076
  //#endregion
3077
+ //#region src/recorder/addon/export-timelapse-chunks.ts
3078
+ /** One hour of concatenated footage — not one hour of wall clock. */
3079
+ var TIMELAPSE_CHUNK_MS = 36e5;
3080
+ /**
3081
+ * Split a concat list into hour-sized chunks. A single file longer than the
3082
+ * chunk stays whole — it is already one open, and cutting it would need a
3083
+ * second demux we do not have.
3084
+ */
3085
+ function splitTimelapseChunks(entries, chunkMs = TIMELAPSE_CHUNK_MS) {
3086
+ if (entries.length === 0) return [];
3087
+ const chunks = [];
3088
+ let current = [];
3089
+ let currentDur = 0;
3090
+ let streamStartMs = 0;
3091
+ for (const entry of entries) {
3092
+ if (current.length > 0 && currentDur + entry.durMs > chunkMs) {
3093
+ chunks.push({
3094
+ entries: current,
3095
+ streamStartMs
3096
+ });
3097
+ streamStartMs += currentDur;
3098
+ current = [];
3099
+ currentDur = 0;
3100
+ }
3101
+ current.push(entry);
3102
+ currentDur += entry.durMs;
3103
+ }
3104
+ if (current.length > 0) chunks.push({
3105
+ entries: current,
3106
+ streamStartMs
3107
+ });
3108
+ return chunks;
3109
+ }
3110
+ /**
3111
+ * Move stream-time dense ranges onto a chunk's own `t=0`. Ranges that miss
3112
+ * the chunk disappear; a range that straddles the edge is clipped. Empty →
3113
+ * `undefined` so the select stays the single-rate form.
3114
+ */
3115
+ function denseRangesForChunk(dense, chunkStreamStartMs, chunkDurMs) {
3116
+ if (dense === void 0 || dense.ranges.length === 0) return void 0;
3117
+ const startSec = chunkStreamStartMs / 1e3;
3118
+ const endSec = (chunkStreamStartMs + chunkDurMs) / 1e3;
3119
+ const ranges = [];
3120
+ for (const r of dense.ranges) {
3121
+ const from = Math.max(r.fromSec, startSec);
3122
+ const to = Math.min(r.toSec, endSec);
3123
+ if (to <= from) continue;
3124
+ ranges.push({
3125
+ fromSec: from - startSec,
3126
+ toSec: to - startSec
3127
+ });
3128
+ }
3129
+ if (ranges.length === 0) return void 0;
3130
+ return {
3131
+ everyMs: dense.everyMs,
3132
+ ranges
3133
+ };
3134
+ }
3135
+ //#endregion
3050
3136
  //#region src/recorder/addon/export-store.ts
3051
3137
  /**
3052
3138
  * Recording-export persistence for the recorder addon.
@@ -3310,15 +3396,15 @@ function expectedOutSeconds(rec) {
3310
3396
  var ExportEngine = class {
3311
3397
  deps;
3312
3398
  queue = [];
3313
- activeId = null;
3314
- activeProc = null;
3399
+ /** In-flight jobs → their current ffmpeg (null between the two timelapse passes). */
3400
+ active = /* @__PURE__ */ new Map();
3315
3401
  cancelled = /* @__PURE__ */ new Set();
3316
3402
  constructor(deps) {
3317
3403
  this.deps = deps;
3318
3404
  }
3319
3405
  /** Queue a persisted `queued` record for rendering. */
3320
3406
  enqueue(id) {
3321
- if (this.queue.includes(id) || this.activeId === id) return;
3407
+ if (this.queue.includes(id) || this.active.has(id)) return;
3322
3408
  this.queue.push(id);
3323
3409
  this.pump();
3324
3410
  }
@@ -3327,29 +3413,32 @@ var ExportEngine = class {
3327
3413
  this.cancelled.add(id);
3328
3414
  const idx = this.queue.indexOf(id);
3329
3415
  if (idx >= 0) this.queue.splice(idx, 1);
3330
- if (this.activeId === id && this.activeProc) try {
3331
- this.activeProc.kill("SIGKILL");
3416
+ const proc = this.active.get(id);
3417
+ if (proc) try {
3418
+ proc.kill("SIGKILL");
3332
3419
  } catch {}
3333
3420
  else if (idx >= 0) await this.fail(id, "cancelled");
3334
3421
  }
3335
- async pump() {
3336
- if (this.activeId) return;
3337
- const id = this.queue.shift();
3338
- if (!id) return;
3339
- if (this.cancelled.has(id)) {
3340
- this.cancelled.delete(id);
3341
- await this.fail(id, "cancelled");
3342
- this.pump();
3343
- return;
3422
+ pump() {
3423
+ while (this.active.size < 2 && this.queue.length > 0) {
3424
+ const id = this.queue.shift();
3425
+ if (!id) return;
3426
+ if (this.cancelled.has(id)) {
3427
+ this.cancelled.delete(id);
3428
+ this.fail(id, "cancelled");
3429
+ continue;
3430
+ }
3431
+ this.active.set(id, null);
3432
+ this.runOne(id);
3344
3433
  }
3345
- this.activeId = id;
3434
+ }
3435
+ async runOne(id) {
3346
3436
  try {
3347
3437
  await this.render(id);
3348
3438
  } catch (err) {
3349
3439
  await this.fail(id, err instanceof Error ? err.message : String(err));
3350
3440
  } finally {
3351
- this.activeId = null;
3352
- this.activeProc = null;
3441
+ this.active.delete(id);
3353
3442
  this.pump();
3354
3443
  }
3355
3444
  }
@@ -3367,7 +3456,7 @@ var ExportEngine = class {
3367
3456
  try {
3368
3457
  await this.spawnRender(id, rec, playlist, this.streamTimeOptions(rec, playlist.segments));
3369
3458
  } finally {
3370
- if (this.deps.cleanupPlaylist) for (const file of [playlist.path, playlist.concatPath]) try {
3459
+ if (this.deps.cleanupPlaylist) for (const file of [playlist.path, playlist.concatPath].filter((p) => typeof p === "string" && p.length > 0)) try {
3371
3460
  await this.deps.cleanupPlaylist(file);
3372
3461
  } catch (err) {
3373
3462
  this.deps.logger.warn("export: source playlist cleanup failed", {
@@ -3430,7 +3519,7 @@ var ExportEngine = class {
3430
3519
  }
3431
3520
  });
3432
3521
  if (options.timelapse) {
3433
- await this.spawnTimelapse(id, rec, playlist.concatPath, outPath, options);
3522
+ await this.spawnTimelapse(id, rec, playlist, outPath, options);
3434
3523
  return;
3435
3524
  }
3436
3525
  const args = buildExportArgs({
@@ -3454,8 +3543,118 @@ var ExportEngine = class {
3454
3543
  * timestamp line runs on the SAME clock the dense map summed — see
3455
3544
  * `export-ffmpeg-args.ts` for what the HLS demuxer's content-stitching cost.
3456
3545
  */
3457
- async spawnTimelapse(id, rec, concatList, outPath, options) {
3546
+ async spawnTimelapse(id, rec, playlist, outPath, options) {
3547
+ const chunks = splitTimelapseChunks(playlist.concatEntries ?? []);
3548
+ if (chunks.length <= 1) {
3549
+ const concatList = playlist.concatPath;
3550
+ if (concatList === void 0) {
3551
+ await this.fail(id, "no concat list for timelapse");
3552
+ return;
3553
+ }
3554
+ await this.spawnTimelapsePair(id, rec, concatList, outPath, options, {
3555
+ floor: 0,
3556
+ ceil: 99
3557
+ });
3558
+ return;
3559
+ }
3560
+ if (this.deps.writeTextFile === void 0) {
3561
+ const concatList = playlist.concatPath;
3562
+ if (concatList === void 0) {
3563
+ await this.fail(id, "no concat list for timelapse");
3564
+ return;
3565
+ }
3566
+ await this.spawnTimelapsePair(id, rec, concatList, outPath, options, {
3567
+ floor: 0,
3568
+ ceil: 99
3569
+ });
3570
+ return;
3571
+ }
3572
+ const survivors = [];
3573
+ const sidecars = [];
3574
+ try {
3575
+ for (let i = 0; i < chunks.length; i++) {
3576
+ if (this.cancelled.has(id)) {
3577
+ await this.fail(id, "cancelled");
3578
+ return;
3579
+ }
3580
+ const chunk = chunks[i];
3581
+ const chunkDur = chunk.entries.reduce((s, e) => s + e.durMs, 0);
3582
+ const listPath = `${outPath}.chunk${i}.ffconcat`;
3583
+ const concatPath = `${outPath}.chunk${i}.concat.ts`;
3584
+ const chunkMp4 = `${outPath}.chunk${i}.mp4`;
3585
+ sidecars.push(listPath, concatPath, chunkMp4);
3586
+ await this.deps.writeTextFile(listPath, buildExportConcatList(chunk.entries));
3587
+ const span = 90 / chunks.length;
3588
+ const floor = Math.round(i * span);
3589
+ if (await this.spawnTimelapsePair(id, rec, listPath, chunkMp4, {
3590
+ ...options,
3591
+ timelapse: options.timelapse ? {
3592
+ ...options.timelapse,
3593
+ dense: denseRangesForChunk(options.timelapse.dense, chunk.streamStartMs, chunkDur)
3594
+ } : options.timelapse
3595
+ }, {
3596
+ floor,
3597
+ ceil: floor + Math.round(span * .95),
3598
+ finish: false
3599
+ }) === "ok") survivors.push(chunkMp4);
3600
+ else this.deps.logger.warn("export timelapse hour skipped", {
3601
+ tags: { deviceId: rec.deviceId },
3602
+ meta: {
3603
+ exportId: id,
3604
+ chunk: i,
3605
+ streamStartMs: chunk.streamStartMs
3606
+ }
3607
+ });
3608
+ }
3609
+ if (survivors.length === 0) {
3610
+ await this.fail(id, "every timelapse hour failed");
3611
+ return;
3612
+ }
3613
+ if (survivors.length === 1) {
3614
+ const only = survivors[0];
3615
+ const copyArgs = buildExportArgs({
3616
+ inputPlaylist: only,
3617
+ outPath,
3618
+ options: {
3619
+ includeAudio: false,
3620
+ maxLifeMs: 1,
3621
+ deleteAfterDownload: false,
3622
+ speed: 1
3623
+ }
3624
+ });
3625
+ const copied = await this.runFfmpeg(id, rec.deviceId, copyArgs, {
3626
+ totalSec: expectedOutSeconds(rec),
3627
+ floor: 95,
3628
+ ceil: 99
3629
+ });
3630
+ await this.finish(id, copied.code, outPath, copied.stderrTail);
3631
+ return;
3632
+ }
3633
+ const assembleList = `${outPath}.assemble.ffconcat`;
3634
+ sidecars.push(assembleList);
3635
+ await this.deps.writeTextFile(assembleList, buildAssembleConcatList(survivors));
3636
+ const assembled = await this.runFfmpeg(id, rec.deviceId, buildAssembleArgs({
3637
+ inputConcatList: assembleList,
3638
+ outPath
3639
+ }), {
3640
+ totalSec: expectedOutSeconds(rec),
3641
+ floor: 95,
3642
+ ceil: 99
3643
+ });
3644
+ await this.finish(id, assembled.code, outPath, assembled.stderrTail);
3645
+ } finally {
3646
+ for (const file of sidecars) try {
3647
+ await this.deps.removeFile(file);
3648
+ } catch {}
3649
+ }
3650
+ }
3651
+ /**
3652
+ * Concat remux then select. `finish: false` lets the chunk walker skip a
3653
+ * dead hour without failing the whole night.
3654
+ */
3655
+ async spawnTimelapsePair(id, rec, concatList, outPath, options, progress) {
3458
3656
  const concatPath = concatPassOutPath(outPath);
3657
+ const mid = progress.floor + Math.round((progress.ceil - progress.floor) / 2);
3459
3658
  try {
3460
3659
  const concatArgs = buildConcatPassArgs({
3461
3660
  inputConcatList: concatList,
@@ -3463,12 +3662,12 @@ var ExportEngine = class {
3463
3662
  });
3464
3663
  const concatRan = await this.runFfmpeg(id, rec.deviceId, concatArgs, {
3465
3664
  totalSec: Math.max(.001, (rec.toMs - rec.fromMs) / 1e3),
3466
- floor: 0,
3467
- ceil: 49
3665
+ floor: progress.floor,
3666
+ ceil: mid
3468
3667
  });
3469
3668
  if (this.cancelled.has(id) || concatRan.code !== 0) {
3470
- await this.finish(id, concatRan.code, concatPath, concatRan.stderrTail);
3471
- return;
3669
+ if (progress.finish !== false) await this.finish(id, concatRan.code, concatPath, concatRan.stderrTail);
3670
+ return "fail";
3472
3671
  }
3473
3672
  const selectArgs = buildExportArgs({
3474
3673
  inputPlaylist: concatPath,
@@ -3477,10 +3676,11 @@ var ExportEngine = class {
3477
3676
  });
3478
3677
  const selectRan = await this.runFfmpeg(id, rec.deviceId, selectArgs, {
3479
3678
  totalSec: expectedOutSeconds(rec),
3480
- floor: 50,
3481
- ceil: 99
3679
+ floor: mid,
3680
+ ceil: progress.ceil
3482
3681
  });
3483
- await this.finish(id, selectRan.code, outPath, selectRan.stderrTail);
3682
+ if (progress.finish !== false) await this.finish(id, selectRan.code, outPath, selectRan.stderrTail);
3683
+ return selectRan.code === 0 ? "ok" : "fail";
3484
3684
  } finally {
3485
3685
  try {
3486
3686
  await this.deps.removeFile(concatPath);
@@ -3492,10 +3692,28 @@ var ExportEngine = class {
3492
3692
  exportId: id,
3493
3693
  args
3494
3694
  } });
3695
+ const stallMs = this.deps.stallMs ?? 9e4;
3495
3696
  return new Promise((resolve) => {
3496
3697
  const proc = this.deps.spawn(this.deps.ffmpegPath ?? "ffmpeg", args);
3497
- this.activeProc = proc;
3698
+ this.active.set(id, proc);
3498
3699
  const stderrTail = [];
3700
+ let stallTimer = null;
3701
+ const clearStall = () => {
3702
+ if (stallTimer !== null) {
3703
+ clearTimeout(stallTimer);
3704
+ stallTimer = null;
3705
+ }
3706
+ };
3707
+ const armStall = () => {
3708
+ clearStall();
3709
+ stallTimer = setTimeout(() => {
3710
+ stderrTail.push(`ffmpeg stalled: no progress for ${stallMs}ms`);
3711
+ try {
3712
+ proc.kill("SIGKILL");
3713
+ } catch {}
3714
+ }, stallMs);
3715
+ };
3716
+ armStall();
3499
3717
  proc.stderr?.on("data", (chunk) => {
3500
3718
  const text = chunk instanceof Buffer ? chunk.toString("utf8") : String(chunk);
3501
3719
  for (const line of text.split("\n")) {
@@ -3506,6 +3724,7 @@ var ExportEngine = class {
3506
3724
  }
3507
3725
  const m = TIME_RE.exec(line);
3508
3726
  if (m) {
3727
+ armStall();
3509
3728
  const sec = Number(m[1]) * 3600 + Number(m[2]) * 60 + Number(m[3]);
3510
3729
  const span = progress.ceil - progress.floor;
3511
3730
  const pct = Math.min(progress.ceil, progress.floor + Math.round(sec / progress.totalSec * span));
@@ -3518,14 +3737,16 @@ var ExportEngine = class {
3518
3737
  }
3519
3738
  });
3520
3739
  proc.on("exit", (code) => {
3521
- this.activeProc = null;
3740
+ clearStall();
3741
+ this.active.set(id, null);
3522
3742
  resolve({
3523
3743
  code: typeof code === "number" ? code : null,
3524
3744
  stderrTail
3525
3745
  });
3526
3746
  });
3527
3747
  proc.on("error", (raw) => {
3528
- this.activeProc = null;
3748
+ clearStall();
3749
+ this.active.set(id, null);
3529
3750
  const failure = readSpawnFailure(raw);
3530
3751
  this.deps.logger.error("export ffmpeg could not be spawned", {
3531
3752
  tags: { deviceId },
@@ -4810,6 +5031,10 @@ async function writeExportSourcePlaylist(input) {
4810
5031
  segments: segments.map((s) => ({
4811
5032
  startMs: s.startMs,
4812
5033
  durMs: s.durMs
5034
+ })),
5035
+ concatEntries: segments.map((s) => ({
5036
+ uri: s.uri,
5037
+ durMs: s.durMs
4813
5038
  }))
4814
5039
  };
4815
5040
  }
@@ -10763,6 +10988,10 @@ var RecorderV2Addon = class extends BaseAddon {
10763
10988
  });
10764
10989
  },
10765
10990
  cleanupPlaylist: removeExportFile,
10991
+ writeTextFile: async (p, contents) => {
10992
+ await promises.mkdir(path.dirname(p), { recursive: true });
10993
+ await promises.writeFile(p, contents, "utf8");
10994
+ },
10766
10995
  outPath: exportOutPath,
10767
10996
  statBytes: async (p) => (await promises.stat(p)).size,
10768
10997
  removeFile: removeExportFile,
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_worker_protocol = require("../worker-protocol-CIrqGbpG.js");
3
- const require_lazy_sharp = require("../lazy-sharp-cOsy7l_P.js");
2
+ const require_worker_protocol = require("../worker-protocol-C1tr8WP3.js");
3
+ const require_lazy_sharp = require("../lazy-sharp-LSlEXQe_.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.
@@ -874,6 +874,15 @@ var NativeTileStore = class {
874
874
  };
875
875
  //#endregion
876
876
  //#region src/session-decode/planar-crop-source.ts
877
+ /** Layout a detection `toBuffer` can read, or `null` when it must name the
878
+ * format and refuse. RGB24 is the VAAPI `pinnedRgbCrop` lease; NV12 is the
879
+ * videotoolbox download. The old path only accepted 3-plane YUV420P. */
880
+ function softwareRoiKind(format, C) {
881
+ if (format === C.AV_PIX_FMT_RGB24) return "rgb24";
882
+ if (format === C.AV_PIX_FMT_YUV420P) return "yuv420p";
883
+ if (format === C.AV_PIX_FMT_NV12) return "nv12";
884
+ return null;
885
+ }
877
886
  /** How many planes each layout must present before the offsets mean anything. */
878
887
  var PLANE_COUNT = {
879
888
  yuv420p: 3,
@@ -2600,29 +2609,28 @@ var DecodeWorkerChild = class {
2600
2609
  const dstFmt = format === "gray" ? C.AV_PIX_FMT_GRAY8 : C.AV_PIX_FMT_RGB24;
2601
2610
  const channels = format === "gray" ? 1 : 3;
2602
2611
  this.logColorProvenance(isFullFrameRegion(region, frame.width, frame.height) ? "sw-fullframe" : "sw-crop", this.resolveFrameColor(frame));
2603
- const planes = frame.data;
2604
- const strides = frame.linesize;
2605
- const yPlane = planes?.[0];
2606
- const uPlane = planes?.[1];
2607
- const vPlane = planes?.[2];
2608
- if (!yPlane || !uPlane || !vPlane) throw new Error("decode-worker-child: decoded frame missing planar YUV420P data");
2609
- const yStride = strides[0] ?? 0;
2610
- const cStride = strides[1] ?? 0;
2611
- const ySlice = yPlane.subarray(region.top * yStride + region.left);
2612
- const uSlice = uPlane.subarray(region.top / 2 * cStride + region.left / 2);
2613
- const vSlice = vPlane.subarray(region.top / 2 * cStride + region.left / 2);
2614
- const scaler = this.ensureScaler(nav, C, region.width, region.height, target.width, target.height, dstFmt);
2612
+ const kind = softwareRoiKind(frame.format, C);
2613
+ if (kind === null) throw new Error(`decode-worker-child: decoded frame in unsupported pixel format ${frame.format} (planes=${frame.data?.length ?? 0}, hw=${frame.isHwFrame()})`);
2614
+ if (kind === "rgb24" && format === "rgb") return this.nativeRgbCropToBuffer(nav, C, frame, {
2615
+ region,
2616
+ target
2617
+ });
2618
+ const source = kind === "rgb24" ? (() => {
2619
+ const plane = frame.data?.[0];
2620
+ const stride = frame.linesize[0] ?? 0;
2621
+ if (!plane || stride <= 0) return null;
2622
+ const rgb = resolveRgbCropSource(plane, stride, region);
2623
+ return {
2624
+ planes: [rgb.src],
2625
+ strides: [rgb.stride]
2626
+ };
2627
+ })() : resolvePlanarCropSource(kind, frame.data, frame.linesize, region);
2628
+ if (!source) throw new Error(`decode-worker-child: retained ${kind} frame missing plane data for ${region.width}x${region.height}+${region.left}+${region.top} (planes=${frame.data?.length ?? 0})`);
2629
+ const scaler = this.ensureScaler(nav, C, region.width, region.height, target.width, target.height, dstFmt, kind);
2615
2630
  const dstStride = target.width * channels;
2616
2631
  const dstBuffer = Buffer.allocUnsafe(dstStride * target.height + 64);
2617
- const scaledHeight = scaler.scaleSync([
2618
- ySlice,
2619
- uSlice,
2620
- vSlice
2621
- ], [
2622
- yStride,
2623
- cStride,
2624
- cStride
2625
- ], 0, region.height, [dstBuffer], [dstStride]);
2632
+ const srcPlanes = source.planes.map((plane) => Buffer.from(plane.buffer, plane.byteOffset, plane.byteLength));
2633
+ const scaledHeight = scaler.scaleSync(srcPlanes, [...source.strides], 0, region.height, [dstBuffer], [dstStride]);
2626
2634
  if (scaledHeight < 0) throw new Error(`sws_scale failed: ${scaledHeight}`);
2627
2635
  if (scaledHeight !== target.height) throw new Error(`scaler produced ${scaledHeight} of ${target.height} rows`);
2628
2636
  return dstBuffer.subarray(0, dstStride * target.height);
@@ -2635,15 +2643,15 @@ var DecodeWorkerChild = class {
2635
2643
  * that leaked native libav memory at frame rate. Synchronous + single-thread
2636
2644
  * (Node), so no epoch/race guard is needed (unlike the async shared session).
2637
2645
  */
2638
- ensureScaler(nav, C, srcW, srcH, dstW, dstH, dstFmt) {
2639
- const key = `${srcW}x${srcH}->${dstW}x${dstH}:${dstFmt}`;
2646
+ ensureScaler(nav, C, srcW, srcH, dstW, dstH, dstFmt, srcKind = "yuv420p") {
2647
+ const key = `${srcKind}:${srcW}x${srcH}->${dstW}x${dstH}:${dstFmt}`;
2640
2648
  const cached = this.scaler;
2641
2649
  if (cached && key === this.scalerKey) return cached;
2642
2650
  this.scaler?.[Symbol.dispose]?.();
2643
2651
  this.scaler = null;
2644
2652
  this.scalerKey = "";
2645
2653
  const scaler = new nav.SoftwareScaleContext();
2646
- scaler.getContext(srcW, srcH, C.AV_PIX_FMT_YUV420P, dstW, dstH, dstFmt, C.SWS_FAST_BILINEAR);
2654
+ scaler.getContext(srcW, srcH, navPixelFormatFor(srcKind, C), dstW, dstH, dstFmt, C.SWS_FAST_BILINEAR);
2647
2655
  const initRet = scaler.initContext();
2648
2656
  if (initRet < 0) {
2649
2657
  scaler[Symbol.dispose]?.();
@@ -1,4 +1,4 @@
1
- import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-DyocTIx_.mjs";
1
+ import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-_zMPqSas.mjs";
2
2
  import { n as setSharpWarnSink, r as hostExternalEntryUrls, t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
3
3
  //#region src/session-decode/color-conversion.ts
4
4
  /**
@@ -873,6 +873,15 @@ var NativeTileStore = class {
873
873
  };
874
874
  //#endregion
875
875
  //#region src/session-decode/planar-crop-source.ts
876
+ /** Layout a detection `toBuffer` can read, or `null` when it must name the
877
+ * format and refuse. RGB24 is the VAAPI `pinnedRgbCrop` lease; NV12 is the
878
+ * videotoolbox download. The old path only accepted 3-plane YUV420P. */
879
+ function softwareRoiKind(format, C) {
880
+ if (format === C.AV_PIX_FMT_RGB24) return "rgb24";
881
+ if (format === C.AV_PIX_FMT_YUV420P) return "yuv420p";
882
+ if (format === C.AV_PIX_FMT_NV12) return "nv12";
883
+ return null;
884
+ }
876
885
  /** How many planes each layout must present before the offsets mean anything. */
877
886
  var PLANE_COUNT = {
878
887
  yuv420p: 3,
@@ -2599,29 +2608,28 @@ var DecodeWorkerChild = class {
2599
2608
  const dstFmt = format === "gray" ? C.AV_PIX_FMT_GRAY8 : C.AV_PIX_FMT_RGB24;
2600
2609
  const channels = format === "gray" ? 1 : 3;
2601
2610
  this.logColorProvenance(isFullFrameRegion(region, frame.width, frame.height) ? "sw-fullframe" : "sw-crop", this.resolveFrameColor(frame));
2602
- const planes = frame.data;
2603
- const strides = frame.linesize;
2604
- const yPlane = planes?.[0];
2605
- const uPlane = planes?.[1];
2606
- const vPlane = planes?.[2];
2607
- if (!yPlane || !uPlane || !vPlane) throw new Error("decode-worker-child: decoded frame missing planar YUV420P data");
2608
- const yStride = strides[0] ?? 0;
2609
- const cStride = strides[1] ?? 0;
2610
- const ySlice = yPlane.subarray(region.top * yStride + region.left);
2611
- const uSlice = uPlane.subarray(region.top / 2 * cStride + region.left / 2);
2612
- const vSlice = vPlane.subarray(region.top / 2 * cStride + region.left / 2);
2613
- const scaler = this.ensureScaler(nav, C, region.width, region.height, target.width, target.height, dstFmt);
2611
+ const kind = softwareRoiKind(frame.format, C);
2612
+ if (kind === null) throw new Error(`decode-worker-child: decoded frame in unsupported pixel format ${frame.format} (planes=${frame.data?.length ?? 0}, hw=${frame.isHwFrame()})`);
2613
+ if (kind === "rgb24" && format === "rgb") return this.nativeRgbCropToBuffer(nav, C, frame, {
2614
+ region,
2615
+ target
2616
+ });
2617
+ const source = kind === "rgb24" ? (() => {
2618
+ const plane = frame.data?.[0];
2619
+ const stride = frame.linesize[0] ?? 0;
2620
+ if (!plane || stride <= 0) return null;
2621
+ const rgb = resolveRgbCropSource(plane, stride, region);
2622
+ return {
2623
+ planes: [rgb.src],
2624
+ strides: [rgb.stride]
2625
+ };
2626
+ })() : resolvePlanarCropSource(kind, frame.data, frame.linesize, region);
2627
+ if (!source) throw new Error(`decode-worker-child: retained ${kind} frame missing plane data for ${region.width}x${region.height}+${region.left}+${region.top} (planes=${frame.data?.length ?? 0})`);
2628
+ const scaler = this.ensureScaler(nav, C, region.width, region.height, target.width, target.height, dstFmt, kind);
2614
2629
  const dstStride = target.width * channels;
2615
2630
  const dstBuffer = Buffer.allocUnsafe(dstStride * target.height + 64);
2616
- const scaledHeight = scaler.scaleSync([
2617
- ySlice,
2618
- uSlice,
2619
- vSlice
2620
- ], [
2621
- yStride,
2622
- cStride,
2623
- cStride
2624
- ], 0, region.height, [dstBuffer], [dstStride]);
2631
+ const srcPlanes = source.planes.map((plane) => Buffer.from(plane.buffer, plane.byteOffset, plane.byteLength));
2632
+ const scaledHeight = scaler.scaleSync(srcPlanes, [...source.strides], 0, region.height, [dstBuffer], [dstStride]);
2625
2633
  if (scaledHeight < 0) throw new Error(`sws_scale failed: ${scaledHeight}`);
2626
2634
  if (scaledHeight !== target.height) throw new Error(`scaler produced ${scaledHeight} of ${target.height} rows`);
2627
2635
  return dstBuffer.subarray(0, dstStride * target.height);
@@ -2634,15 +2642,15 @@ var DecodeWorkerChild = class {
2634
2642
  * that leaked native libav memory at frame rate. Synchronous + single-thread
2635
2643
  * (Node), so no epoch/race guard is needed (unlike the async shared session).
2636
2644
  */
2637
- ensureScaler(nav, C, srcW, srcH, dstW, dstH, dstFmt) {
2638
- const key = `${srcW}x${srcH}->${dstW}x${dstH}:${dstFmt}`;
2645
+ ensureScaler(nav, C, srcW, srcH, dstW, dstH, dstFmt, srcKind = "yuv420p") {
2646
+ const key = `${srcKind}:${srcW}x${srcH}->${dstW}x${dstH}:${dstFmt}`;
2639
2647
  const cached = this.scaler;
2640
2648
  if (cached && key === this.scalerKey) return cached;
2641
2649
  this.scaler?.[Symbol.dispose]?.();
2642
2650
  this.scaler = null;
2643
2651
  this.scalerKey = "";
2644
2652
  const scaler = new nav.SoftwareScaleContext();
2645
- scaler.getContext(srcW, srcH, C.AV_PIX_FMT_YUV420P, dstW, dstH, dstFmt, C.SWS_FAST_BILINEAR);
2653
+ scaler.getContext(srcW, srcH, navPixelFormatFor(srcKind, C), dstW, dstH, dstFmt, C.SWS_FAST_BILINEAR);
2646
2654
  const initRet = scaler.initContext();
2647
2655
  if (initRet < 0) {
2648
2656
  scaler[Symbol.dispose]?.();
@@ -1,7 +1,7 @@
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-BAMyzCWw.mjs";
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-D-NFPV_g.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-B1LDFHY9.mjs";
4
+ import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BiNja8Hw.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) => {
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.99",
21
+ version: "1.2.101",
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.68",
36
+ version: "1.2.69",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_stream_broker_widgets",