@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as __toESM, t as __commonJSMin } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { $ as errMsg, At as EventCategory, Dt as record, E as createHwAccelCache, Et as object, N as egressTranscodeSharingKey, Ot as string, P as egressTransportFromRequest, Q as webrtcSessionCapability, S as addonWidgetsSourceCapability, St as discriminatedUnion, T as cameraStreamsCapability, Tt as number, X as streamBrokerCapability, _ as RATE_CONTROL_RELAXED, at as BaseAddon, b as RingBuffer, bt as array, ct as DeviceType, d as EncodeProfileSchema, dt as isEvent, et as AUDIO_PRESETS, ft as makeProfileBrokerId, gt as parseProfileBrokerId, it as isSoftwareDecode, kt as union, lt as createEvent, mt as nodePin, nt as buildFfmpegArgs$1, ot as CAM_PROFILE_ORDER, pt as makeSourceBrokerId, q as resolveEgressDecodeHwAccel, rt as invocationFromEncodeProfile, st as DeviceFeature, tt as Fmp4BoxSplitter, v as RATE_CONTROL_TIGHT, wt as literal, xt as boolean, yt as _enum, z as maskUrlCredentials } from "../dist-
|
|
2
|
+
import { $ as errMsg, At as EventCategory, Dt as record, E as createHwAccelCache, Et as object, N as egressTranscodeSharingKey, Ot as string, P as egressTransportFromRequest, Q as webrtcSessionCapability, S as addonWidgetsSourceCapability, St as discriminatedUnion, T as cameraStreamsCapability, Tt as number, X as streamBrokerCapability, _ as RATE_CONTROL_RELAXED, at as BaseAddon, b as RingBuffer, bt as array, ct as DeviceType, d as EncodeProfileSchema, dt as isEvent, et as AUDIO_PRESETS, ft as makeProfileBrokerId, gt as parseProfileBrokerId, it as isSoftwareDecode, kt as union, lt as createEvent, mt as nodePin, nt as buildFfmpegArgs$1, ot as CAM_PROFILE_ORDER, pt as makeSourceBrokerId, q as resolveEgressDecodeHwAccel, rt as invocationFromEncodeProfile, st as DeviceFeature, tt as Fmp4BoxSplitter, v as RATE_CONTROL_TIGHT, wt as literal, xt as boolean, yt as _enum, z as maskUrlCredentials } from "../dist-DoTw0NM3.mjs";
|
|
3
3
|
import { n as profileForStreamId } from "../remote-restream-BeHi78PZ.mjs";
|
|
4
4
|
import { n as createFileDataPlaneHandler } from "../model-download-service-D8B-4ktF-BB7oZL3y.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
@@ -5161,127 +5161,6 @@ function defaultId() {
|
|
|
5161
5161
|
counter += 1;
|
|
5162
5162
|
return `${Date.now().toString(36)}${counter.toString(36)}`;
|
|
5163
5163
|
}
|
|
5164
|
-
/** A derive that has not finished by now will not finish inside the drain. */
|
|
5165
|
-
var GIF_RENDER_TIMEOUT_MS = 2e4;
|
|
5166
|
-
/**
|
|
5167
|
-
* The filter chain. Extracted so a test can assert the palette pair without
|
|
5168
|
-
* spawning ffmpeg — an argv test cannot see quality, but it can see a filter
|
|
5169
|
-
* that somebody deleted.
|
|
5170
|
-
*/
|
|
5171
|
-
function gifFilterComplex(input) {
|
|
5172
|
-
const setpts = input.speed !== 1 ? `,setpts=PTS/${input.speed}` : "";
|
|
5173
|
-
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`;
|
|
5174
|
-
}
|
|
5175
|
-
/** The full argv, for the same reason. */
|
|
5176
|
-
function gifFromMp4Args(input) {
|
|
5177
|
-
return [
|
|
5178
|
-
"-y",
|
|
5179
|
-
"-v",
|
|
5180
|
-
"error",
|
|
5181
|
-
"-f",
|
|
5182
|
-
"mp4",
|
|
5183
|
-
"-i",
|
|
5184
|
-
"pipe:0",
|
|
5185
|
-
"-filter_complex",
|
|
5186
|
-
gifFilterComplex(input),
|
|
5187
|
-
"-loop",
|
|
5188
|
-
"0",
|
|
5189
|
-
"-f",
|
|
5190
|
-
"gif",
|
|
5191
|
-
"pipe:1"
|
|
5192
|
-
];
|
|
5193
|
-
}
|
|
5194
|
-
/**
|
|
5195
|
-
* Derive the gif. `null` on every failure — a notification that lost its gif is
|
|
5196
|
-
* a notification, and one that failed is not.
|
|
5197
|
-
*
|
|
5198
|
-
* The child is guaranteed dead before this settles: these run on every
|
|
5199
|
-
* notification that asks for a gif, and a leaked ffmpeg accumulates on the node
|
|
5200
|
-
* until somebody notices the host is slow.
|
|
5201
|
-
*/
|
|
5202
|
-
async function gifFromMp4(input) {
|
|
5203
|
-
const args = gifFromMp4Args({
|
|
5204
|
-
maxWidth: input.maxWidth ?? 640,
|
|
5205
|
-
fps: input.fps ?? 8,
|
|
5206
|
-
speed: input.speed ?? 1
|
|
5207
|
-
});
|
|
5208
|
-
const startedAt = Date.now();
|
|
5209
|
-
try {
|
|
5210
|
-
const bytes = await runGif(input, args);
|
|
5211
|
-
input.logger?.info("nc gif: derived from the notification video", {
|
|
5212
|
-
tags: { deviceId: input.deviceId },
|
|
5213
|
-
meta: {
|
|
5214
|
-
sourceBytes: input.mp4.length,
|
|
5215
|
-
gifBytes: bytes.length,
|
|
5216
|
-
width: input.maxWidth ?? 640,
|
|
5217
|
-
fps: input.fps ?? 8,
|
|
5218
|
-
encodeMs: Date.now() - startedAt
|
|
5219
|
-
}
|
|
5220
|
-
});
|
|
5221
|
-
return bytes;
|
|
5222
|
-
} catch (err) {
|
|
5223
|
-
input.logger?.warn("nc gif: the derive failed — this notification ships without a gif", {
|
|
5224
|
-
tags: { deviceId: input.deviceId },
|
|
5225
|
-
meta: {
|
|
5226
|
-
sourceBytes: input.mp4.length,
|
|
5227
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5228
|
-
}
|
|
5229
|
-
});
|
|
5230
|
-
return null;
|
|
5231
|
-
}
|
|
5232
|
-
}
|
|
5233
|
-
function runGif(input, args) {
|
|
5234
|
-
return new Promise((resolve, reject) => {
|
|
5235
|
-
const proc = input.spawnFn(input.ffmpegPath ?? "ffmpeg", [...args], { stdio: [
|
|
5236
|
-
"pipe",
|
|
5237
|
-
"pipe",
|
|
5238
|
-
"pipe"
|
|
5239
|
-
] });
|
|
5240
|
-
const chunks = [];
|
|
5241
|
-
let stderrTail = "";
|
|
5242
|
-
let done = false;
|
|
5243
|
-
let timer = null;
|
|
5244
|
-
const settle = (err, out) => {
|
|
5245
|
-
if (done) return;
|
|
5246
|
-
done = true;
|
|
5247
|
-
if (timer !== null) clearTimeout(timer);
|
|
5248
|
-
if (proc.exitCode === null && proc.signalCode === null) try {
|
|
5249
|
-
proc.kill("SIGKILL");
|
|
5250
|
-
} catch {}
|
|
5251
|
-
try {
|
|
5252
|
-
proc.stdout?.removeAllListeners();
|
|
5253
|
-
proc.stderr?.removeAllListeners();
|
|
5254
|
-
proc.stdin?.destroy();
|
|
5255
|
-
} catch {}
|
|
5256
|
-
if (err !== null) reject(err);
|
|
5257
|
-
else resolve(out ?? new Uint8Array(0));
|
|
5258
|
-
};
|
|
5259
|
-
timer = setTimeout(() => settle(/* @__PURE__ */ new Error("gif derive timed out")), GIF_RENDER_TIMEOUT_MS);
|
|
5260
|
-
timer.unref?.();
|
|
5261
|
-
proc.stdout?.on("data", (c) => chunks.push(c));
|
|
5262
|
-
proc.stderr?.on("data", (c) => {
|
|
5263
|
-
stderrTail = (stderrTail + c.toString()).slice(-400);
|
|
5264
|
-
});
|
|
5265
|
-
proc.on("error", (err) => settle(err instanceof Error ? err : new Error(String(err))));
|
|
5266
|
-
proc.on("close", (code) => {
|
|
5267
|
-
if (code !== 0) {
|
|
5268
|
-
settle(/* @__PURE__ */ new Error(`ffmpeg gif derive exited ${code}: ${stderrTail}`));
|
|
5269
|
-
return;
|
|
5270
|
-
}
|
|
5271
|
-
const out = Buffer.concat(chunks);
|
|
5272
|
-
if (out.byteLength === 0) {
|
|
5273
|
-
settle(/* @__PURE__ */ new Error("gif derive produced no bytes"));
|
|
5274
|
-
return;
|
|
5275
|
-
}
|
|
5276
|
-
const bytes = new Uint8Array(out.byteLength);
|
|
5277
|
-
bytes.set(out);
|
|
5278
|
-
settle(null, bytes);
|
|
5279
|
-
});
|
|
5280
|
-
proc.stdin?.on("error", () => {});
|
|
5281
|
-
proc.stdin?.write(Buffer.from(input.mp4));
|
|
5282
|
-
proc.stdin?.end();
|
|
5283
|
-
});
|
|
5284
|
-
}
|
|
5285
5164
|
//#endregion
|
|
5286
5165
|
//#region src/stream-broker/webrtc/nal-utils.ts
|
|
5287
5166
|
var NAL_START_CODE_4B = Buffer.from([
|
|
@@ -5962,6 +5841,41 @@ function toElementaryStream(window, sdpParameterSets) {
|
|
|
5962
5841
|
}
|
|
5963
5842
|
var RENDER_TIMEOUT_MS = 2e4;
|
|
5964
5843
|
/**
|
|
5844
|
+
* The widest an event clip is RE-ENCODED at — 720p.
|
|
5845
|
+
*
|
|
5846
|
+
* Only ever reached when the render already forced a decode (a speed other than
|
|
5847
|
+
* real time, or an H.265 source). At real time the width must stay the source's
|
|
5848
|
+
* own, because an identity scale is precisely what `clipCanCopy` looks for.
|
|
5849
|
+
*
|
|
5850
|
+
* Measured on a real 615 720p window, `-preset veryfast`: crf 23 at 1280 is
|
|
5851
|
+
* 254 KB and 0.23 s of encode, against the 922 KB the copy of the same window
|
|
5852
|
+
* carried. Capping wider renditions here costs nothing an operator can see on a
|
|
5853
|
+
* phone and keeps the burst bounded on a hub that is already busy.
|
|
5854
|
+
*/
|
|
5855
|
+
var EVENT_CLIP_ENCODE_MAX_WIDTH = 1280;
|
|
5856
|
+
/**
|
|
5857
|
+
* The width to render an event clip at, given what the source is and whether
|
|
5858
|
+
* the caller asked for real time.
|
|
5859
|
+
*
|
|
5860
|
+
* One function because two call sites have to agree: the mux and the
|
|
5861
|
+
* `copy`/`encode` the production REPORTS. They disagreed once already — a
|
|
5862
|
+
* reported mode that does not match the render is a log line that lies.
|
|
5863
|
+
*/
|
|
5864
|
+
function eventClipEncodeWidth(sourceWidth, speed) {
|
|
5865
|
+
const source = Math.max(1, Math.round(sourceWidth));
|
|
5866
|
+
if (speed === 1) return source;
|
|
5867
|
+
return Math.min(source, EVENT_CLIP_ENCODE_MAX_WIDTH);
|
|
5868
|
+
}
|
|
5869
|
+
/**
|
|
5870
|
+
* Format a frame rate for an ffmpeg filter argument.
|
|
5871
|
+
*
|
|
5872
|
+
* `12 / 4` must reach the filter as `3` and `15 / 4` as `3.75`, not as a float
|
|
5873
|
+
* tail that `av_parse_video_rate` then has to guess at.
|
|
5874
|
+
*/
|
|
5875
|
+
function ffmpegRate(rate) {
|
|
5876
|
+
return String(Math.round(rate * 1e3) / 1e3);
|
|
5877
|
+
}
|
|
5878
|
+
/**
|
|
5965
5879
|
* Whether this render can COPY the camera's own H.264 instead of re-encoding it.
|
|
5966
5880
|
*
|
|
5967
5881
|
* The ring already holds the camera's elementary stream. Re-encoding it is only
|
|
@@ -5997,6 +5911,7 @@ function clipFfmpegArgs(input) {
|
|
|
5997
5911
|
const demuxer = input.hevc === true ? "hevc" : "h264";
|
|
5998
5912
|
const speed = input.speed ?? 2;
|
|
5999
5913
|
const setpts = speed !== 1 ? `,setpts=PTS/${speed}` : "";
|
|
5914
|
+
const gifSetpts = speed !== 1 ? `${setpts},fps=${ffmpegRate(input.fps * speed)}` : setpts;
|
|
6000
5915
|
if (input.format === "gif") return [
|
|
6001
5916
|
"-y",
|
|
6002
5917
|
"-f",
|
|
@@ -6004,7 +5919,7 @@ function clipFfmpegArgs(input) {
|
|
|
6004
5919
|
"-i",
|
|
6005
5920
|
"pipe:0",
|
|
6006
5921
|
"-filter_complex",
|
|
6007
|
-
`[0:v]fps=${input.fps}${
|
|
5922
|
+
`[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`,
|
|
6008
5923
|
"-loop",
|
|
6009
5924
|
"0",
|
|
6010
5925
|
"-f",
|
|
@@ -6039,7 +5954,7 @@ function clipFfmpegArgs(input) {
|
|
|
6039
5954
|
"-preset",
|
|
6040
5955
|
"veryfast",
|
|
6041
5956
|
"-crf",
|
|
6042
|
-
"
|
|
5957
|
+
"23",
|
|
6043
5958
|
"-pix_fmt",
|
|
6044
5959
|
"yuv420p",
|
|
6045
5960
|
"-movflags",
|
|
@@ -6115,6 +6030,142 @@ async function renderPreBufferClip(input) {
|
|
|
6115
6030
|
proc.stdin?.end();
|
|
6116
6031
|
});
|
|
6117
6032
|
}
|
|
6033
|
+
/**
|
|
6034
|
+
* The factor the derive must apply ON TOP of the speed the mp4 already carries.
|
|
6035
|
+
*
|
|
6036
|
+
* The gif is derived from the muxed mp4, so if that mux already ran `setpts` at
|
|
6037
|
+
* the caller's `speed`, applying the absolute gif rate again would compound
|
|
6038
|
+
* them — a 4× gif beside a 2× video would come out at 8×. Discounting here is
|
|
6039
|
+
* what keeps {@link GIF_SPEED} an ABSOLUTE rate against real time whatever the
|
|
6040
|
+
* video does.
|
|
6041
|
+
*/
|
|
6042
|
+
function gifDeriveSpeed(gifSpeed, videoSpeed) {
|
|
6043
|
+
if (!Number.isFinite(videoSpeed) || videoSpeed <= 0) return gifSpeed;
|
|
6044
|
+
return gifSpeed / videoSpeed;
|
|
6045
|
+
}
|
|
6046
|
+
/** A derive that has not finished by now will not finish inside the drain. */
|
|
6047
|
+
var GIF_RENDER_TIMEOUT_MS = 2e4;
|
|
6048
|
+
/**
|
|
6049
|
+
* The filter chain. Extracted so a test can assert the palette pair without
|
|
6050
|
+
* spawning ffmpeg — an argv test cannot see quality, but it can see a filter
|
|
6051
|
+
* that somebody deleted.
|
|
6052
|
+
*/
|
|
6053
|
+
function gifFilterComplex(input) {
|
|
6054
|
+
const speed = input.speed > 0 ? input.speed : 1;
|
|
6055
|
+
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`;
|
|
6056
|
+
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`;
|
|
6057
|
+
}
|
|
6058
|
+
/** The full argv, for the same reason. */
|
|
6059
|
+
function gifFromMp4Args(input) {
|
|
6060
|
+
return [
|
|
6061
|
+
"-y",
|
|
6062
|
+
"-v",
|
|
6063
|
+
"error",
|
|
6064
|
+
"-f",
|
|
6065
|
+
"mp4",
|
|
6066
|
+
"-i",
|
|
6067
|
+
"pipe:0",
|
|
6068
|
+
"-filter_complex",
|
|
6069
|
+
gifFilterComplex(input),
|
|
6070
|
+
"-loop",
|
|
6071
|
+
"0",
|
|
6072
|
+
"-f",
|
|
6073
|
+
"gif",
|
|
6074
|
+
"pipe:1"
|
|
6075
|
+
];
|
|
6076
|
+
}
|
|
6077
|
+
/**
|
|
6078
|
+
* Derive the gif. `null` on every failure — a notification that lost its gif is
|
|
6079
|
+
* a notification, and one that failed is not.
|
|
6080
|
+
*
|
|
6081
|
+
* The child is guaranteed dead before this settles: these run on every
|
|
6082
|
+
* notification that asks for a gif, and a leaked ffmpeg accumulates on the node
|
|
6083
|
+
* until somebody notices the host is slow.
|
|
6084
|
+
*/
|
|
6085
|
+
async function gifFromMp4(input) {
|
|
6086
|
+
const args = gifFromMp4Args({
|
|
6087
|
+
maxWidth: input.maxWidth ?? 640,
|
|
6088
|
+
fps: input.fps ?? 12,
|
|
6089
|
+
speed: input.speed ?? 4
|
|
6090
|
+
});
|
|
6091
|
+
const startedAt = Date.now();
|
|
6092
|
+
try {
|
|
6093
|
+
const bytes = await runGif(input, args);
|
|
6094
|
+
input.logger?.info("nc gif: derived from the notification video", {
|
|
6095
|
+
tags: { deviceId: input.deviceId },
|
|
6096
|
+
meta: {
|
|
6097
|
+
sourceBytes: input.mp4.length,
|
|
6098
|
+
gifBytes: bytes.length,
|
|
6099
|
+
width: input.maxWidth ?? 640,
|
|
6100
|
+
fps: input.fps ?? 12,
|
|
6101
|
+
speed: input.speed ?? 4,
|
|
6102
|
+
encodeMs: Date.now() - startedAt
|
|
6103
|
+
}
|
|
6104
|
+
});
|
|
6105
|
+
return bytes;
|
|
6106
|
+
} catch (err) {
|
|
6107
|
+
input.logger?.warn("nc gif: the derive failed — this notification ships without a gif", {
|
|
6108
|
+
tags: { deviceId: input.deviceId },
|
|
6109
|
+
meta: {
|
|
6110
|
+
sourceBytes: input.mp4.length,
|
|
6111
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6112
|
+
}
|
|
6113
|
+
});
|
|
6114
|
+
return null;
|
|
6115
|
+
}
|
|
6116
|
+
}
|
|
6117
|
+
function runGif(input, args) {
|
|
6118
|
+
return new Promise((resolve, reject) => {
|
|
6119
|
+
const proc = input.spawnFn(input.ffmpegPath ?? "ffmpeg", [...args], { stdio: [
|
|
6120
|
+
"pipe",
|
|
6121
|
+
"pipe",
|
|
6122
|
+
"pipe"
|
|
6123
|
+
] });
|
|
6124
|
+
const chunks = [];
|
|
6125
|
+
let stderrTail = "";
|
|
6126
|
+
let done = false;
|
|
6127
|
+
let timer = null;
|
|
6128
|
+
const settle = (err, out) => {
|
|
6129
|
+
if (done) return;
|
|
6130
|
+
done = true;
|
|
6131
|
+
if (timer !== null) clearTimeout(timer);
|
|
6132
|
+
if (proc.exitCode === null && proc.signalCode === null) try {
|
|
6133
|
+
proc.kill("SIGKILL");
|
|
6134
|
+
} catch {}
|
|
6135
|
+
try {
|
|
6136
|
+
proc.stdout?.removeAllListeners();
|
|
6137
|
+
proc.stderr?.removeAllListeners();
|
|
6138
|
+
proc.stdin?.destroy();
|
|
6139
|
+
} catch {}
|
|
6140
|
+
if (err !== null) reject(err);
|
|
6141
|
+
else resolve(out ?? new Uint8Array(0));
|
|
6142
|
+
};
|
|
6143
|
+
timer = setTimeout(() => settle(/* @__PURE__ */ new Error("gif derive timed out")), GIF_RENDER_TIMEOUT_MS);
|
|
6144
|
+
timer.unref?.();
|
|
6145
|
+
proc.stdout?.on("data", (c) => chunks.push(c));
|
|
6146
|
+
proc.stderr?.on("data", (c) => {
|
|
6147
|
+
stderrTail = (stderrTail + c.toString()).slice(-400);
|
|
6148
|
+
});
|
|
6149
|
+
proc.on("error", (err) => settle(err instanceof Error ? err : new Error(String(err))));
|
|
6150
|
+
proc.on("close", (code) => {
|
|
6151
|
+
if (code !== 0) {
|
|
6152
|
+
settle(/* @__PURE__ */ new Error(`ffmpeg gif derive exited ${code}: ${stderrTail}`));
|
|
6153
|
+
return;
|
|
6154
|
+
}
|
|
6155
|
+
const out = Buffer.concat(chunks);
|
|
6156
|
+
if (out.byteLength === 0) {
|
|
6157
|
+
settle(/* @__PURE__ */ new Error("gif derive produced no bytes"));
|
|
6158
|
+
return;
|
|
6159
|
+
}
|
|
6160
|
+
const bytes = new Uint8Array(out.byteLength);
|
|
6161
|
+
bytes.set(out);
|
|
6162
|
+
settle(null, bytes);
|
|
6163
|
+
});
|
|
6164
|
+
proc.stdin?.on("error", () => {});
|
|
6165
|
+
proc.stdin?.write(Buffer.from(input.mp4));
|
|
6166
|
+
proc.stdin?.end();
|
|
6167
|
+
});
|
|
6168
|
+
}
|
|
6118
6169
|
//#endregion
|
|
6119
6170
|
//#region src/stream-broker/stream-broker/profile-restreamers.ts
|
|
6120
6171
|
/**
|
|
@@ -15245,9 +15296,10 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15245
15296
|
postRollSec: input.postSeconds,
|
|
15246
15297
|
...preferred !== void 0 ? { profile: preferred } : {}
|
|
15247
15298
|
});
|
|
15299
|
+
const clipWidth = eventClipEncodeWidth(cut.sourceWidth, input.speed);
|
|
15248
15300
|
const mp4 = await this.muxClipWindow(cut, {
|
|
15249
15301
|
format: "mp4",
|
|
15250
|
-
maxWidth:
|
|
15302
|
+
maxWidth: clipWidth,
|
|
15251
15303
|
fps: 0,
|
|
15252
15304
|
speed: input.speed
|
|
15253
15305
|
});
|
|
@@ -15263,22 +15315,23 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15263
15315
|
nodeId: this.localNodeId ?? "",
|
|
15264
15316
|
mime: "video/mp4",
|
|
15265
15317
|
bytes: mp4.byteLength,
|
|
15266
|
-
width:
|
|
15267
|
-
height: cut
|
|
15318
|
+
width: clipWidth,
|
|
15319
|
+
height: scaledHeight(cut, clipWidth)
|
|
15268
15320
|
});
|
|
15321
|
+
const gifSpeed = input.gifSpeed ?? 4;
|
|
15269
15322
|
if (input.kinds.includes("gif")) {
|
|
15270
15323
|
const gif = await gifFromMp4({
|
|
15271
15324
|
mp4,
|
|
15272
15325
|
deviceId: input.deviceId,
|
|
15273
15326
|
maxWidth: input.gifMaxWidth,
|
|
15274
15327
|
fps: input.gifFps,
|
|
15275
|
-
speed:
|
|
15328
|
+
speed: gifDeriveSpeed(gifSpeed, input.speed),
|
|
15276
15329
|
...this.logger !== void 0 ? { logger: this.logger } : {},
|
|
15277
15330
|
spawnFn: spawn,
|
|
15278
15331
|
...this.ffmpegConfig.binaryPath !== void 0 ? { ffmpegPath: this.ffmpegConfig.binaryPath } : {}
|
|
15279
15332
|
});
|
|
15280
15333
|
if (gif !== null) {
|
|
15281
|
-
const height = cut
|
|
15334
|
+
const height = scaledHeight(cut, input.gifMaxWidth);
|
|
15282
15335
|
media.push({
|
|
15283
15336
|
kind: "gif",
|
|
15284
15337
|
handle: this.eventMedia.put({
|
|
@@ -15297,7 +15350,7 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15297
15350
|
}
|
|
15298
15351
|
const video = this.clipVideoMode(cut, {
|
|
15299
15352
|
format: "mp4",
|
|
15300
|
-
maxWidth:
|
|
15353
|
+
maxWidth: clipWidth,
|
|
15301
15354
|
speed: input.speed
|
|
15302
15355
|
});
|
|
15303
15356
|
this.logger?.info("event media produced — one window, every container", {
|
|
@@ -15309,6 +15362,8 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
15309
15362
|
packets: cut.window.length,
|
|
15310
15363
|
kinds: [...input.kinds].join(","),
|
|
15311
15364
|
artifacts: media.map((m) => `${m.kind}:${m.bytes}`).join(" "),
|
|
15365
|
+
speed: input.speed,
|
|
15366
|
+
gifSpeed,
|
|
15312
15367
|
startOffsetMs: cut.coverage.fromTs - input.aroundMs,
|
|
15313
15368
|
endOffsetMs: cut.coverage.toTs - input.aroundMs
|
|
15314
15369
|
}
|
|
@@ -17566,6 +17621,17 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
17566
17621
|
}
|
|
17567
17622
|
}
|
|
17568
17623
|
};
|
|
17624
|
+
/**
|
|
17625
|
+
* The height a render lands at, once `scale=W:-2` has picked it.
|
|
17626
|
+
*
|
|
17627
|
+
* `-2` means "keep the aspect, round to an EVEN number" — libx264 rejects an
|
|
17628
|
+
* odd dimension in yuv420p. Reporting the arithmetic without the rounding is
|
|
17629
|
+
* how an artifact ends up one pixel taller than the file it describes.
|
|
17630
|
+
*/
|
|
17631
|
+
function scaledHeight(cut, width) {
|
|
17632
|
+
if (cut.sourceWidth <= 0 || cut.sourceHeight <= 0) return 0;
|
|
17633
|
+
return 2 * Math.round(width / cut.sourceWidth * cut.sourceHeight / 2);
|
|
17634
|
+
}
|
|
17569
17635
|
//#endregion
|
|
17570
17636
|
//#region src/stream-broker/encode-profile-resolver.ts
|
|
17571
17637
|
/**
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BNISghj9.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as DEFAULT_NATIVE_LEASE_SETTINGS, h as NativeLeaseSettingsSchema, m as NativeLeaseAdmissionSchema } from "./dist-
|
|
1
|
+
import { c as DEFAULT_NATIVE_LEASE_SETTINGS, h as NativeLeaseSettingsSchema, m as NativeLeaseAdmissionSchema } from "./dist-DoTw0NM3.mjs";
|
|
2
2
|
//#region src/session-decode/native-lease-config.ts
|
|
3
3
|
/**
|
|
4
4
|
* Resolution of the native-frame lease knobs for one decode worker.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{d as e,f as t,g as n,h as r,m as i,p as a}from"./index-
|
|
1
|
+
import{d as e,f as t,g as n,h as r,m as i,p as a}from"./index-C3FKV1x-.js";var o=n(t()),s=n(e(),1),c=Math.PI/180;function l(){return typeof window<`u`&&({}.toString.call(window)===`[object Window]`||{}.toString.call(window)===`[object global]`)}var u=typeof global<`u`?global:typeof window<`u`?window:typeof WorkerGlobalScope<`u`?self:{},d={_global:u,version:`10.3.0`,isBrowser:l(),isUnminified:/param/.test(function(e){}.toString()),dblClickWindow:400,getAngle(e){return d.angleDeg?e*c:e},enableTrace:!1,pointerEventsEnabled:!0,autoDrawEnabled:!0,hitOnDragEnabled:!1,capturePointerEventsEnabled:!1,_mouseListenClick:!1,_touchListenClick:!1,_pointerListenClick:!1,_mouseInDblClickWindow:!1,_touchInDblClickWindow:!1,_pointerInDblClickWindow:!1,_mouseDblClickPointerId:null,_touchDblClickPointerId:null,_pointerDblClickPointerId:null,_renderBackend:`web`,legacyTextRendering:!1,pixelRatio:typeof window<`u`&&window.devicePixelRatio||1,dragDistance:3,angleDeg:!0,showWarnings:!0,dragButtons:[0,1],isDragging(){return d.DD.isDragging},isTransforming(){return d.Transformer?.isTransforming()??!1},isDragReady(){return!!d.DD.node},releaseCanvasOnDestroy:!0,document:u.document,_injectGlobal(e){u.Konva!==void 0&&console.error(`Several Konva instances detected. It is not recommended to use multiple Konva instances in the same environment.`),u.Konva=e}},f=e=>{d[e.prototype.getClassName()]=e};d._injectGlobal(d);var p=`Konva.js unsupported environment.
|
|
2
2
|
|
|
3
3
|
Looks like you are trying to use Konva.js in Node.js environment. because "document" object is undefined.
|
|
4
4
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,d as n,f as r,g as i,l as a,o,r as s,s as c,u as l}from"./index-
|
|
1
|
+
import{a as e,c as t,d as n,f as r,g as i,l as a,o,r as s,s as c,u as l}from"./index-C3FKV1x-.js";import{MaskShapeCanvas as u}from"./MaskShapeCanvas-DI4BY7W2-EcWZ5m8e.js";var d=i(r(),1),f=i(n(),1),p=o(`grid-2x2`,[[`path`,{d:`M12 3v18`,key:`108xh3`}],[`path`,{d:`M3 12h18`,key:`1i2n21`}],[`rect`,{x:`3`,y:`3`,width:`18`,height:`18`,rx:`2`,key:`h1oib`}]]),m=110,h=`motion-zones`,g=0,_=[1,2,3],v=1,y=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,b=`rounded-md border border-primary/50 bg-primary/15 px-2.5 py-1 text-[11px] font-medium text-primary hover:bg-primary/25 disabled:opacity-40 transition-colors`;function x(e,t,n){let r=t*n,i=Array.from({length:r});for(let t=0;t<r;t+=1)i[t]=e[t]===!0;return i}function S(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(e[n]!==t[n])return!1;return!0}function C(e,t){return Math.ceil(e/t)}function w(e,t,n){return Math.min(n-1,Math.floor((e+.5)/t*n))}function T(e,t,n,r){let i=C(t,r),a=C(n,r),o=Array.from({length:i*a},()=>!1);for(let r=0;r<n;r+=1){let s=w(r,n,a);for(let n=0;n<t;n+=1)e[r*t+n]===!0&&(o[s*i+w(n,t,i)]=!0)}return o}function E(e,t,n,r){let i=C(t,r),a=C(n,r),o=Array.from({length:t*n},()=>!1);for(let r=0;r<n;r+=1){let s=w(r,n,a);for(let n=0;n<t;n+=1)o[r*t+n]=e[s*i+w(n,t,i)]===!0}return o}function D({deviceId:n}){let r=t(l().trpcClient,n),[i,o]=(0,d.useState)(null),[w,D]=(0,d.useState)(!1),[O,k]=(0,d.useState)(null),[A,j]=(0,d.useState)(null),[M,N]=(0,d.useState)(v),[P,F]=(0,d.useState)(!1),[I,L]=(0,d.useState)(!1),R=(0,d.useRef)(!1);(0,d.useEffect)(()=>{if(!r)return;let e=!1;return R.current=!1,o(null),D(!1),k(null),j(null),N(v),L(!1),(async()=>{try{let t=await r.motionZones?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(o(t),R.current)return;let n=await r.motionZones?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);R.current=!0;let i=n.regions.find(e=>e.shape.kind===`grid`),a=x(i?i.shape.cells:[],t.grid.width,t.grid.height);j(a),k(T(a,t.grid.width,t.grid.height,v))}catch(t){if(e)return;c(t)?D(!0):console.error(`Motion Zones load failed`,t)}})(),()=>{e=!0}},[r]);let z=i?i.grid.width:0,B=i?i.grid.height:0,V=C(z,M),H=C(B,M),U=(0,d.useMemo)(()=>O&&i?E(O,z,B,M):null,[O,i,z,B,M]),W=(0,d.useMemo)(()=>U!==null&&A!==null&&!S(U,A),[U,A]),G=(0,d.useMemo)(()=>U?U.reduce((e,t)=>t?e+1:e,0):0,[U]),K=z*B,q=V*H,J=(0,d.useCallback)(e=>{q>0&&k(Array.from({length:q},()=>e))},[q]),Y=(0,d.useCallback)(()=>{k(e=>e&&e.map(e=>!e))},[]),X=(0,d.useCallback)(()=>{A&&i&&k(T(A,z,B,M))},[A,i,z,B,M]),Z=(0,d.useCallback)(e=>{e===M||!i||k(t=>{if(!t)return N(e),t;let n=T(E(t,z,B,M),z,B,e);return N(e),n})},[M,i,z,B]),Q=(0,d.useMemo)(()=>i&&O?[{id:g,shape:{kind:`grid`,gridWidth:V,gridHeight:H,cells:[...O]}}]:[],[i,O,V,H]),$=(0,d.useCallback)((e,t)=>{t.kind===`grid`&&k(t.cells)},[]),ee=(0,d.useCallback)(async()=>{if(!(!r||!O||!i)){F(!0);try{let e=E(O,i.grid.width,i.grid.height,M),t={kind:`grid`,gridWidth:i.grid.width,gridHeight:i.grid.height,cells:e};await r.motionZones?.setZone({patch:{regions:[{id:g,enabled:!0,shape:t}]}});let n=await r.motionZones?.getStatus({});if(n){let e=n.regions.find(e=>e.shape.kind===`grid`),t=x(e?e.shape.cells:[],i.grid.width,i.grid.height);j(t),k(T(t,i.grid.width,i.grid.height,M))}}catch(e){console.error(`Motion Zones save failed`,e)}finally{F(!1)}}},[r,O,i,M]);a((0,d.useMemo)(()=>I&&!w&&i&&O?{id:h,order:m,node:(0,f.jsx)(u,{transparent:!0,items:Q,supportedShapes:[`grid`],grid:{width:V,height:H},selectedId:g,onSelect:()=>{},onShapeChange:$,onDrawComplete:()=>{},drawingKind:null})}:null,[I,w,i,O,Q,$,V,H]));let te=!w&&i!==null&&O!==null;return r?(0,f.jsx)(e,{title:`Motion Zones`,icon:(0,f.jsx)(p,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,f.jsx)(`div`,{className:`flex flex-col gap-3`,children:w?(0,f.jsx)(`p`,{className:`${s} leading-relaxed`,children:`This camera doesn't expose an on-board motion zones grid.`}):te?(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`p`,{className:`${s} leading-relaxed`,children:[`Toggle `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Edit grid`}),` to paint the region directly on the live frame, then `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Save`}),` to push the mask to the camera. Pick a bigger`,` `,(0,f.jsx)(`strong`,{className:`text-foreground`,children:`Cell size`}),` for quicker, broad-stroke painting — it's resampled to the camera's native `,i.grid.width,`×`,i.grid.height,` grid on save (×1 is the finest).`]}),(0,f.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,f.jsx)(`button`,{type:`button`,onClick:()=>L(e=>!e),disabled:P,"aria-pressed":I,className:I?b:y,children:I?`Done editing`:`Edit grid`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>J(!0),disabled:P,className:y,children:`All on`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>J(!1),disabled:P,className:y,children:`All off`}),(0,f.jsx)(`button`,{type:`button`,onClick:Y,disabled:P,className:y,children:`Invert`}),(0,f.jsxs)(`div`,{className:`flex items-center gap-1 ml-1`,role:`group`,"aria-label":`Cell size`,children:[(0,f.jsx)(`span`,{className:`${s} mr-0.5`,children:`Cell size`}),_.map(e=>(0,f.jsxs)(`button`,{type:`button`,onClick:()=>Z(e),disabled:P,"aria-pressed":M===e,title:e===1?`Camera grid ${z}×${B} (finest)`:`${C(z,e)}×${C(B,e)} painting grid · cells ×${e} bigger`,className:M===e?b:y,children:[`×`,e]},e))]}),(0,f.jsxs)(`span`,{className:`${s} ml-1 tabular-nums`,children:[G,` / `,K,` cells · `,i.grid.width,`×`,i.grid.height,M===1?``:` · paint ${V}×${H}`]}),(0,f.jsx)(`span`,{className:`flex-1`}),(0,f.jsx)(`button`,{type:`button`,onClick:X,disabled:P||!W,className:`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,children:`Revert`}),(0,f.jsx)(`button`,{type:`button`,onClick:()=>void ee(),disabled:P||!W,className:b,children:P?`Saving…`:`Save`})]})]}):(0,f.jsx)(`p`,{className:`${s} leading-relaxed`,children:`Loading the camera's grid…`})})}):null}export{D as MotionZonesSettings};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,d as n,f as r,g as i,i as a,l as o,n as s,o as c,r as l,s as u,t as d,u as ee}from"./index-
|
|
1
|
+
import{a as e,c as t,d as n,f as r,g as i,i as a,l as o,n as s,o as c,r as l,s as u,t as d,u as ee}from"./index-C3FKV1x-.js";import{MaskShapeCanvas as te}from"./MaskShapeCanvas-DI4BY7W2-EcWZ5m8e.js";var f=i(r(),1),p=i(n(),1),m=c(`hexagon`,[[`path`,{d:`M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z`,key:`yt0hxn`}]]),h=120,g=`privacy-mask`,_=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,v=`rounded-md border border-primary/50 bg-primary/15 px-2.5 py-1 text-[11px] font-medium text-primary hover:bg-primary/25 disabled:opacity-40 transition-colors`;function y(e){return e.kind===`rect`||e.kind===`polygon`?e:null}function b(e){let t=new Set(e.map(e=>e.id)),n=0;for(;t.has(n);)n+=1;return n}function x(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(JSON.stringify(e[n])!==JSON.stringify(t[n]))return!1;return!0}function S({deviceId:n}){let r=t(ee().trpcClient,n),[i,c]=(0,f.useState)(null),[S,C]=(0,f.useState)(!1),[w,T]=(0,f.useState)(null),[E,D]=(0,f.useState)(null),[O,k]=(0,f.useState)(!1),[A,j]=(0,f.useState)(!1),[M,N]=(0,f.useState)(null),[P,F]=(0,f.useState)(null),I=(0,f.useRef)(!1);(0,f.useEffect)(()=>{if(!r)return;let e=!1;return I.current=!1,c(null),C(!1),T(null),D(null),j(!1),N(null),F(null),(async()=>{try{let t=await r.privacyMask?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(c(t),I.current)return;let n=await r.privacyMask?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);I.current=!0;let i={enabled:n.enabled,regions:n.regions};D(i),T(i)}catch(t){if(e)return;u(t)?C(!0):console.error(`Privacy Mask load failed`,t)}})(),()=>{e=!0}},[r]);let L=(0,f.useMemo)(()=>w!==null&&E!==null&&(w.enabled!==E.enabled||!x(w.regions,E.regions)),[w,E]),R=w?w.regions.length:0,z=i?i.maxRegions:0,B=(0,f.useRef)(0);(0,f.useEffect)(()=>{B.current=z},[z]);let V=z>0&&R>=z,H=(0,f.useCallback)(()=>{T(e=>e&&{...e,enabled:!e.enabled})},[]),U=(0,f.useCallback)(e=>{N(typeof e==`number`?e:null)},[]),W=(0,f.useMemo)(()=>w?w.regions.map(e=>({id:e.id,shape:e.shape,enabled:e.enabled,label:`Zone ${String(e.id)}`})):[],[w]),G=(0,f.useCallback)((e,t)=>{let n=y(t);n&&T(t=>t&&{...t,regions:t.regions.map(t=>t.id===e?{...t,shape:n}:t)})},[]),K=(0,f.useCallback)(e=>{let t=y(e);t&&(F(null),T(e=>{if(!e)return e;let n=B.current;if(n>0&&e.regions.length>=n)return e;let r=b(e.regions),i={id:r,enabled:!0,shape:t};return N(r),{...e,regions:[...e.regions,i]}}))},[]),q=(0,f.useCallback)(e=>{j(!0),N(null),F(e)},[]),J=(0,f.useCallback)(e=>{j(!0),F(null),N(e)},[]),Y=(0,f.useCallback)(e=>{N(t=>t===e?null:t),T(t=>t&&{...t,regions:t.regions.filter(t=>t.id!==e)})},[]),X=(0,f.useCallback)(()=>{F(null),N(null),E&&T({enabled:E.enabled,regions:E.regions})},[E]),Z=(0,f.useCallback)(async()=>{if(!(!r||!w)){k(!0);try{await r.privacyMask?.setMask({patch:{enabled:w.enabled,regions:[...w.regions]}});let e=await r.privacyMask?.getStatus({});if(e){let t={enabled:e.enabled,regions:e.regions};D(t),T(t)}}catch(e){console.error(`Privacy Mask save failed`,e)}finally{k(!1)}}},[r,w]),ne=(0,f.useCallback)(()=>{j(e=>(e&&(F(null),N(null)),!e))},[]),Q=i?.supportedShapes??[];o((0,f.useMemo)(()=>A&&!S&&i&&w?{id:g,order:h,node:(0,p.jsx)(te,{transparent:!0,items:W,supportedShapes:Q,polygonVertices:i.polygonVertices,selectedId:M,onSelect:U,onShapeChange:G,onDrawComplete:K,drawingKind:P})}:null,[A,S,i,w,W,Q,M,U,G,K,P]));let re=i?.supportedShapes.includes(`rect`)??!1,ie=i?.supportedShapes.includes(`polygon`)??!1,$=i!==null&&(i.maxRegions<=0||i.supportedShapes.length===0),ae=!S&&!$&&i!==null&&w!==null;return r?(0,p.jsx)(e,{title:`Privacy Mask`,icon:(0,p.jsx)(d,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,p.jsx)(`div`,{className:`flex flex-col gap-3`,children:S||$?(0,p.jsx)(`p`,{className:`${l} leading-relaxed`,children:`This camera doesn't support an on-board privacy mask.`}):ae?(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(`p`,{className:`${l} leading-relaxed`,children:[`Toggle `,(0,p.jsx)(`strong`,{className:`text-foreground`,children:`Edit mask`}),` to draw blanked-out zones on the live frame, then `,(0,p.jsx)(`strong`,{className:`text-foreground`,children:`Save`}),` to push them to the camera. Drag a rectangle to move, its corner to resize; drag polygon vertices, click an edge midpoint to add one, or right-click a vertex to remove it.`]}),(0,p.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,p.jsx)(`button`,{type:`button`,onClick:ne,disabled:O,"aria-pressed":A,className:A?v:_,children:A?`Done editing`:`Edit mask`}),(0,p.jsx)(`button`,{type:`button`,onClick:H,disabled:O,"aria-pressed":w.enabled,className:w.enabled?v:_,children:w.enabled?`Mask on`:`Mask off`}),re&&(0,p.jsx)(`button`,{type:`button`,onClick:()=>q(`rect`),disabled:O||V,"aria-pressed":P===`rect`,className:P===`rect`?v:_,title:V?`Maximum zones reached`:`Add a rectangle zone`,children:`+ Rect`}),ie&&(0,p.jsx)(`button`,{type:`button`,onClick:()=>q(`polygon`),disabled:O||V,"aria-pressed":P===`polygon`,className:P===`polygon`?v:_,title:V?`Maximum zones reached`:`Add a polygon zone`,children:`+ Polygon`}),(0,p.jsxs)(`span`,{className:`${l} ml-1 tabular-nums`,children:[R,` / `,z,` zones`]}),(0,p.jsx)(`span`,{className:`flex-1`}),(0,p.jsx)(`button`,{type:`button`,onClick:X,disabled:O||!L,className:`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,children:`Revert`}),(0,p.jsx)(`button`,{type:`button`,onClick:()=>void Z(),disabled:O||!L,className:v,children:O?`Saving…`:`Save`})]}),R>0?(0,p.jsx)(`div`,{className:`flex flex-col gap-1`,children:w.regions.map(e=>{let t=M===e.id,n=e.shape.kind===`polygon`?m:s;return(0,p.jsxs)(`div`,{className:`flex items-center gap-2 rounded-md border px-2 py-1 transition-colors ${t?`border-primary/50 bg-primary/10`:`border-border bg-surface`}`,children:[(0,p.jsxs)(`button`,{type:`button`,onClick:()=>J(e.id),disabled:O,className:`flex flex-1 items-center gap-2 text-left text-[11px] font-medium text-foreground-subtle hover:text-foreground disabled:opacity-40 transition-colors`,children:[(0,p.jsx)(n,{className:`h-3.5 w-3.5 shrink-0`}),(0,p.jsxs)(`span`,{children:[`Zone `,e.id]}),(0,p.jsx)(`span`,{className:`text-foreground-faint capitalize`,children:e.shape.kind})]}),(0,p.jsx)(`button`,{type:`button`,onClick:()=>Y(e.id),disabled:O,"aria-label":`Delete zone ${String(e.id)}`,title:`Delete zone`,className:`inline-flex h-6 w-6 items-center justify-center rounded border border-border bg-surface text-foreground-subtle hover:border-red-400/40 hover:bg-red-500/10 hover:text-red-400 disabled:opacity-40 transition-colors`,children:(0,p.jsx)(a,{className:`h-3.5 w-3.5`})})]},e.id)})}):null]}):(0,p.jsx)(`p`,{className:`${l} leading-relaxed`,children:`Loading the camera's privacy mask…`})})}):null}export{S as PrivacyMaskSettings};
|