@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.
- package/dist/{addon-utils-C0ht2KPO.js → addon-utils-RAHF2hmW.js} +1 -1
- package/dist/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/detection-pipeline/index.js +171 -167
- package/dist/detection-pipeline/index.mjs +169 -165
- package/dist/{dist-BqIbYW1A.js → dist-DIsM42Ex.js} +121 -11
- package/dist/{dist-DGQkkWc6.mjs → dist-Db664g2O.mjs} +116 -12
- package/dist/{event-loop-stall-monitor-ylMGpz48.js → event-loop-stall-monitor-CSpdFZb6.js} +1 -1
- package/dist/{event-loop-stall-monitor-D5jK5v4Z.mjs → event-loop-stall-monitor-DY6339bX.mjs} +1 -1
- package/dist/{lazy-sharp-cOsy7l_P.js → lazy-sharp-LSlEXQe_.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 +12 -14
- package/dist/pipeline-runner/index.mjs +11 -13
- package/dist/{process-memory-BtT7WfbJ.mjs → process-memory-CPm5TMBl.mjs} +1 -1
- package/dist/{process-memory-45juuHpN.js → process-memory-DpSJQF-p.js} +1 -1
- package/dist/recorder/index.js +261 -32
- package/dist/recorder/index.mjs +260 -31
- package/dist/session-decode/decode-worker-child.js +34 -26
- package/dist/session-decode/decode-worker-child.mjs +33 -25
- package/dist/stream-broker/_stub.js +2 -2
- 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
- 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
- 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
- package/dist/stream-broker/{hostInit-CG28NvlV.mjs → hostInit-kHeLqEod.mjs} +2 -2
- package/dist/stream-broker/index.js +8 -6
- package/dist/stream-broker/index.mjs +8 -6
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-CIrqGbpG.js → worker-protocol-C1tr8WP3.js} +1 -1
- package/dist/{worker-protocol-DyocTIx_.mjs → worker-protocol-_zMPqSas.mjs} +1 -1
- package/package.json +1 -1
- 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
package/dist/recorder/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const require_dist = require("../dist-
|
|
1
|
+
const require_dist = require("../dist-DIsM42Ex.js");
|
|
2
2
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
3
3
|
const require_restream_intent = require("../restream-intent-Cv9x3jmu.js");
|
|
4
|
-
const require_addon_utils = require("../addon-utils-
|
|
4
|
+
const require_addon_utils = require("../addon-utils-RAHF2hmW.js");
|
|
5
5
|
const require_retire_root_keys = require("../retire-root-keys-KE6D6Xh_.js");
|
|
6
6
|
let node_crypto = require("node:crypto");
|
|
7
7
|
let node_child_process = require("node:child_process");
|
|
@@ -2984,11 +2984,23 @@ function concatFileDirective(uri) {
|
|
|
2984
2984
|
function buildExportConcatList(segments) {
|
|
2985
2985
|
return `ffconcat version 1.0\n${segments.map((s) => `${concatFileDirective(s.uri)}\nduration ${sec(s.durMs / 1e3)}`).map((l) => `${l}\n`).join("")}`;
|
|
2986
2986
|
}
|
|
2987
|
+
/**
|
|
2988
|
+
* Concat list for already-finished MP4s. A `duration` line here would
|
|
2989
|
+
* TRUNCATE each hour to whatever dummy we wrote (the stream clock is
|
|
2990
|
+
* already inside the file).
|
|
2991
|
+
*/
|
|
2992
|
+
function buildAssembleConcatList(uris) {
|
|
2993
|
+
return `ffconcat version 1.0\n${uris.map((u) => `${concatFileDirective(u)}\n`).join("")}`;
|
|
2994
|
+
}
|
|
2987
2995
|
/** Pass 1 of a timelapse: remux the ffconcat list into one continuous file.
|
|
2988
2996
|
* `-safe 0` because the list's paths are absolute — which is exactly what the
|
|
2989
2997
|
* per-export source list contains (`export-source-playlist.ts`). */
|
|
2990
2998
|
function buildConcatPassArgs(input) {
|
|
2991
2999
|
return [
|
|
3000
|
+
"-fflags",
|
|
3001
|
+
"+discardcorrupt+genpts",
|
|
3002
|
+
"-err_detect",
|
|
3003
|
+
"ignore_err",
|
|
2992
3004
|
"-f",
|
|
2993
3005
|
"concat",
|
|
2994
3006
|
"-safe",
|
|
@@ -3003,6 +3015,21 @@ function buildConcatPassArgs(input) {
|
|
|
3003
3015
|
input.outPath
|
|
3004
3016
|
];
|
|
3005
3017
|
}
|
|
3018
|
+
/** Final stitch of the surviving per-hour MP4s. Already selected + encoded. */
|
|
3019
|
+
function buildAssembleArgs(input) {
|
|
3020
|
+
return [
|
|
3021
|
+
"-f",
|
|
3022
|
+
"concat",
|
|
3023
|
+
"-safe",
|
|
3024
|
+
"0",
|
|
3025
|
+
"-i",
|
|
3026
|
+
input.inputConcatList,
|
|
3027
|
+
"-c",
|
|
3028
|
+
"copy",
|
|
3029
|
+
...FASTSTART,
|
|
3030
|
+
input.outPath
|
|
3031
|
+
];
|
|
3032
|
+
}
|
|
3006
3033
|
/** Build the ffmpeg argv for one export render. */
|
|
3007
3034
|
function buildExportArgs(input) {
|
|
3008
3035
|
const { inputPlaylist, outPath, options } = input;
|
|
@@ -3048,6 +3075,65 @@ function buildExportArgs(input) {
|
|
|
3048
3075
|
];
|
|
3049
3076
|
}
|
|
3050
3077
|
//#endregion
|
|
3078
|
+
//#region src/recorder/addon/export-timelapse-chunks.ts
|
|
3079
|
+
/** One hour of concatenated footage — not one hour of wall clock. */
|
|
3080
|
+
var TIMELAPSE_CHUNK_MS = 36e5;
|
|
3081
|
+
/**
|
|
3082
|
+
* Split a concat list into hour-sized chunks. A single file longer than the
|
|
3083
|
+
* chunk stays whole — it is already one open, and cutting it would need a
|
|
3084
|
+
* second demux we do not have.
|
|
3085
|
+
*/
|
|
3086
|
+
function splitTimelapseChunks(entries, chunkMs = TIMELAPSE_CHUNK_MS) {
|
|
3087
|
+
if (entries.length === 0) return [];
|
|
3088
|
+
const chunks = [];
|
|
3089
|
+
let current = [];
|
|
3090
|
+
let currentDur = 0;
|
|
3091
|
+
let streamStartMs = 0;
|
|
3092
|
+
for (const entry of entries) {
|
|
3093
|
+
if (current.length > 0 && currentDur + entry.durMs > chunkMs) {
|
|
3094
|
+
chunks.push({
|
|
3095
|
+
entries: current,
|
|
3096
|
+
streamStartMs
|
|
3097
|
+
});
|
|
3098
|
+
streamStartMs += currentDur;
|
|
3099
|
+
current = [];
|
|
3100
|
+
currentDur = 0;
|
|
3101
|
+
}
|
|
3102
|
+
current.push(entry);
|
|
3103
|
+
currentDur += entry.durMs;
|
|
3104
|
+
}
|
|
3105
|
+
if (current.length > 0) chunks.push({
|
|
3106
|
+
entries: current,
|
|
3107
|
+
streamStartMs
|
|
3108
|
+
});
|
|
3109
|
+
return chunks;
|
|
3110
|
+
}
|
|
3111
|
+
/**
|
|
3112
|
+
* Move stream-time dense ranges onto a chunk's own `t=0`. Ranges that miss
|
|
3113
|
+
* the chunk disappear; a range that straddles the edge is clipped. Empty →
|
|
3114
|
+
* `undefined` so the select stays the single-rate form.
|
|
3115
|
+
*/
|
|
3116
|
+
function denseRangesForChunk(dense, chunkStreamStartMs, chunkDurMs) {
|
|
3117
|
+
if (dense === void 0 || dense.ranges.length === 0) return void 0;
|
|
3118
|
+
const startSec = chunkStreamStartMs / 1e3;
|
|
3119
|
+
const endSec = (chunkStreamStartMs + chunkDurMs) / 1e3;
|
|
3120
|
+
const ranges = [];
|
|
3121
|
+
for (const r of dense.ranges) {
|
|
3122
|
+
const from = Math.max(r.fromSec, startSec);
|
|
3123
|
+
const to = Math.min(r.toSec, endSec);
|
|
3124
|
+
if (to <= from) continue;
|
|
3125
|
+
ranges.push({
|
|
3126
|
+
fromSec: from - startSec,
|
|
3127
|
+
toSec: to - startSec
|
|
3128
|
+
});
|
|
3129
|
+
}
|
|
3130
|
+
if (ranges.length === 0) return void 0;
|
|
3131
|
+
return {
|
|
3132
|
+
everyMs: dense.everyMs,
|
|
3133
|
+
ranges
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
//#endregion
|
|
3051
3137
|
//#region src/recorder/addon/export-store.ts
|
|
3052
3138
|
/**
|
|
3053
3139
|
* Recording-export persistence for the recorder addon.
|
|
@@ -3311,15 +3397,15 @@ function expectedOutSeconds(rec) {
|
|
|
3311
3397
|
var ExportEngine = class {
|
|
3312
3398
|
deps;
|
|
3313
3399
|
queue = [];
|
|
3314
|
-
|
|
3315
|
-
|
|
3400
|
+
/** In-flight jobs → their current ffmpeg (null between the two timelapse passes). */
|
|
3401
|
+
active = /* @__PURE__ */ new Map();
|
|
3316
3402
|
cancelled = /* @__PURE__ */ new Set();
|
|
3317
3403
|
constructor(deps) {
|
|
3318
3404
|
this.deps = deps;
|
|
3319
3405
|
}
|
|
3320
3406
|
/** Queue a persisted `queued` record for rendering. */
|
|
3321
3407
|
enqueue(id) {
|
|
3322
|
-
if (this.queue.includes(id) || this.
|
|
3408
|
+
if (this.queue.includes(id) || this.active.has(id)) return;
|
|
3323
3409
|
this.queue.push(id);
|
|
3324
3410
|
this.pump();
|
|
3325
3411
|
}
|
|
@@ -3328,29 +3414,32 @@ var ExportEngine = class {
|
|
|
3328
3414
|
this.cancelled.add(id);
|
|
3329
3415
|
const idx = this.queue.indexOf(id);
|
|
3330
3416
|
if (idx >= 0) this.queue.splice(idx, 1);
|
|
3331
|
-
|
|
3332
|
-
|
|
3417
|
+
const proc = this.active.get(id);
|
|
3418
|
+
if (proc) try {
|
|
3419
|
+
proc.kill("SIGKILL");
|
|
3333
3420
|
} catch {}
|
|
3334
3421
|
else if (idx >= 0) await this.fail(id, "cancelled");
|
|
3335
3422
|
}
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3423
|
+
pump() {
|
|
3424
|
+
while (this.active.size < 2 && this.queue.length > 0) {
|
|
3425
|
+
const id = this.queue.shift();
|
|
3426
|
+
if (!id) return;
|
|
3427
|
+
if (this.cancelled.has(id)) {
|
|
3428
|
+
this.cancelled.delete(id);
|
|
3429
|
+
this.fail(id, "cancelled");
|
|
3430
|
+
continue;
|
|
3431
|
+
}
|
|
3432
|
+
this.active.set(id, null);
|
|
3433
|
+
this.runOne(id);
|
|
3345
3434
|
}
|
|
3346
|
-
|
|
3435
|
+
}
|
|
3436
|
+
async runOne(id) {
|
|
3347
3437
|
try {
|
|
3348
3438
|
await this.render(id);
|
|
3349
3439
|
} catch (err) {
|
|
3350
3440
|
await this.fail(id, err instanceof Error ? err.message : String(err));
|
|
3351
3441
|
} finally {
|
|
3352
|
-
this.
|
|
3353
|
-
this.activeProc = null;
|
|
3442
|
+
this.active.delete(id);
|
|
3354
3443
|
this.pump();
|
|
3355
3444
|
}
|
|
3356
3445
|
}
|
|
@@ -3368,7 +3457,7 @@ var ExportEngine = class {
|
|
|
3368
3457
|
try {
|
|
3369
3458
|
await this.spawnRender(id, rec, playlist, this.streamTimeOptions(rec, playlist.segments));
|
|
3370
3459
|
} finally {
|
|
3371
|
-
if (this.deps.cleanupPlaylist) for (const file of [playlist.path, playlist.concatPath]) try {
|
|
3460
|
+
if (this.deps.cleanupPlaylist) for (const file of [playlist.path, playlist.concatPath].filter((p) => typeof p === "string" && p.length > 0)) try {
|
|
3372
3461
|
await this.deps.cleanupPlaylist(file);
|
|
3373
3462
|
} catch (err) {
|
|
3374
3463
|
this.deps.logger.warn("export: source playlist cleanup failed", {
|
|
@@ -3431,7 +3520,7 @@ var ExportEngine = class {
|
|
|
3431
3520
|
}
|
|
3432
3521
|
});
|
|
3433
3522
|
if (options.timelapse) {
|
|
3434
|
-
await this.spawnTimelapse(id, rec, playlist
|
|
3523
|
+
await this.spawnTimelapse(id, rec, playlist, outPath, options);
|
|
3435
3524
|
return;
|
|
3436
3525
|
}
|
|
3437
3526
|
const args = buildExportArgs({
|
|
@@ -3455,8 +3544,118 @@ var ExportEngine = class {
|
|
|
3455
3544
|
* timestamp line runs on the SAME clock the dense map summed — see
|
|
3456
3545
|
* `export-ffmpeg-args.ts` for what the HLS demuxer's content-stitching cost.
|
|
3457
3546
|
*/
|
|
3458
|
-
async spawnTimelapse(id, rec,
|
|
3547
|
+
async spawnTimelapse(id, rec, playlist, outPath, options) {
|
|
3548
|
+
const chunks = splitTimelapseChunks(playlist.concatEntries ?? []);
|
|
3549
|
+
if (chunks.length <= 1) {
|
|
3550
|
+
const concatList = playlist.concatPath;
|
|
3551
|
+
if (concatList === void 0) {
|
|
3552
|
+
await this.fail(id, "no concat list for timelapse");
|
|
3553
|
+
return;
|
|
3554
|
+
}
|
|
3555
|
+
await this.spawnTimelapsePair(id, rec, concatList, outPath, options, {
|
|
3556
|
+
floor: 0,
|
|
3557
|
+
ceil: 99
|
|
3558
|
+
});
|
|
3559
|
+
return;
|
|
3560
|
+
}
|
|
3561
|
+
if (this.deps.writeTextFile === void 0) {
|
|
3562
|
+
const concatList = playlist.concatPath;
|
|
3563
|
+
if (concatList === void 0) {
|
|
3564
|
+
await this.fail(id, "no concat list for timelapse");
|
|
3565
|
+
return;
|
|
3566
|
+
}
|
|
3567
|
+
await this.spawnTimelapsePair(id, rec, concatList, outPath, options, {
|
|
3568
|
+
floor: 0,
|
|
3569
|
+
ceil: 99
|
|
3570
|
+
});
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3573
|
+
const survivors = [];
|
|
3574
|
+
const sidecars = [];
|
|
3575
|
+
try {
|
|
3576
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
3577
|
+
if (this.cancelled.has(id)) {
|
|
3578
|
+
await this.fail(id, "cancelled");
|
|
3579
|
+
return;
|
|
3580
|
+
}
|
|
3581
|
+
const chunk = chunks[i];
|
|
3582
|
+
const chunkDur = chunk.entries.reduce((s, e) => s + e.durMs, 0);
|
|
3583
|
+
const listPath = `${outPath}.chunk${i}.ffconcat`;
|
|
3584
|
+
const concatPath = `${outPath}.chunk${i}.concat.ts`;
|
|
3585
|
+
const chunkMp4 = `${outPath}.chunk${i}.mp4`;
|
|
3586
|
+
sidecars.push(listPath, concatPath, chunkMp4);
|
|
3587
|
+
await this.deps.writeTextFile(listPath, buildExportConcatList(chunk.entries));
|
|
3588
|
+
const span = 90 / chunks.length;
|
|
3589
|
+
const floor = Math.round(i * span);
|
|
3590
|
+
if (await this.spawnTimelapsePair(id, rec, listPath, chunkMp4, {
|
|
3591
|
+
...options,
|
|
3592
|
+
timelapse: options.timelapse ? {
|
|
3593
|
+
...options.timelapse,
|
|
3594
|
+
dense: denseRangesForChunk(options.timelapse.dense, chunk.streamStartMs, chunkDur)
|
|
3595
|
+
} : options.timelapse
|
|
3596
|
+
}, {
|
|
3597
|
+
floor,
|
|
3598
|
+
ceil: floor + Math.round(span * .95),
|
|
3599
|
+
finish: false
|
|
3600
|
+
}) === "ok") survivors.push(chunkMp4);
|
|
3601
|
+
else this.deps.logger.warn("export timelapse hour skipped", {
|
|
3602
|
+
tags: { deviceId: rec.deviceId },
|
|
3603
|
+
meta: {
|
|
3604
|
+
exportId: id,
|
|
3605
|
+
chunk: i,
|
|
3606
|
+
streamStartMs: chunk.streamStartMs
|
|
3607
|
+
}
|
|
3608
|
+
});
|
|
3609
|
+
}
|
|
3610
|
+
if (survivors.length === 0) {
|
|
3611
|
+
await this.fail(id, "every timelapse hour failed");
|
|
3612
|
+
return;
|
|
3613
|
+
}
|
|
3614
|
+
if (survivors.length === 1) {
|
|
3615
|
+
const only = survivors[0];
|
|
3616
|
+
const copyArgs = buildExportArgs({
|
|
3617
|
+
inputPlaylist: only,
|
|
3618
|
+
outPath,
|
|
3619
|
+
options: {
|
|
3620
|
+
includeAudio: false,
|
|
3621
|
+
maxLifeMs: 1,
|
|
3622
|
+
deleteAfterDownload: false,
|
|
3623
|
+
speed: 1
|
|
3624
|
+
}
|
|
3625
|
+
});
|
|
3626
|
+
const copied = await this.runFfmpeg(id, rec.deviceId, copyArgs, {
|
|
3627
|
+
totalSec: expectedOutSeconds(rec),
|
|
3628
|
+
floor: 95,
|
|
3629
|
+
ceil: 99
|
|
3630
|
+
});
|
|
3631
|
+
await this.finish(id, copied.code, outPath, copied.stderrTail);
|
|
3632
|
+
return;
|
|
3633
|
+
}
|
|
3634
|
+
const assembleList = `${outPath}.assemble.ffconcat`;
|
|
3635
|
+
sidecars.push(assembleList);
|
|
3636
|
+
await this.deps.writeTextFile(assembleList, buildAssembleConcatList(survivors));
|
|
3637
|
+
const assembled = await this.runFfmpeg(id, rec.deviceId, buildAssembleArgs({
|
|
3638
|
+
inputConcatList: assembleList,
|
|
3639
|
+
outPath
|
|
3640
|
+
}), {
|
|
3641
|
+
totalSec: expectedOutSeconds(rec),
|
|
3642
|
+
floor: 95,
|
|
3643
|
+
ceil: 99
|
|
3644
|
+
});
|
|
3645
|
+
await this.finish(id, assembled.code, outPath, assembled.stderrTail);
|
|
3646
|
+
} finally {
|
|
3647
|
+
for (const file of sidecars) try {
|
|
3648
|
+
await this.deps.removeFile(file);
|
|
3649
|
+
} catch {}
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
/**
|
|
3653
|
+
* Concat remux then select. `finish: false` lets the chunk walker skip a
|
|
3654
|
+
* dead hour without failing the whole night.
|
|
3655
|
+
*/
|
|
3656
|
+
async spawnTimelapsePair(id, rec, concatList, outPath, options, progress) {
|
|
3459
3657
|
const concatPath = concatPassOutPath(outPath);
|
|
3658
|
+
const mid = progress.floor + Math.round((progress.ceil - progress.floor) / 2);
|
|
3460
3659
|
try {
|
|
3461
3660
|
const concatArgs = buildConcatPassArgs({
|
|
3462
3661
|
inputConcatList: concatList,
|
|
@@ -3464,12 +3663,12 @@ var ExportEngine = class {
|
|
|
3464
3663
|
});
|
|
3465
3664
|
const concatRan = await this.runFfmpeg(id, rec.deviceId, concatArgs, {
|
|
3466
3665
|
totalSec: Math.max(.001, (rec.toMs - rec.fromMs) / 1e3),
|
|
3467
|
-
floor:
|
|
3468
|
-
ceil:
|
|
3666
|
+
floor: progress.floor,
|
|
3667
|
+
ceil: mid
|
|
3469
3668
|
});
|
|
3470
3669
|
if (this.cancelled.has(id) || concatRan.code !== 0) {
|
|
3471
|
-
await this.finish(id, concatRan.code, concatPath, concatRan.stderrTail);
|
|
3472
|
-
return;
|
|
3670
|
+
if (progress.finish !== false) await this.finish(id, concatRan.code, concatPath, concatRan.stderrTail);
|
|
3671
|
+
return "fail";
|
|
3473
3672
|
}
|
|
3474
3673
|
const selectArgs = buildExportArgs({
|
|
3475
3674
|
inputPlaylist: concatPath,
|
|
@@ -3478,10 +3677,11 @@ var ExportEngine = class {
|
|
|
3478
3677
|
});
|
|
3479
3678
|
const selectRan = await this.runFfmpeg(id, rec.deviceId, selectArgs, {
|
|
3480
3679
|
totalSec: expectedOutSeconds(rec),
|
|
3481
|
-
floor:
|
|
3482
|
-
ceil:
|
|
3680
|
+
floor: mid,
|
|
3681
|
+
ceil: progress.ceil
|
|
3483
3682
|
});
|
|
3484
|
-
await this.finish(id, selectRan.code, outPath, selectRan.stderrTail);
|
|
3683
|
+
if (progress.finish !== false) await this.finish(id, selectRan.code, outPath, selectRan.stderrTail);
|
|
3684
|
+
return selectRan.code === 0 ? "ok" : "fail";
|
|
3485
3685
|
} finally {
|
|
3486
3686
|
try {
|
|
3487
3687
|
await this.deps.removeFile(concatPath);
|
|
@@ -3493,10 +3693,28 @@ var ExportEngine = class {
|
|
|
3493
3693
|
exportId: id,
|
|
3494
3694
|
args
|
|
3495
3695
|
} });
|
|
3696
|
+
const stallMs = this.deps.stallMs ?? 9e4;
|
|
3496
3697
|
return new Promise((resolve) => {
|
|
3497
3698
|
const proc = this.deps.spawn(this.deps.ffmpegPath ?? "ffmpeg", args);
|
|
3498
|
-
this.
|
|
3699
|
+
this.active.set(id, proc);
|
|
3499
3700
|
const stderrTail = [];
|
|
3701
|
+
let stallTimer = null;
|
|
3702
|
+
const clearStall = () => {
|
|
3703
|
+
if (stallTimer !== null) {
|
|
3704
|
+
clearTimeout(stallTimer);
|
|
3705
|
+
stallTimer = null;
|
|
3706
|
+
}
|
|
3707
|
+
};
|
|
3708
|
+
const armStall = () => {
|
|
3709
|
+
clearStall();
|
|
3710
|
+
stallTimer = setTimeout(() => {
|
|
3711
|
+
stderrTail.push(`ffmpeg stalled: no progress for ${stallMs}ms`);
|
|
3712
|
+
try {
|
|
3713
|
+
proc.kill("SIGKILL");
|
|
3714
|
+
} catch {}
|
|
3715
|
+
}, stallMs);
|
|
3716
|
+
};
|
|
3717
|
+
armStall();
|
|
3500
3718
|
proc.stderr?.on("data", (chunk) => {
|
|
3501
3719
|
const text = chunk instanceof Buffer ? chunk.toString("utf8") : String(chunk);
|
|
3502
3720
|
for (const line of text.split("\n")) {
|
|
@@ -3507,6 +3725,7 @@ var ExportEngine = class {
|
|
|
3507
3725
|
}
|
|
3508
3726
|
const m = TIME_RE.exec(line);
|
|
3509
3727
|
if (m) {
|
|
3728
|
+
armStall();
|
|
3510
3729
|
const sec = Number(m[1]) * 3600 + Number(m[2]) * 60 + Number(m[3]);
|
|
3511
3730
|
const span = progress.ceil - progress.floor;
|
|
3512
3731
|
const pct = Math.min(progress.ceil, progress.floor + Math.round(sec / progress.totalSec * span));
|
|
@@ -3519,14 +3738,16 @@ var ExportEngine = class {
|
|
|
3519
3738
|
}
|
|
3520
3739
|
});
|
|
3521
3740
|
proc.on("exit", (code) => {
|
|
3522
|
-
|
|
3741
|
+
clearStall();
|
|
3742
|
+
this.active.set(id, null);
|
|
3523
3743
|
resolve({
|
|
3524
3744
|
code: typeof code === "number" ? code : null,
|
|
3525
3745
|
stderrTail
|
|
3526
3746
|
});
|
|
3527
3747
|
});
|
|
3528
3748
|
proc.on("error", (raw) => {
|
|
3529
|
-
|
|
3749
|
+
clearStall();
|
|
3750
|
+
this.active.set(id, null);
|
|
3530
3751
|
const failure = readSpawnFailure(raw);
|
|
3531
3752
|
this.deps.logger.error("export ffmpeg could not be spawned", {
|
|
3532
3753
|
tags: { deviceId },
|
|
@@ -4811,6 +5032,10 @@ async function writeExportSourcePlaylist(input) {
|
|
|
4811
5032
|
segments: segments.map((s) => ({
|
|
4812
5033
|
startMs: s.startMs,
|
|
4813
5034
|
durMs: s.durMs
|
|
5035
|
+
})),
|
|
5036
|
+
concatEntries: segments.map((s) => ({
|
|
5037
|
+
uri: s.uri,
|
|
5038
|
+
durMs: s.durMs
|
|
4814
5039
|
}))
|
|
4815
5040
|
};
|
|
4816
5041
|
}
|
|
@@ -10764,6 +10989,10 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
10764
10989
|
});
|
|
10765
10990
|
},
|
|
10766
10991
|
cleanupPlaylist: removeExportFile,
|
|
10992
|
+
writeTextFile: async (p, contents) => {
|
|
10993
|
+
await node_fs.promises.mkdir(node_path.default.dirname(p), { recursive: true });
|
|
10994
|
+
await node_fs.promises.writeFile(p, contents, "utf8");
|
|
10995
|
+
},
|
|
10767
10996
|
outPath: exportOutPath,
|
|
10768
10997
|
statBytes: async (p) => (await node_fs.promises.stat(p)).size,
|
|
10769
10998
|
removeFile: removeExportFile,
|