@camstack/addon-pipeline 1.2.88 → 1.2.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{addon-utils-B8tPjc8F.js → addon-utils-yc-chXuC.js} +1 -1
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +4 -4
- package/dist/detection-pipeline/index.mjs +2 -2
- package/dist/{dist-DCnOL8bz.mjs → dist-BJYwWEZ9.mjs} +54 -11
- package/dist/{dist-CMHxEH6s.js → dist-n4Nbnxx5.js} +54 -11
- package/dist/{event-loop-stall-monitor-BLJ0S3w2.mjs → event-loop-stall-monitor-BiPffk0D.mjs} +1 -1
- package/dist/{event-loop-stall-monitor-asYJVMb1.js → event-loop-stall-monitor-DG7QY00k.js} +1 -1
- package/dist/{lazy-sharp-1s0ymn1V.js → lazy-sharp-EoSOHgoj.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +4 -4
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/recorder/index.js +189 -51
- package/dist/recorder/index.mjs +188 -50
- package/dist/session-decode/decode-worker-child.js +2 -2
- package/dist/session-decode/decode-worker-child.mjs +1 -1
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BeSS5V2H.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CsrMZYLB.mjs} +1 -1
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B2scqOH7.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DNSELPGi.mjs} +1 -1
- package/dist/stream-broker/{hostInit-BC9QIyzJ.mjs → hostInit-DoFovQmc.mjs} +1 -1
- package/dist/stream-broker/index.js +3 -2
- package/dist/stream-broker/index.mjs +3 -2
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-BzvA7rx4.js → worker-protocol-DjuxhGo3.js} +1 -1
- package/dist/{worker-protocol-CBTyzyds.mjs → worker-protocol-XZ1e36rM.mjs} +1 -1
- package/package.json +1 -1
package/dist/recorder/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const require_dist = require("../dist-
|
|
1
|
+
const require_dist = require("../dist-n4Nbnxx5.js");
|
|
2
2
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
3
|
-
const require_addon_utils = require("../addon-utils-
|
|
3
|
+
const require_addon_utils = require("../addon-utils-yc-chXuC.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
let node_child_process = require("node:child_process");
|
|
6
6
|
let node_path = require("node:path");
|
|
@@ -2634,6 +2634,28 @@ function withStreamTimeDenseRanges(input) {
|
|
|
2634
2634
|
}
|
|
2635
2635
|
//#endregion
|
|
2636
2636
|
//#region src/recorder/addon/export-ffmpeg-args.ts
|
|
2637
|
+
/**
|
|
2638
|
+
* ffmpeg argv builder for recording EXPORTS (render a footage range → one MP4).
|
|
2639
|
+
*
|
|
2640
|
+
* Input is the export's OWN source playlist for the range — a per-export file
|
|
2641
|
+
* whose segment URIs are absolute on-disk paths (`export-source-playlist.ts`).
|
|
2642
|
+
* It is deliberately NOT the playback manifest's `localMasterPath`: those URIs
|
|
2643
|
+
* resolve only through the data-plane's multi-root search, which ffmpeg does not
|
|
2644
|
+
* have. Output is a single faststart MP4. Three shapes, from `ExportOptions`:
|
|
2645
|
+
* - 1× + audio → pure remux (`-c copy`), zero re-encode.
|
|
2646
|
+
* - speed ≠ 1 → `setpts=PTS/speed` video re-encode; audio kept
|
|
2647
|
+
* only for 0.5–2× (single `atempo`), else dropped.
|
|
2648
|
+
* - timelapse → sample one frame per `everyMs`, output at
|
|
2649
|
+
* `outputFps`, always silent.
|
|
2650
|
+
* Distinct from `buildPassthroughArgs` (live segmentation) — do not conflate.
|
|
2651
|
+
*
|
|
2652
|
+
* A **timelapse of a long HLS concat** is two argv sets, not one. Pass 1
|
|
2653
|
+
* (`buildConcatPassArgs`) remuxes the playlist to a single mpegts with `-c
|
|
2654
|
+
* copy`. Pass 2 (`buildExportArgs` with that file as `-i`) then selects
|
|
2655
|
+
* frames. Selecting WHILE walking `#EXT-X-DISCONTINUITY` (one per 10s
|
|
2656
|
+
* segment from `-reset_timestamps`) is what parked a 30h salone job at
|
|
2657
|
+
* `rendering 0%` / 0% CPU — ffmpeg never left the concat demuxer.
|
|
2658
|
+
*/
|
|
2637
2659
|
/** Widest [min,max] speed a single ffmpeg `atempo` filter accepts. Outside this
|
|
2638
2660
|
* band audio is dropped rather than chained (kept simple + predictable). */
|
|
2639
2661
|
var ATEMPO_MIN = .5;
|
|
@@ -2697,6 +2719,29 @@ function selectPredicate(everySec, dense) {
|
|
|
2697
2719
|
if (dense === void 0 || dense.ranges.length === 0) return `${seed}+${elapsed(everySec)}`;
|
|
2698
2720
|
return `${seed}+if(${dense.ranges.map((r) => `between(t,${sec(r.fromSec)},${sec(r.toSec)})`).join("+")},${elapsed(dense.everyMs / 1e3)},${elapsed(everySec)})`;
|
|
2699
2721
|
}
|
|
2722
|
+
/**
|
|
2723
|
+
* Sidecar for timelapse pass 1: same directory as the final MP4, `.concat.ts`.
|
|
2724
|
+
* mpegts (not MP4) because `-c copy` across HLS discontinuities is reliable
|
|
2725
|
+
* in a transport stream and often is not in a fragmented MP4.
|
|
2726
|
+
*/
|
|
2727
|
+
function concatPassOutPath(outPath) {
|
|
2728
|
+
const dir = node_path.default.dirname(outPath);
|
|
2729
|
+
const base = node_path.default.basename(outPath, node_path.default.extname(outPath));
|
|
2730
|
+
return node_path.default.join(dir, `${base}.concat.ts`);
|
|
2731
|
+
}
|
|
2732
|
+
/** Pass 1 of a timelapse: remux the HLS playlist into one continuous file. */
|
|
2733
|
+
function buildConcatPassArgs(input) {
|
|
2734
|
+
return [
|
|
2735
|
+
"-i",
|
|
2736
|
+
input.inputPlaylist,
|
|
2737
|
+
"-c",
|
|
2738
|
+
"copy",
|
|
2739
|
+
"-an",
|
|
2740
|
+
"-f",
|
|
2741
|
+
"mpegts",
|
|
2742
|
+
input.outPath
|
|
2743
|
+
];
|
|
2744
|
+
}
|
|
2700
2745
|
/** Build the ffmpeg argv for one export render. */
|
|
2701
2746
|
function buildExportArgs(input) {
|
|
2702
2747
|
const { inputPlaylist, outPath, options } = input;
|
|
@@ -2983,12 +3028,6 @@ var ExportEngine = class {
|
|
|
2983
3028
|
/** Spawn ffmpeg for one render and resolve when it has exited + finalized. */
|
|
2984
3029
|
async spawnRender(id, rec, playlist, options) {
|
|
2985
3030
|
const outPath = this.deps.outPath(rec);
|
|
2986
|
-
const args = buildExportArgs({
|
|
2987
|
-
inputPlaylist: playlist,
|
|
2988
|
-
outPath,
|
|
2989
|
-
options
|
|
2990
|
-
});
|
|
2991
|
-
const totalSec = expectedOutSeconds(rec);
|
|
2992
3031
|
this.deps.logger.info("export render starting", {
|
|
2993
3032
|
tags: { deviceId: rec.deviceId },
|
|
2994
3033
|
meta: {
|
|
@@ -2999,11 +3038,67 @@ var ExportEngine = class {
|
|
|
2999
3038
|
outPath
|
|
3000
3039
|
}
|
|
3001
3040
|
});
|
|
3041
|
+
if (options.timelapse) {
|
|
3042
|
+
await this.spawnTimelapse(id, rec, playlist, outPath, options);
|
|
3043
|
+
return;
|
|
3044
|
+
}
|
|
3045
|
+
const args = buildExportArgs({
|
|
3046
|
+
inputPlaylist: playlist,
|
|
3047
|
+
outPath,
|
|
3048
|
+
options
|
|
3049
|
+
});
|
|
3050
|
+
const ran = await this.runFfmpeg(id, args, {
|
|
3051
|
+
totalSec: expectedOutSeconds(rec),
|
|
3052
|
+
floor: 0,
|
|
3053
|
+
ceil: 99
|
|
3054
|
+
});
|
|
3055
|
+
await this.finish(id, ran.code, outPath, ran.stderrTail);
|
|
3056
|
+
}
|
|
3057
|
+
/**
|
|
3058
|
+
* Concat the HLS pieces into one continuous file, THEN select frames.
|
|
3059
|
+
* The select filter on a discontinuity-heavy playlist is what parked a
|
|
3060
|
+
* 30h job at `rendering 0%` — pass 1 is `-c copy` into mpegts so pass 2
|
|
3061
|
+
* sees a single timestamp line.
|
|
3062
|
+
*/
|
|
3063
|
+
async spawnTimelapse(id, rec, playlist, outPath, options) {
|
|
3064
|
+
const concatPath = concatPassOutPath(outPath);
|
|
3065
|
+
try {
|
|
3066
|
+
const concatArgs = buildConcatPassArgs({
|
|
3067
|
+
inputPlaylist: playlist,
|
|
3068
|
+
outPath: concatPath
|
|
3069
|
+
});
|
|
3070
|
+
const concatRan = await this.runFfmpeg(id, concatArgs, {
|
|
3071
|
+
totalSec: Math.max(.001, (rec.toMs - rec.fromMs) / 1e3),
|
|
3072
|
+
floor: 0,
|
|
3073
|
+
ceil: 49
|
|
3074
|
+
});
|
|
3075
|
+
if (this.cancelled.has(id) || concatRan.code !== 0) {
|
|
3076
|
+
await this.finish(id, concatRan.code, concatPath, concatRan.stderrTail);
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
3079
|
+
const selectArgs = buildExportArgs({
|
|
3080
|
+
inputPlaylist: concatPath,
|
|
3081
|
+
outPath,
|
|
3082
|
+
options
|
|
3083
|
+
});
|
|
3084
|
+
const selectRan = await this.runFfmpeg(id, selectArgs, {
|
|
3085
|
+
totalSec: expectedOutSeconds(rec),
|
|
3086
|
+
floor: 50,
|
|
3087
|
+
ceil: 99
|
|
3088
|
+
});
|
|
3089
|
+
await this.finish(id, selectRan.code, outPath, selectRan.stderrTail);
|
|
3090
|
+
} finally {
|
|
3091
|
+
try {
|
|
3092
|
+
await this.deps.removeFile(concatPath);
|
|
3093
|
+
} catch {}
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
runFfmpeg(id, args, progress) {
|
|
3002
3097
|
this.deps.logger.debug("export ffmpeg argv", { meta: {
|
|
3003
3098
|
exportId: id,
|
|
3004
3099
|
args
|
|
3005
3100
|
} });
|
|
3006
|
-
|
|
3101
|
+
return new Promise((resolve) => {
|
|
3007
3102
|
const proc = this.deps.spawn(this.deps.ffmpegPath ?? "ffmpeg", args);
|
|
3008
3103
|
this.activeProc = proc;
|
|
3009
3104
|
const stderrTail = [];
|
|
@@ -3018,7 +3113,8 @@ var ExportEngine = class {
|
|
|
3018
3113
|
const m = TIME_RE.exec(line);
|
|
3019
3114
|
if (m) {
|
|
3020
3115
|
const sec = Number(m[1]) * 3600 + Number(m[2]) * 60 + Number(m[3]);
|
|
3021
|
-
const
|
|
3116
|
+
const span = progress.ceil - progress.floor;
|
|
3117
|
+
const pct = Math.min(progress.ceil, progress.floor + Math.round(sec / progress.totalSec * span));
|
|
3022
3118
|
patchExport(this.deps.state, id, { progressPct: pct });
|
|
3023
3119
|
this.deps.emit(require_dist.EventCategory.RecordingExportProgress, {
|
|
3024
3120
|
exportId: id,
|
|
@@ -3029,7 +3125,10 @@ var ExportEngine = class {
|
|
|
3029
3125
|
});
|
|
3030
3126
|
proc.on("exit", (code) => {
|
|
3031
3127
|
this.activeProc = null;
|
|
3032
|
-
|
|
3128
|
+
resolve({
|
|
3129
|
+
code: typeof code === "number" ? code : null,
|
|
3130
|
+
stderrTail
|
|
3131
|
+
});
|
|
3033
3132
|
});
|
|
3034
3133
|
});
|
|
3035
3134
|
}
|
|
@@ -3080,9 +3179,15 @@ var ExportEngine = class {
|
|
|
3080
3179
|
outPath,
|
|
3081
3180
|
bytes
|
|
3082
3181
|
} });
|
|
3182
|
+
const rec = await getExportRecord(this.deps.state, id);
|
|
3083
3183
|
this.deps.emit(require_dist.EventCategory.RecordingExportCompleted, {
|
|
3084
3184
|
exportId: id,
|
|
3085
|
-
fileBytes: bytes
|
|
3185
|
+
fileBytes: bytes,
|
|
3186
|
+
...rec !== null ? {
|
|
3187
|
+
deviceId: rec.deviceId,
|
|
3188
|
+
...rec.options.title !== void 0 ? { title: rec.options.title } : {},
|
|
3189
|
+
...rec.options.notifyTargetIds !== void 0 && rec.options.notifyTargetIds.length > 0 ? { notifyTargetIds: rec.options.notifyTargetIds } : {}
|
|
3190
|
+
} : {}
|
|
3086
3191
|
});
|
|
3087
3192
|
}
|
|
3088
3193
|
async fail(id, error) {
|
|
@@ -5759,8 +5864,19 @@ function startPeriodicPass(deps) {
|
|
|
5759
5864
|
isRunning: () => startedAt !== null
|
|
5760
5865
|
};
|
|
5761
5866
|
}
|
|
5762
|
-
|
|
5763
|
-
|
|
5867
|
+
async function runBounded(items, concurrency, worker) {
|
|
5868
|
+
if (items.length === 0) return;
|
|
5869
|
+
const queue = [...items];
|
|
5870
|
+
const lanes = Math.max(1, Math.min(concurrency, queue.length));
|
|
5871
|
+
const lane = async () => {
|
|
5872
|
+
for (;;) {
|
|
5873
|
+
const item = queue.shift();
|
|
5874
|
+
if (item === void 0) return;
|
|
5875
|
+
await worker(item);
|
|
5876
|
+
}
|
|
5877
|
+
};
|
|
5878
|
+
await Promise.all(Array.from({ length: lanes }, () => lane()));
|
|
5879
|
+
}
|
|
5764
5880
|
var ReadinessRestore = class {
|
|
5765
5881
|
deps;
|
|
5766
5882
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -5770,6 +5886,9 @@ var ReadinessRestore = class {
|
|
|
5770
5886
|
constructor(deps) {
|
|
5771
5887
|
this.deps = deps;
|
|
5772
5888
|
}
|
|
5889
|
+
get concurrency() {
|
|
5890
|
+
return Math.max(1, this.deps.concurrency ?? 8);
|
|
5891
|
+
}
|
|
5773
5892
|
/** Seed the pending set, subscribe to readiness, and drain once immediately
|
|
5774
5893
|
* (covers the already-ready case). Returns when the first drain settles. */
|
|
5775
5894
|
async start(items) {
|
|
@@ -5800,7 +5919,8 @@ var ReadinessRestore = class {
|
|
|
5800
5919
|
return this.pending.has(key);
|
|
5801
5920
|
}
|
|
5802
5921
|
/** Attempt every still-pending item; repeat once if a readiness signal arrived
|
|
5803
|
-
* mid-flight. Re-entrant and self-coalescing.
|
|
5922
|
+
* mid-flight. Re-entrant and self-coalescing. Overlapping up to
|
|
5923
|
+
* {@link ReadinessRestoreDeps.concurrency}. */
|
|
5804
5924
|
async drain() {
|
|
5805
5925
|
if (this.draining) {
|
|
5806
5926
|
this.rerun = true;
|
|
@@ -5811,12 +5931,15 @@ var ReadinessRestore = class {
|
|
|
5811
5931
|
try {
|
|
5812
5932
|
do {
|
|
5813
5933
|
this.rerun = false;
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5934
|
+
await runBounded(Array.from(this.pending), this.concurrency, async ([key, value]) => {
|
|
5935
|
+
if (!this.pending.has(key)) return;
|
|
5936
|
+
try {
|
|
5937
|
+
await this.deps.attempt(key, value);
|
|
5938
|
+
this.pending.delete(key);
|
|
5939
|
+
} catch (err) {
|
|
5940
|
+
this.deps.onDefer?.(key, value, err);
|
|
5941
|
+
}
|
|
5942
|
+
});
|
|
5820
5943
|
} while (this.rerun && this.pending.size > 0);
|
|
5821
5944
|
} finally {
|
|
5822
5945
|
this.draining = false;
|
|
@@ -6511,14 +6634,16 @@ var RecordingController = class {
|
|
|
6511
6634
|
} catch (err) {
|
|
6512
6635
|
this.deps.logger.warn("recorder controller: placement recompute failed", { meta: { error: require_dist.errMsg(err) } });
|
|
6513
6636
|
}
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6637
|
+
await runBounded([...ids], 8, async (id) => {
|
|
6638
|
+
try {
|
|
6639
|
+
await this.evaluateDevice(id);
|
|
6640
|
+
} catch (err) {
|
|
6641
|
+
this.deps.logger.warn("recorder controller: convergence evaluate failed", {
|
|
6642
|
+
tags: { deviceId: id },
|
|
6643
|
+
meta: { error: require_dist.errMsg(err) }
|
|
6644
|
+
});
|
|
6645
|
+
}
|
|
6646
|
+
});
|
|
6522
6647
|
}
|
|
6523
6648
|
/**
|
|
6524
6649
|
* Re-evaluate one device after a config change (called from `setDeviceConfig`).
|
|
@@ -6942,6 +7067,12 @@ function exportFileName(rec) {
|
|
|
6942
7067
|
async function hasFootage(index, deviceId, profile, fromMs, toMs) {
|
|
6943
7068
|
return (await index.segmentsInWindow(deviceId, profile, fromMs, toMs)).some((s) => s.startMs < toMs && s.startMs + s.durMs > fromMs);
|
|
6944
7069
|
}
|
|
7070
|
+
/** Canonical `profiles[]`, falling back to the legacy singular `profile`. */
|
|
7071
|
+
function resolveExportProfiles(input) {
|
|
7072
|
+
if (input.profiles !== void 0 && input.profiles.length > 0) return [...input.profiles];
|
|
7073
|
+
if (typeof input.profile === "string" && input.profile.length > 0) return [input.profile];
|
|
7074
|
+
return [];
|
|
7075
|
+
}
|
|
6945
7076
|
/** Load a record or throw a clear NOT_FOUND-style error. */
|
|
6946
7077
|
async function requireRecord(state, exportId) {
|
|
6947
7078
|
const rec = await getExportRecord(state, exportId);
|
|
@@ -6951,30 +7082,37 @@ async function requireRecord(state, exportId) {
|
|
|
6951
7082
|
/** Build the `recordingExport` provider over the injected deps. */
|
|
6952
7083
|
function buildRecordingExportProvider(deps) {
|
|
6953
7084
|
return {
|
|
6954
|
-
createExport: async (
|
|
7085
|
+
createExport: async (input) => {
|
|
7086
|
+
const { deviceId, fromMs, toMs, options } = input;
|
|
7087
|
+
const profiles = resolveExportProfiles(input);
|
|
7088
|
+
if (profiles.length < 1) throw new Error("recording-export: profiles must contain at least one profile");
|
|
6955
7089
|
if (!(fromMs < toMs)) throw new Error(`recording-export: invalid range ${fromMs}..${toMs}`);
|
|
6956
|
-
if (!await hasFootage(deps.index, deviceId, profile, fromMs, toMs)) throw new Error(`recording-export: no footage for ${deviceId}/${profile} in ${fromMs}..${toMs}`);
|
|
7090
|
+
for (const profile of profiles) if (!await hasFootage(deps.index, deviceId, profile, fromMs, toMs)) throw new Error(`recording-export: no footage for ${deviceId}/${profile} in ${fromMs}..${toMs}`);
|
|
6957
7091
|
const now = deps.now();
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
7092
|
+
let first = null;
|
|
7093
|
+
for (const profile of profiles) {
|
|
7094
|
+
const record = {
|
|
7095
|
+
id: deps.newId(),
|
|
7096
|
+
deviceId,
|
|
7097
|
+
profile,
|
|
7098
|
+
fromMs,
|
|
7099
|
+
toMs,
|
|
7100
|
+
options,
|
|
7101
|
+
state: "queued",
|
|
7102
|
+
progressPct: null,
|
|
7103
|
+
fileBytes: null,
|
|
7104
|
+
expiresAt: now + options.maxLifeMs,
|
|
7105
|
+
deleteAfterDownload: options.deleteAfterDownload,
|
|
7106
|
+
downloadedAt: null,
|
|
7107
|
+
createdAt: now,
|
|
7108
|
+
createdBy: deps.createdBy(),
|
|
7109
|
+
error: null
|
|
7110
|
+
};
|
|
7111
|
+
const saved = await upsertExport(deps.state, record);
|
|
7112
|
+
deps.engine.enqueue(saved.id);
|
|
7113
|
+
first ??= saved;
|
|
7114
|
+
}
|
|
7115
|
+
return first;
|
|
6978
7116
|
},
|
|
6979
7117
|
listExports: ({ deviceId }) => listExportsFor(deps.state, deviceId),
|
|
6980
7118
|
getExport: ({ exportId }) => requireRecord(deps.state, exportId),
|