@camstack/addon-pipeline 1.2.47 → 1.2.48
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/audio-analyzer/index.js +1 -1
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +2 -2
- package/dist/detection-pipeline/index.mjs +2 -2
- package/dist/{dist-C9Q1t0nT.js → dist-CksKE1Sn.js} +1262 -1283
- package/dist/{dist-CfxC7XoI.mjs → dist-DoTw0NM3.mjs} +1262 -1283
- package/dist/{event-loop-stall-monitor-B_XUPTwp.mjs → event-loop-stall-monitor-CtktCf3u.mjs} +1 -1
- package/dist/{event-loop-stall-monitor-Luq9AX4t.js → event-loop-stall-monitor-DzdwBr4X.js} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +3 -3
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/session-decode/decode-worker-child.js +1 -1
- package/dist/session-decode/decode-worker-child.mjs +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-SKj0Zdz2.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BNISghj9.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Cj2xMupA.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-Czrsozsg.mjs +26 -0
- package/dist/stream-broker/{hostInit-D8gyxL0j.mjs → hostInit-DqD1qq8w.mjs} +2 -2
- package/dist/stream-broker/index.js +196 -130
- package/dist/stream-broker/index.mjs +196 -130
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-rqbyKlm9.mjs → worker-protocol-4_4L1QOe.mjs} +1 -1
- package/dist/{worker-protocol-BUJpm7l3.js → worker-protocol-CuEcTi66.js} +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-DwXIyaHw.js → MaskShapeCanvas-DI4BY7W2-EcWZ5m8e.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-Cf8EtWEG.js → MotionZonesSettings-NcxxQN8r-CfiyCD3z.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BvsVXya9.js → PrivacyMaskSettings-APgPLF7p-ESPy4XXk.js} +1 -1
- package/embed-dist/assets/index-C3FKV1x-.js +112 -0
- package/embed-dist/index.html +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-KpixNEma.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-rd7UaqfV.mjs +0 -26
- package/embed-dist/assets/index-BuAeIGv8.js +0 -112
|
@@ -3,7 +3,7 @@ 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-
|
|
6
|
+
const require_dist = require("../dist-CksKE1Sn.js");
|
|
7
7
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
8
8
|
const require_model_download_service_D8B_4ktF = require("../model-download-service-D8B-4ktF-7-YxE9Wx.js");
|
|
9
9
|
let node_crypto = require("node:crypto");
|
|
@@ -5167,127 +5167,6 @@ function defaultId() {
|
|
|
5167
5167
|
counter += 1;
|
|
5168
5168
|
return `${Date.now().toString(36)}${counter.toString(36)}`;
|
|
5169
5169
|
}
|
|
5170
|
-
/** A derive that has not finished by now will not finish inside the drain. */
|
|
5171
|
-
var GIF_RENDER_TIMEOUT_MS = 2e4;
|
|
5172
|
-
/**
|
|
5173
|
-
* The filter chain. Extracted so a test can assert the palette pair without
|
|
5174
|
-
* spawning ffmpeg — an argv test cannot see quality, but it can see a filter
|
|
5175
|
-
* that somebody deleted.
|
|
5176
|
-
*/
|
|
5177
|
-
function gifFilterComplex(input) {
|
|
5178
|
-
const setpts = input.speed !== 1 ? `,setpts=PTS/${input.speed}` : "";
|
|
5179
|
-
return `[0:v]fps=${input.fps}${setpts},scale=${input.maxWidth}:-2:flags=lanczos,split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`;
|
|
5180
|
-
}
|
|
5181
|
-
/** The full argv, for the same reason. */
|
|
5182
|
-
function gifFromMp4Args(input) {
|
|
5183
|
-
return [
|
|
5184
|
-
"-y",
|
|
5185
|
-
"-v",
|
|
5186
|
-
"error",
|
|
5187
|
-
"-f",
|
|
5188
|
-
"mp4",
|
|
5189
|
-
"-i",
|
|
5190
|
-
"pipe:0",
|
|
5191
|
-
"-filter_complex",
|
|
5192
|
-
gifFilterComplex(input),
|
|
5193
|
-
"-loop",
|
|
5194
|
-
"0",
|
|
5195
|
-
"-f",
|
|
5196
|
-
"gif",
|
|
5197
|
-
"pipe:1"
|
|
5198
|
-
];
|
|
5199
|
-
}
|
|
5200
|
-
/**
|
|
5201
|
-
* Derive the gif. `null` on every failure — a notification that lost its gif is
|
|
5202
|
-
* a notification, and one that failed is not.
|
|
5203
|
-
*
|
|
5204
|
-
* The child is guaranteed dead before this settles: these run on every
|
|
5205
|
-
* notification that asks for a gif, and a leaked ffmpeg accumulates on the node
|
|
5206
|
-
* until somebody notices the host is slow.
|
|
5207
|
-
*/
|
|
5208
|
-
async function gifFromMp4(input) {
|
|
5209
|
-
const args = gifFromMp4Args({
|
|
5210
|
-
maxWidth: input.maxWidth ?? 640,
|
|
5211
|
-
fps: input.fps ?? 8,
|
|
5212
|
-
speed: input.speed ?? 1
|
|
5213
|
-
});
|
|
5214
|
-
const startedAt = Date.now();
|
|
5215
|
-
try {
|
|
5216
|
-
const bytes = await runGif(input, args);
|
|
5217
|
-
input.logger?.info("nc gif: derived from the notification video", {
|
|
5218
|
-
tags: { deviceId: input.deviceId },
|
|
5219
|
-
meta: {
|
|
5220
|
-
sourceBytes: input.mp4.length,
|
|
5221
|
-
gifBytes: bytes.length,
|
|
5222
|
-
width: input.maxWidth ?? 640,
|
|
5223
|
-
fps: input.fps ?? 8,
|
|
5224
|
-
encodeMs: Date.now() - startedAt
|
|
5225
|
-
}
|
|
5226
|
-
});
|
|
5227
|
-
return bytes;
|
|
5228
|
-
} catch (err) {
|
|
5229
|
-
input.logger?.warn("nc gif: the derive failed — this notification ships without a gif", {
|
|
5230
|
-
tags: { deviceId: input.deviceId },
|
|
5231
|
-
meta: {
|
|
5232
|
-
sourceBytes: input.mp4.length,
|
|
5233
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5234
|
-
}
|
|
5235
|
-
});
|
|
5236
|
-
return null;
|
|
5237
|
-
}
|
|
5238
|
-
}
|
|
5239
|
-
function runGif(input, args) {
|
|
5240
|
-
return new Promise((resolve, reject) => {
|
|
5241
|
-
const proc = input.spawnFn(input.ffmpegPath ?? "ffmpeg", [...args], { stdio: [
|
|
5242
|
-
"pipe",
|
|
5243
|
-
"pipe",
|
|
5244
|
-
"pipe"
|
|
5245
|
-
] });
|
|
5246
|
-
const chunks = [];
|
|
5247
|
-
let stderrTail = "";
|
|
5248
|
-
let done = false;
|
|
5249
|
-
let timer = null;
|
|
5250
|
-
const settle = (err, out) => {
|
|
5251
|
-
if (done) return;
|
|
5252
|
-
done = true;
|
|
5253
|
-
if (timer !== null) clearTimeout(timer);
|
|
5254
|
-
if (proc.exitCode === null && proc.signalCode === null) try {
|
|
5255
|
-
proc.kill("SIGKILL");
|
|
5256
|
-
} catch {}
|
|
5257
|
-
try {
|
|
5258
|
-
proc.stdout?.removeAllListeners();
|
|
5259
|
-
proc.stderr?.removeAllListeners();
|
|
5260
|
-
proc.stdin?.destroy();
|
|
5261
|
-
} catch {}
|
|
5262
|
-
if (err !== null) reject(err);
|
|
5263
|
-
else resolve(out ?? new Uint8Array(0));
|
|
5264
|
-
};
|
|
5265
|
-
timer = setTimeout(() => settle(/* @__PURE__ */ new Error("gif derive timed out")), GIF_RENDER_TIMEOUT_MS);
|
|
5266
|
-
timer.unref?.();
|
|
5267
|
-
proc.stdout?.on("data", (c) => chunks.push(c));
|
|
5268
|
-
proc.stderr?.on("data", (c) => {
|
|
5269
|
-
stderrTail = (stderrTail + c.toString()).slice(-400);
|
|
5270
|
-
});
|
|
5271
|
-
proc.on("error", (err) => settle(err instanceof Error ? err : new Error(String(err))));
|
|
5272
|
-
proc.on("close", (code) => {
|
|
5273
|
-
if (code !== 0) {
|
|
5274
|
-
settle(/* @__PURE__ */ new Error(`ffmpeg gif derive exited ${code}: ${stderrTail}`));
|
|
5275
|
-
return;
|
|
5276
|
-
}
|
|
5277
|
-
const out = Buffer.concat(chunks);
|
|
5278
|
-
if (out.byteLength === 0) {
|
|
5279
|
-
settle(/* @__PURE__ */ new Error("gif derive produced no bytes"));
|
|
5280
|
-
return;
|
|
5281
|
-
}
|
|
5282
|
-
const bytes = new Uint8Array(out.byteLength);
|
|
5283
|
-
bytes.set(out);
|
|
5284
|
-
settle(null, bytes);
|
|
5285
|
-
});
|
|
5286
|
-
proc.stdin?.on("error", () => {});
|
|
5287
|
-
proc.stdin?.write(Buffer.from(input.mp4));
|
|
5288
|
-
proc.stdin?.end();
|
|
5289
|
-
});
|
|
5290
|
-
}
|
|
5291
5170
|
//#endregion
|
|
5292
5171
|
//#region src/stream-broker/webrtc/nal-utils.ts
|
|
5293
5172
|
var NAL_START_CODE_4B = Buffer.from([
|
|
@@ -5968,6 +5847,41 @@ function toElementaryStream(window, sdpParameterSets) {
|
|
|
5968
5847
|
}
|
|
5969
5848
|
var RENDER_TIMEOUT_MS = 2e4;
|
|
5970
5849
|
/**
|
|
5850
|
+
* The widest an event clip is RE-ENCODED at — 720p.
|
|
5851
|
+
*
|
|
5852
|
+
* Only ever reached when the render already forced a decode (a speed other than
|
|
5853
|
+
* real time, or an H.265 source). At real time the width must stay the source's
|
|
5854
|
+
* own, because an identity scale is precisely what `clipCanCopy` looks for.
|
|
5855
|
+
*
|
|
5856
|
+
* Measured on a real 615 720p window, `-preset veryfast`: crf 23 at 1280 is
|
|
5857
|
+
* 254 KB and 0.23 s of encode, against the 922 KB the copy of the same window
|
|
5858
|
+
* carried. Capping wider renditions here costs nothing an operator can see on a
|
|
5859
|
+
* phone and keeps the burst bounded on a hub that is already busy.
|
|
5860
|
+
*/
|
|
5861
|
+
var EVENT_CLIP_ENCODE_MAX_WIDTH = 1280;
|
|
5862
|
+
/**
|
|
5863
|
+
* The width to render an event clip at, given what the source is and whether
|
|
5864
|
+
* the caller asked for real time.
|
|
5865
|
+
*
|
|
5866
|
+
* One function because two call sites have to agree: the mux and the
|
|
5867
|
+
* `copy`/`encode` the production REPORTS. They disagreed once already — a
|
|
5868
|
+
* reported mode that does not match the render is a log line that lies.
|
|
5869
|
+
*/
|
|
5870
|
+
function eventClipEncodeWidth(sourceWidth, speed) {
|
|
5871
|
+
const source = Math.max(1, Math.round(sourceWidth));
|
|
5872
|
+
if (speed === 1) return source;
|
|
5873
|
+
return Math.min(source, EVENT_CLIP_ENCODE_MAX_WIDTH);
|
|
5874
|
+
}
|
|
5875
|
+
/**
|
|
5876
|
+
* Format a frame rate for an ffmpeg filter argument.
|
|
5877
|
+
*
|
|
5878
|
+
* `12 / 4` must reach the filter as `3` and `15 / 4` as `3.75`, not as a float
|
|
5879
|
+
* tail that `av_parse_video_rate` then has to guess at.
|
|
5880
|
+
*/
|
|
5881
|
+
function ffmpegRate(rate) {
|
|
5882
|
+
return String(Math.round(rate * 1e3) / 1e3);
|
|
5883
|
+
}
|
|
5884
|
+
/**
|
|
5971
5885
|
* Whether this render can COPY the camera's own H.264 instead of re-encoding it.
|
|
5972
5886
|
*
|
|
5973
5887
|
* The ring already holds the camera's elementary stream. Re-encoding it is only
|
|
@@ -6003,6 +5917,7 @@ function clipFfmpegArgs(input) {
|
|
|
6003
5917
|
const demuxer = input.hevc === true ? "hevc" : "h264";
|
|
6004
5918
|
const speed = input.speed ?? 2;
|
|
6005
5919
|
const setpts = speed !== 1 ? `,setpts=PTS/${speed}` : "";
|
|
5920
|
+
const gifSetpts = speed !== 1 ? `${setpts},fps=${ffmpegRate(input.fps * speed)}` : setpts;
|
|
6006
5921
|
if (input.format === "gif") return [
|
|
6007
5922
|
"-y",
|
|
6008
5923
|
"-f",
|
|
@@ -6010,7 +5925,7 @@ function clipFfmpegArgs(input) {
|
|
|
6010
5925
|
"-i",
|
|
6011
5926
|
"pipe:0",
|
|
6012
5927
|
"-filter_complex",
|
|
6013
|
-
`[0:v]fps=${input.fps}${
|
|
5928
|
+
`[0:v]fps=${ffmpegRate(input.fps)}${gifSetpts},${scale},split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`,
|
|
6014
5929
|
"-loop",
|
|
6015
5930
|
"0",
|
|
6016
5931
|
"-f",
|
|
@@ -6045,7 +5960,7 @@ function clipFfmpegArgs(input) {
|
|
|
6045
5960
|
"-preset",
|
|
6046
5961
|
"veryfast",
|
|
6047
5962
|
"-crf",
|
|
6048
|
-
"
|
|
5963
|
+
"23",
|
|
6049
5964
|
"-pix_fmt",
|
|
6050
5965
|
"yuv420p",
|
|
6051
5966
|
"-movflags",
|
|
@@ -6121,6 +6036,142 @@ async function renderPreBufferClip(input) {
|
|
|
6121
6036
|
proc.stdin?.end();
|
|
6122
6037
|
});
|
|
6123
6038
|
}
|
|
6039
|
+
/**
|
|
6040
|
+
* The factor the derive must apply ON TOP of the speed the mp4 already carries.
|
|
6041
|
+
*
|
|
6042
|
+
* The gif is derived from the muxed mp4, so if that mux already ran `setpts` at
|
|
6043
|
+
* the caller's `speed`, applying the absolute gif rate again would compound
|
|
6044
|
+
* them — a 4× gif beside a 2× video would come out at 8×. Discounting here is
|
|
6045
|
+
* what keeps {@link GIF_SPEED} an ABSOLUTE rate against real time whatever the
|
|
6046
|
+
* video does.
|
|
6047
|
+
*/
|
|
6048
|
+
function gifDeriveSpeed(gifSpeed, videoSpeed) {
|
|
6049
|
+
if (!Number.isFinite(videoSpeed) || videoSpeed <= 0) return gifSpeed;
|
|
6050
|
+
return gifSpeed / videoSpeed;
|
|
6051
|
+
}
|
|
6052
|
+
/** A derive that has not finished by now will not finish inside the drain. */
|
|
6053
|
+
var GIF_RENDER_TIMEOUT_MS = 2e4;
|
|
6054
|
+
/**
|
|
6055
|
+
* The filter chain. Extracted so a test can assert the palette pair without
|
|
6056
|
+
* spawning ffmpeg — an argv test cannot see quality, but it can see a filter
|
|
6057
|
+
* that somebody deleted.
|
|
6058
|
+
*/
|
|
6059
|
+
function gifFilterComplex(input) {
|
|
6060
|
+
const speed = input.speed > 0 ? input.speed : 1;
|
|
6061
|
+
if (speed === 1) return `[0:v]fps=${ffmpegRate(input.fps)},scale=${input.maxWidth}:-2:flags=lanczos,split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`;
|
|
6062
|
+
return `[0:v]fps=${ffmpegRate(input.fps / speed)},setpts=PTS/${speed},fps=${ffmpegRate(input.fps)},scale=${input.maxWidth}:-2:flags=lanczos,split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`;
|
|
6063
|
+
}
|
|
6064
|
+
/** The full argv, for the same reason. */
|
|
6065
|
+
function gifFromMp4Args(input) {
|
|
6066
|
+
return [
|
|
6067
|
+
"-y",
|
|
6068
|
+
"-v",
|
|
6069
|
+
"error",
|
|
6070
|
+
"-f",
|
|
6071
|
+
"mp4",
|
|
6072
|
+
"-i",
|
|
6073
|
+
"pipe:0",
|
|
6074
|
+
"-filter_complex",
|
|
6075
|
+
gifFilterComplex(input),
|
|
6076
|
+
"-loop",
|
|
6077
|
+
"0",
|
|
6078
|
+
"-f",
|
|
6079
|
+
"gif",
|
|
6080
|
+
"pipe:1"
|
|
6081
|
+
];
|
|
6082
|
+
}
|
|
6083
|
+
/**
|
|
6084
|
+
* Derive the gif. `null` on every failure — a notification that lost its gif is
|
|
6085
|
+
* a notification, and one that failed is not.
|
|
6086
|
+
*
|
|
6087
|
+
* The child is guaranteed dead before this settles: these run on every
|
|
6088
|
+
* notification that asks for a gif, and a leaked ffmpeg accumulates on the node
|
|
6089
|
+
* until somebody notices the host is slow.
|
|
6090
|
+
*/
|
|
6091
|
+
async function gifFromMp4(input) {
|
|
6092
|
+
const args = gifFromMp4Args({
|
|
6093
|
+
maxWidth: input.maxWidth ?? 640,
|
|
6094
|
+
fps: input.fps ?? 12,
|
|
6095
|
+
speed: input.speed ?? 4
|
|
6096
|
+
});
|
|
6097
|
+
const startedAt = Date.now();
|
|
6098
|
+
try {
|
|
6099
|
+
const bytes = await runGif(input, args);
|
|
6100
|
+
input.logger?.info("nc gif: derived from the notification video", {
|
|
6101
|
+
tags: { deviceId: input.deviceId },
|
|
6102
|
+
meta: {
|
|
6103
|
+
sourceBytes: input.mp4.length,
|
|
6104
|
+
gifBytes: bytes.length,
|
|
6105
|
+
width: input.maxWidth ?? 640,
|
|
6106
|
+
fps: input.fps ?? 12,
|
|
6107
|
+
speed: input.speed ?? 4,
|
|
6108
|
+
encodeMs: Date.now() - startedAt
|
|
6109
|
+
}
|
|
6110
|
+
});
|
|
6111
|
+
return bytes;
|
|
6112
|
+
} catch (err) {
|
|
6113
|
+
input.logger?.warn("nc gif: the derive failed — this notification ships without a gif", {
|
|
6114
|
+
tags: { deviceId: input.deviceId },
|
|
6115
|
+
meta: {
|
|
6116
|
+
sourceBytes: input.mp4.length,
|
|
6117
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6118
|
+
}
|
|
6119
|
+
});
|
|
6120
|
+
return null;
|
|
6121
|
+
}
|
|
6122
|
+
}
|
|
6123
|
+
function runGif(input, args) {
|
|
6124
|
+
return new Promise((resolve, reject) => {
|
|
6125
|
+
const proc = input.spawnFn(input.ffmpegPath ?? "ffmpeg", [...args], { stdio: [
|
|
6126
|
+
"pipe",
|
|
6127
|
+
"pipe",
|
|
6128
|
+
"pipe"
|
|
6129
|
+
] });
|
|
6130
|
+
const chunks = [];
|
|
6131
|
+
let stderrTail = "";
|
|
6132
|
+
let done = false;
|
|
6133
|
+
let timer = null;
|
|
6134
|
+
const settle = (err, out) => {
|
|
6135
|
+
if (done) return;
|
|
6136
|
+
done = true;
|
|
6137
|
+
if (timer !== null) clearTimeout(timer);
|
|
6138
|
+
if (proc.exitCode === null && proc.signalCode === null) try {
|
|
6139
|
+
proc.kill("SIGKILL");
|
|
6140
|
+
} catch {}
|
|
6141
|
+
try {
|
|
6142
|
+
proc.stdout?.removeAllListeners();
|
|
6143
|
+
proc.stderr?.removeAllListeners();
|
|
6144
|
+
proc.stdin?.destroy();
|
|
6145
|
+
} catch {}
|
|
6146
|
+
if (err !== null) reject(err);
|
|
6147
|
+
else resolve(out ?? new Uint8Array(0));
|
|
6148
|
+
};
|
|
6149
|
+
timer = setTimeout(() => settle(/* @__PURE__ */ new Error("gif derive timed out")), GIF_RENDER_TIMEOUT_MS);
|
|
6150
|
+
timer.unref?.();
|
|
6151
|
+
proc.stdout?.on("data", (c) => chunks.push(c));
|
|
6152
|
+
proc.stderr?.on("data", (c) => {
|
|
6153
|
+
stderrTail = (stderrTail + c.toString()).slice(-400);
|
|
6154
|
+
});
|
|
6155
|
+
proc.on("error", (err) => settle(err instanceof Error ? err : new Error(String(err))));
|
|
6156
|
+
proc.on("close", (code) => {
|
|
6157
|
+
if (code !== 0) {
|
|
6158
|
+
settle(/* @__PURE__ */ new Error(`ffmpeg gif derive exited ${code}: ${stderrTail}`));
|
|
6159
|
+
return;
|
|
6160
|
+
}
|
|
6161
|
+
const out = Buffer.concat(chunks);
|
|
6162
|
+
if (out.byteLength === 0) {
|
|
6163
|
+
settle(/* @__PURE__ */ new Error("gif derive produced no bytes"));
|
|
6164
|
+
return;
|
|
6165
|
+
}
|
|
6166
|
+
const bytes = new Uint8Array(out.byteLength);
|
|
6167
|
+
bytes.set(out);
|
|
6168
|
+
settle(null, bytes);
|
|
6169
|
+
});
|
|
6170
|
+
proc.stdin?.on("error", () => {});
|
|
6171
|
+
proc.stdin?.write(Buffer.from(input.mp4));
|
|
6172
|
+
proc.stdin?.end();
|
|
6173
|
+
});
|
|
6174
|
+
}
|
|
6124
6175
|
//#endregion
|
|
6125
6176
|
//#region src/stream-broker/stream-broker/profile-restreamers.ts
|
|
6126
6177
|
/**
|
|
@@ -15251,9 +15302,10 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15251
15302
|
postRollSec: input.postSeconds,
|
|
15252
15303
|
...preferred !== void 0 ? { profile: preferred } : {}
|
|
15253
15304
|
});
|
|
15305
|
+
const clipWidth = eventClipEncodeWidth(cut.sourceWidth, input.speed);
|
|
15254
15306
|
const mp4 = await this.muxClipWindow(cut, {
|
|
15255
15307
|
format: "mp4",
|
|
15256
|
-
maxWidth:
|
|
15308
|
+
maxWidth: clipWidth,
|
|
15257
15309
|
fps: 0,
|
|
15258
15310
|
speed: input.speed
|
|
15259
15311
|
});
|
|
@@ -15269,22 +15321,23 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15269
15321
|
nodeId: this.localNodeId ?? "",
|
|
15270
15322
|
mime: "video/mp4",
|
|
15271
15323
|
bytes: mp4.byteLength,
|
|
15272
|
-
width:
|
|
15273
|
-
height: cut
|
|
15324
|
+
width: clipWidth,
|
|
15325
|
+
height: scaledHeight(cut, clipWidth)
|
|
15274
15326
|
});
|
|
15327
|
+
const gifSpeed = input.gifSpeed ?? 4;
|
|
15275
15328
|
if (input.kinds.includes("gif")) {
|
|
15276
15329
|
const gif = await gifFromMp4({
|
|
15277
15330
|
mp4,
|
|
15278
15331
|
deviceId: input.deviceId,
|
|
15279
15332
|
maxWidth: input.gifMaxWidth,
|
|
15280
15333
|
fps: input.gifFps,
|
|
15281
|
-
speed:
|
|
15334
|
+
speed: gifDeriveSpeed(gifSpeed, input.speed),
|
|
15282
15335
|
...this.logger !== void 0 ? { logger: this.logger } : {},
|
|
15283
15336
|
spawnFn: node_child_process.spawn,
|
|
15284
15337
|
...this.ffmpegConfig.binaryPath !== void 0 ? { ffmpegPath: this.ffmpegConfig.binaryPath } : {}
|
|
15285
15338
|
});
|
|
15286
15339
|
if (gif !== null) {
|
|
15287
|
-
const height = cut
|
|
15340
|
+
const height = scaledHeight(cut, input.gifMaxWidth);
|
|
15288
15341
|
media.push({
|
|
15289
15342
|
kind: "gif",
|
|
15290
15343
|
handle: this.eventMedia.put({
|
|
@@ -15303,7 +15356,7 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15303
15356
|
}
|
|
15304
15357
|
const video = this.clipVideoMode(cut, {
|
|
15305
15358
|
format: "mp4",
|
|
15306
|
-
maxWidth:
|
|
15359
|
+
maxWidth: clipWidth,
|
|
15307
15360
|
speed: input.speed
|
|
15308
15361
|
});
|
|
15309
15362
|
this.logger?.info("event media produced — one window, every container", {
|
|
@@ -15315,6 +15368,8 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15315
15368
|
packets: cut.window.length,
|
|
15316
15369
|
kinds: [...input.kinds].join(","),
|
|
15317
15370
|
artifacts: media.map((m) => `${m.kind}:${m.bytes}`).join(" "),
|
|
15371
|
+
speed: input.speed,
|
|
15372
|
+
gifSpeed,
|
|
15318
15373
|
startOffsetMs: cut.coverage.fromTs - input.aroundMs,
|
|
15319
15374
|
endOffsetMs: cut.coverage.toTs - input.aroundMs
|
|
15320
15375
|
}
|
|
@@ -17572,6 +17627,17 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
17572
17627
|
}
|
|
17573
17628
|
}
|
|
17574
17629
|
};
|
|
17630
|
+
/**
|
|
17631
|
+
* The height a render lands at, once `scale=W:-2` has picked it.
|
|
17632
|
+
*
|
|
17633
|
+
* `-2` means "keep the aspect, round to an EVEN number" — libx264 rejects an
|
|
17634
|
+
* odd dimension in yuv420p. Reporting the arithmetic without the rounding is
|
|
17635
|
+
* how an artifact ends up one pixel taller than the file it describes.
|
|
17636
|
+
*/
|
|
17637
|
+
function scaledHeight(cut, width) {
|
|
17638
|
+
if (cut.sourceWidth <= 0 || cut.sourceHeight <= 0) return 0;
|
|
17639
|
+
return 2 * Math.round(width / cut.sourceWidth * cut.sourceHeight / 2);
|
|
17640
|
+
}
|
|
17575
17641
|
//#endregion
|
|
17576
17642
|
//#region src/stream-broker/encode-profile-resolver.ts
|
|
17577
17643
|
/**
|