@camstack/addon-pipeline 1.2.42 → 1.2.43
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 +4 -4
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/detection-pipeline/index.js +9 -9
- package/dist/detection-pipeline/index.mjs +3 -3
- package/dist/{dist-COK_1Ot8.mjs → dist-B-VVBzrL.mjs} +889 -20
- package/dist/{dist-BdDd3ql_.js → dist-UHbKZiSa.js} +906 -19
- package/dist/{event-loop-stall-monitor-DNFRgNef.js → event-loop-stall-monitor-C7_L6vHO.js} +1 -1
- package/dist/{event-loop-stall-monitor-CWOJtTNc.mjs → event-loop-stall-monitor-DfKdC7G4.mjs} +1 -1
- package/dist/{model-download-service-Cp9f4dk6-fsdDExML.js → model-download-service-D8B-4ktF-7-YxE9Wx.js} +2 -2
- package/dist/{model-download-service-Cp9f4dk6-CwUb5v3Y.mjs → model-download-service-D8B-4ktF-BB7oZL3y.mjs} +2 -2
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +160 -4
- package/dist/pipeline-runner/index.mjs +160 -4
- package/dist/recorder/index.js +6 -6
- package/dist/recorder/index.mjs +3 -3
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C8ovZN0g.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D7iQbuKY.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCC9fqHH.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-DAjAxXHm.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-54IklEep.mjs} +1 -1
- package/dist/stream-broker/{hostInit-mEtjQWcr.mjs → hostInit-CKYS9yZO.mjs} +2 -2
- package/dist/stream-broker/index.js +16202 -15821
- package/dist/stream-broker/index.mjs +16201 -15820
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C0TIfr0G.js → MaskShapeCanvas-DI4BY7W2-DxZ75vB8.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-x0SkfHwv.js → MotionZonesSettings-NcxxQN8r-BBY2Ztz5.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BoaAB4Ta.js → PrivacyMaskSettings-APgPLF7p-BC-3QmHa.js} +1 -1
- package/embed-dist/assets/{index-BgRdCCVy.js → index-on1wyOnd.js} +12 -12
- 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-SU_6F3Y7.mjs +0 -26
|
@@ -7113,6 +7113,14 @@ var EncodeProfileSchema = object({
|
|
|
7113
7113
|
"main",
|
|
7114
7114
|
"high"
|
|
7115
7115
|
]).optional(),
|
|
7116
|
+
/**
|
|
7117
|
+
* `-level`, e.g. `'3.1'`. A consumer that ADVERTISES a level in its SDP
|
|
7118
|
+
* (`profile-level-id=42e01f` is Baseline 3.1) must constrain the encoder to
|
|
7119
|
+
* it, or it ships a stream that does not match its own advertisement — the
|
|
7120
|
+
* defect class that kept HomeKit black for a year and that Alexa carried
|
|
7121
|
+
* silently. Optional because a browser negotiates the level itself.
|
|
7122
|
+
*/
|
|
7123
|
+
level: string().optional(),
|
|
7116
7124
|
width: number().int().positive().optional(),
|
|
7117
7125
|
height: number().int().positive().optional(),
|
|
7118
7126
|
fps: number().positive().optional(),
|
|
@@ -7159,6 +7167,356 @@ var EncodeProfileSchema = object({
|
|
|
7159
7167
|
*/
|
|
7160
7168
|
outputArgs: array(string()).optional()
|
|
7161
7169
|
});
|
|
7170
|
+
var AUDIO_ENCODER_BY_CODEC = {
|
|
7171
|
+
opus: "libopus",
|
|
7172
|
+
aac: "aac",
|
|
7173
|
+
pcmu: "pcm_mulaw",
|
|
7174
|
+
pcma: "pcm_alaw"
|
|
7175
|
+
};
|
|
7176
|
+
/**
|
|
7177
|
+
* Camera-microphone audio, per codec. Lives HERE rather than in
|
|
7178
|
+
* `encode-defaults.ts` only to avoid an import cycle (`encode-defaults` depends
|
|
7179
|
+
* on these types); it is re-exported from there, which is where to read it.
|
|
7180
|
+
*
|
|
7181
|
+
* Every source in this repo is a mono camera mic. The former broker preset
|
|
7182
|
+
* encoded Opus at `channels: 2`, spending bitrate duplicating one channel —
|
|
7183
|
+
* that is the value this consolidation changed.
|
|
7184
|
+
*/
|
|
7185
|
+
var AUDIO_PRESETS = {
|
|
7186
|
+
aac: {
|
|
7187
|
+
kind: "encode",
|
|
7188
|
+
codec: "aac",
|
|
7189
|
+
bitrateKbps: 128,
|
|
7190
|
+
sampleRateHz: 48e3,
|
|
7191
|
+
channels: 1
|
|
7192
|
+
},
|
|
7193
|
+
opus: {
|
|
7194
|
+
kind: "encode",
|
|
7195
|
+
codec: "opus",
|
|
7196
|
+
bitrateKbps: 64,
|
|
7197
|
+
sampleRateHz: 48e3,
|
|
7198
|
+
channels: 1
|
|
7199
|
+
},
|
|
7200
|
+
pcmu: {
|
|
7201
|
+
kind: "encode",
|
|
7202
|
+
codec: "pcmu",
|
|
7203
|
+
sampleRateHz: 8e3,
|
|
7204
|
+
channels: 1
|
|
7205
|
+
}
|
|
7206
|
+
};
|
|
7207
|
+
/** `-hide_banner -loglevel <level>` — every ffmpeg site opens with this. */
|
|
7208
|
+
function logBannerArgs(level) {
|
|
7209
|
+
return [
|
|
7210
|
+
"-hide_banner",
|
|
7211
|
+
"-loglevel",
|
|
7212
|
+
level
|
|
7213
|
+
];
|
|
7214
|
+
}
|
|
7215
|
+
/** `true` when the resolved value means "decode in software" (⇒ no `-hwaccel`). */
|
|
7216
|
+
function isSoftwareDecode(decodeHwAccel) {
|
|
7217
|
+
return !decodeHwAccel || decodeHwAccel === "none" || decodeHwAccel === "copy";
|
|
7218
|
+
}
|
|
7219
|
+
/**
|
|
7220
|
+
* Every INPUT option, in order, terminated by `-i <url>`. Nothing may be
|
|
7221
|
+
* appended to this list by a caller — that is the whole point of the function.
|
|
7222
|
+
*/
|
|
7223
|
+
function buildInputArgs(input, decodeHwAccel) {
|
|
7224
|
+
const args = [];
|
|
7225
|
+
if (!isSoftwareDecode(decodeHwAccel)) args.push("-hwaccel", String(decodeHwAccel));
|
|
7226
|
+
if (input.extraArgs?.length) args.push(...input.extraArgs);
|
|
7227
|
+
if (input.fflags?.length) for (const flag of input.fflags) args.push("-fflags", flag);
|
|
7228
|
+
if (input.rtspTransport) args.push("-rtsp_transport", input.rtspTransport);
|
|
7229
|
+
args.push("-i", input.url);
|
|
7230
|
+
return args;
|
|
7231
|
+
}
|
|
7232
|
+
/** The `-vf` filter args, or `[]` when a consumer `-vf` already claims the slot. */
|
|
7233
|
+
function buildVideoFilterArgs(scale, outputArgs) {
|
|
7234
|
+
if (!scale) return [];
|
|
7235
|
+
if (outputArgs.some((a) => a === "-vf")) return [];
|
|
7236
|
+
if (scale.mode === "exact") return ["-vf", `scale=${scale.width}:${scale.height}`];
|
|
7237
|
+
return ["-vf", `scale='min(${scale.width},iw)':'min(${scale.height},ih)':force_original_aspect_ratio=decrease:force_divisible_by=2`];
|
|
7238
|
+
}
|
|
7239
|
+
/** Rate-control args for an encode plan. */
|
|
7240
|
+
function buildRateControlArgs(video) {
|
|
7241
|
+
const kbps = video.bitrateKbps;
|
|
7242
|
+
if (kbps === void 0) return [];
|
|
7243
|
+
const rc = video.rateControl ?? {
|
|
7244
|
+
kind: "cap",
|
|
7245
|
+
vbvSeconds: 2
|
|
7246
|
+
};
|
|
7247
|
+
const bufsize = Math.max(1, Math.round(kbps * rc.vbvSeconds));
|
|
7248
|
+
return [
|
|
7249
|
+
...rc.kind === "cbr" ? ["-b:v", `${kbps}k`] : [],
|
|
7250
|
+
"-maxrate",
|
|
7251
|
+
`${kbps}k`,
|
|
7252
|
+
"-bufsize",
|
|
7253
|
+
`${bufsize}k`
|
|
7254
|
+
];
|
|
7255
|
+
}
|
|
7256
|
+
/** The whole video block (`-vf` … `-c:v` … knobs), after `-i`. */
|
|
7257
|
+
function buildVideoArgs(video, outputArgs) {
|
|
7258
|
+
if (video.kind === "copy") return [
|
|
7259
|
+
"-c:v",
|
|
7260
|
+
"copy",
|
|
7261
|
+
...video.bitstreamFilter ? ["-bsf:v", video.bitstreamFilter] : []
|
|
7262
|
+
];
|
|
7263
|
+
const args = [
|
|
7264
|
+
...buildVideoFilterArgs(video.scale, outputArgs),
|
|
7265
|
+
"-c:v",
|
|
7266
|
+
video.encoder
|
|
7267
|
+
];
|
|
7268
|
+
if (video.preset !== void 0) args.push("-preset", video.preset);
|
|
7269
|
+
if (video.tune !== void 0) args.push("-tune", video.tune);
|
|
7270
|
+
if (video.profile !== void 0) args.push("-profile:v", video.profile);
|
|
7271
|
+
if (video.level !== void 0) args.push("-level", video.level);
|
|
7272
|
+
if (video.pixelFormat !== void 0) args.push("-pix_fmt", video.pixelFormat);
|
|
7273
|
+
if (video.fps !== void 0) args.push("-r", String(video.fps));
|
|
7274
|
+
if (video.gopFrames !== void 0) args.push("-g", String(video.gopFrames));
|
|
7275
|
+
if (video.bf !== void 0) args.push("-bf", String(video.bf));
|
|
7276
|
+
args.push(...buildRateControlArgs(video));
|
|
7277
|
+
if (video.bitstreamFilter !== void 0) args.push("-bsf:v", video.bitstreamFilter);
|
|
7278
|
+
return args;
|
|
7279
|
+
}
|
|
7280
|
+
/** The whole audio block, after `-i`. */
|
|
7281
|
+
function buildAudioArgs(audio) {
|
|
7282
|
+
if (audio.kind === "none") return ["-an"];
|
|
7283
|
+
if (audio.kind === "copy") return ["-c:a", "copy"];
|
|
7284
|
+
const args = [];
|
|
7285
|
+
if (audio.filter !== void 0) args.push("-af", audio.filter);
|
|
7286
|
+
args.push("-c:a", AUDIO_ENCODER_BY_CODEC[audio.codec]);
|
|
7287
|
+
if (audio.application !== void 0) args.push("-application", audio.application);
|
|
7288
|
+
if (audio.frameDurationMs !== void 0) args.push("-frame_duration", String(audio.frameDurationMs));
|
|
7289
|
+
if (audio.globalHeader === true) args.push("-flags", "+global_header");
|
|
7290
|
+
if (audio.sampleRateHz !== void 0) args.push("-ar", String(audio.sampleRateHz));
|
|
7291
|
+
if (audio.bitrateKbps !== void 0) args.push("-b:a", `${audio.bitrateKbps}k`);
|
|
7292
|
+
if (audio.vbvBufferKbits !== void 0) args.push("-bufsize", `${audio.vbvBufferKbits}k`);
|
|
7293
|
+
if (audio.channels !== void 0) args.push("-ac", String(audio.channels));
|
|
7294
|
+
return args;
|
|
7295
|
+
}
|
|
7296
|
+
/** RTP output-leg args (`-payload_type`, `-ssrc`, `-sdp_file`, `-f rtp <url>`). */
|
|
7297
|
+
function buildRtpOutputArgs(out) {
|
|
7298
|
+
const args = [];
|
|
7299
|
+
if (out.payloadType !== void 0) args.push("-payload_type", String(out.payloadType));
|
|
7300
|
+
if (out.ssrc !== void 0) args.push("-ssrc", String(out.ssrc));
|
|
7301
|
+
if (out.sdpFile !== void 0) args.push("-sdp_file", out.sdpFile);
|
|
7302
|
+
args.push("-f", "rtp", out.url);
|
|
7303
|
+
return args;
|
|
7304
|
+
}
|
|
7305
|
+
/** `true` when the sink is a raw elementary bytestream that cannot mux audio. */
|
|
7306
|
+
function isElementaryVideoSink(sink) {
|
|
7307
|
+
return sink.kind === "stdout" && (sink.container === "h264" || sink.container === "hevc");
|
|
7308
|
+
}
|
|
7309
|
+
/**
|
|
7310
|
+
* A second output mapping source audio to RTP-over-UDP. `0:a:0?` makes the
|
|
7311
|
+
* audio optional so a source with no audio skips it instead of failing the
|
|
7312
|
+
* whole invocation.
|
|
7313
|
+
*/
|
|
7314
|
+
function buildAudioSidecarArgs(sidecar) {
|
|
7315
|
+
return [
|
|
7316
|
+
"-map",
|
|
7317
|
+
"0:a:0?",
|
|
7318
|
+
...buildAudioArgs(sidecar.codec === "pcma" ? {
|
|
7319
|
+
kind: "encode",
|
|
7320
|
+
codec: "pcma",
|
|
7321
|
+
sampleRateHz: 8e3,
|
|
7322
|
+
channels: 1
|
|
7323
|
+
} : AUDIO_PRESETS[sidecar.codec]),
|
|
7324
|
+
...buildRtpOutputArgs({
|
|
7325
|
+
url: sidecar.rtpUrl,
|
|
7326
|
+
sdpFile: sidecar.sdpFile
|
|
7327
|
+
})
|
|
7328
|
+
];
|
|
7329
|
+
}
|
|
7330
|
+
/**
|
|
7331
|
+
* Assemble the full ffmpeg argument list. Layout:
|
|
7332
|
+
*
|
|
7333
|
+
* -hide_banner -loglevel <level>
|
|
7334
|
+
* [-hwaccel <backend|auto>] ─┐ INPUT options — strictly before -i.
|
|
7335
|
+
* [<input.extraArgs>] │
|
|
7336
|
+
* [-fflags <flag>…] │
|
|
7337
|
+
* [-rtsp_transport tcp] │
|
|
7338
|
+
* -i <url> ─┘
|
|
7339
|
+
* <video block> <threads> <audio block> ─┐ OUTPUT options.
|
|
7340
|
+
* <consumer outputArgs verbatim> │
|
|
7341
|
+
* <sink> ─┘ terminal
|
|
7342
|
+
*/
|
|
7343
|
+
function buildFfmpegArgs(inv) {
|
|
7344
|
+
const head = [...logBannerArgs(inv.logLevel), ...buildInputArgs(inv.input, inv.decodeHwAccel)];
|
|
7345
|
+
const threadArgs = inv.threadCount > 0 ? ["-threads", String(inv.threadCount)] : [];
|
|
7346
|
+
if (inv.sink.kind === "rtp-outputs") {
|
|
7347
|
+
const videoLeg = inv.sink.video ? [
|
|
7348
|
+
"-an",
|
|
7349
|
+
"-map",
|
|
7350
|
+
"0:v:0",
|
|
7351
|
+
...buildVideoArgs(inv.video, inv.outputArgs),
|
|
7352
|
+
...threadArgs,
|
|
7353
|
+
...inv.outputArgs,
|
|
7354
|
+
...buildRtpOutputArgs(inv.sink.video)
|
|
7355
|
+
] : [];
|
|
7356
|
+
const audioLeg = inv.sink.audio ? [
|
|
7357
|
+
"-vn",
|
|
7358
|
+
"-map",
|
|
7359
|
+
"0:a:0?",
|
|
7360
|
+
...buildAudioArgs(inv.audio),
|
|
7361
|
+
...buildRtpOutputArgs(inv.sink.audio)
|
|
7362
|
+
] : [];
|
|
7363
|
+
return [
|
|
7364
|
+
...head,
|
|
7365
|
+
...videoLeg,
|
|
7366
|
+
...audioLeg
|
|
7367
|
+
];
|
|
7368
|
+
}
|
|
7369
|
+
const audioArgs = isElementaryVideoSink(inv.sink) ? ["-an"] : buildAudioArgs(inv.audio);
|
|
7370
|
+
const sinkArgs = inv.sink.kind === "stdout" ? [
|
|
7371
|
+
"-f",
|
|
7372
|
+
inv.sink.container,
|
|
7373
|
+
"pipe:1"
|
|
7374
|
+
] : [
|
|
7375
|
+
"-f",
|
|
7376
|
+
"rtsp",
|
|
7377
|
+
"-rtsp_transport",
|
|
7378
|
+
"tcp",
|
|
7379
|
+
"-rtsp_flags",
|
|
7380
|
+
"listen",
|
|
7381
|
+
inv.sink.url
|
|
7382
|
+
];
|
|
7383
|
+
return [
|
|
7384
|
+
...head,
|
|
7385
|
+
...buildVideoArgs(inv.video, inv.outputArgs),
|
|
7386
|
+
...threadArgs,
|
|
7387
|
+
...audioArgs,
|
|
7388
|
+
...inv.outputArgs,
|
|
7389
|
+
...sinkArgs,
|
|
7390
|
+
...inv.audioSidecar ? buildAudioSidecarArgs(inv.audioSidecar) : []
|
|
7391
|
+
];
|
|
7392
|
+
}
|
|
7393
|
+
/**
|
|
7394
|
+
* Hardware ENCODER ids per decode-hwaccel backend — a static, deterministic
|
|
7395
|
+
* map (the same shape the reference NVR uses: platform → encoder, no probe).
|
|
7396
|
+
*/
|
|
7397
|
+
var ENCODER_IDS_BY_BACKEND = {
|
|
7398
|
+
videotoolbox: {
|
|
7399
|
+
h264: "h264_videotoolbox",
|
|
7400
|
+
h265: "hevc_videotoolbox"
|
|
7401
|
+
},
|
|
7402
|
+
vaapi: {
|
|
7403
|
+
h264: "h264_vaapi",
|
|
7404
|
+
h265: "hevc_vaapi"
|
|
7405
|
+
},
|
|
7406
|
+
qsv: {
|
|
7407
|
+
h264: "h264_qsv",
|
|
7408
|
+
h265: "hevc_qsv"
|
|
7409
|
+
},
|
|
7410
|
+
cuda: {
|
|
7411
|
+
h264: "h264_nvenc",
|
|
7412
|
+
h265: "hevc_nvenc"
|
|
7413
|
+
},
|
|
7414
|
+
nvdec: {
|
|
7415
|
+
h264: "h264_nvenc",
|
|
7416
|
+
h265: "hevc_nvenc"
|
|
7417
|
+
},
|
|
7418
|
+
amf: {
|
|
7419
|
+
h264: "h264_amf",
|
|
7420
|
+
h265: "hevc_amf"
|
|
7421
|
+
}
|
|
7422
|
+
};
|
|
7423
|
+
/**
|
|
7424
|
+
* The hardware encoder for a target codec on `backend`, or the software one.
|
|
7425
|
+
* `'auto'` is NOT a backend identity (it is an instruction to ffmpeg), so it
|
|
7426
|
+
* maps to software encoding.
|
|
7427
|
+
*/
|
|
7428
|
+
function pickVideoEncoder(target, backend, useHardware) {
|
|
7429
|
+
const software = target === "h264" ? "libx264" : "libx265";
|
|
7430
|
+
if (!useHardware || backend === null || isSoftwareDecode(backend) || backend === "auto") return software;
|
|
7431
|
+
const ids = ENCODER_IDS_BY_BACKEND[backend.toLowerCase()];
|
|
7432
|
+
if (!ids) return software;
|
|
7433
|
+
return target === "h264" ? ids.h264 : ids.h265;
|
|
7434
|
+
}
|
|
7435
|
+
/** Map an `EncodeProfile.audio` to an audio plan. */
|
|
7436
|
+
function audioPlanFromEncodeProfile(audio) {
|
|
7437
|
+
if (audio === "passthrough") return { kind: "none" };
|
|
7438
|
+
if (audio.codec === "copy") return { kind: "copy" };
|
|
7439
|
+
return {
|
|
7440
|
+
kind: "encode",
|
|
7441
|
+
codec: audio.codec,
|
|
7442
|
+
...audio.bitrateKbps !== void 0 ? { bitrateKbps: audio.bitrateKbps } : {},
|
|
7443
|
+
...audio.sampleRateHz !== void 0 ? { sampleRateHz: audio.sampleRateHz } : {},
|
|
7444
|
+
...audio.channels !== void 0 ? { channels: audio.channels } : {}
|
|
7445
|
+
};
|
|
7446
|
+
}
|
|
7447
|
+
/**
|
|
7448
|
+
* Adapt an `EncodeProfile` (the operator/consumer-facing shape) into an
|
|
7449
|
+
* {@link FfmpegInvocation}. This is the ONLY bridge between the two models —
|
|
7450
|
+
* a second one is how the repo grew two argv builders that disagreed about
|
|
7451
|
+
* hardware.
|
|
7452
|
+
*
|
|
7453
|
+
* Smart video copy: when the source already speaks the requested codec the
|
|
7454
|
+
* encode block is elided entirely and ffmpeg runs as a re-muxer on the video
|
|
7455
|
+
* plane. Width / height / fps / bitrate in the profile are a downstream BUDGET,
|
|
7456
|
+
* not a forced rescale.
|
|
7457
|
+
*/
|
|
7458
|
+
function invocationFromEncodeProfile(input) {
|
|
7459
|
+
const v = input.profile.video;
|
|
7460
|
+
const shouldCopy = v.codec === "copy" || input.forceReencode !== true && v.codec === input.sourceCodec;
|
|
7461
|
+
const scale = v.width !== void 0 && v.height !== void 0 ? {
|
|
7462
|
+
mode: "fit",
|
|
7463
|
+
width: v.width,
|
|
7464
|
+
height: v.height
|
|
7465
|
+
} : null;
|
|
7466
|
+
const target = v.codec === "h265" ? "h265" : "h264";
|
|
7467
|
+
const video = shouldCopy ? { kind: "copy" } : {
|
|
7468
|
+
kind: "encode",
|
|
7469
|
+
encoder: pickVideoEncoder(target, input.decodeHwAccel, input.hardwareEncoders === true),
|
|
7470
|
+
scale,
|
|
7471
|
+
...v.preset !== void 0 ? { preset: v.preset } : {},
|
|
7472
|
+
...v.tune !== void 0 ? { tune: v.tune } : {},
|
|
7473
|
+
...v.profile !== void 0 ? { profile: v.profile } : {},
|
|
7474
|
+
...v.level !== void 0 ? { level: v.level } : {},
|
|
7475
|
+
...v.fps !== void 0 ? { fps: v.fps } : {},
|
|
7476
|
+
...v.gopFrames !== void 0 ? { gopFrames: v.gopFrames } : {},
|
|
7477
|
+
...v.bf !== void 0 ? { bf: v.bf } : {},
|
|
7478
|
+
...v.bitrateKbps !== void 0 ? { bitrateKbps: v.bitrateKbps } : {}
|
|
7479
|
+
};
|
|
7480
|
+
return {
|
|
7481
|
+
logLevel: input.logLevel ?? "error",
|
|
7482
|
+
decodeHwAccel: input.decodeHwAccel,
|
|
7483
|
+
input: {
|
|
7484
|
+
url: input.sourceUrl,
|
|
7485
|
+
rtspTransport: "tcp",
|
|
7486
|
+
fflags: ["+discardcorrupt"],
|
|
7487
|
+
...input.profile.inputArgs?.length ? { extraArgs: input.profile.inputArgs } : {}
|
|
7488
|
+
},
|
|
7489
|
+
video,
|
|
7490
|
+
audio: audioPlanFromEncodeProfile(input.profile.audio),
|
|
7491
|
+
threadCount: input.threadCount ?? 0,
|
|
7492
|
+
outputArgs: input.profile.outputArgs ?? [],
|
|
7493
|
+
sink: input.sink,
|
|
7494
|
+
...input.audioSidecar !== void 0 ? { audioSidecar: input.audioSidecar } : {}
|
|
7495
|
+
};
|
|
7496
|
+
}
|
|
7497
|
+
/**
|
|
7498
|
+
* The shape every live egress starts from: H.264 Baseline 3.1 at 720p25.
|
|
7499
|
+
* Baseline because it is the one profile every consumer in this repo decodes
|
|
7500
|
+
* (Echo, iOS, an old browser); 3.1 because that is what the SDPs advertise.
|
|
7501
|
+
*/
|
|
7502
|
+
var BASE_LIVE_EGRESS_PROFILE = {
|
|
7503
|
+
video: {
|
|
7504
|
+
codec: "h264",
|
|
7505
|
+
profile: "baseline",
|
|
7506
|
+
level: "3.1",
|
|
7507
|
+
width: 1280,
|
|
7508
|
+
height: 720,
|
|
7509
|
+
fps: 25,
|
|
7510
|
+
bitrateKbps: 2500,
|
|
7511
|
+
gopFrames: 25,
|
|
7512
|
+
bf: 0,
|
|
7513
|
+
preset: "veryfast",
|
|
7514
|
+
tune: "zerolatency"
|
|
7515
|
+
},
|
|
7516
|
+
audio: "passthrough"
|
|
7517
|
+
};
|
|
7518
|
+
({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
|
|
7519
|
+
({ ...BASE_LIVE_EGRESS_PROFILE });
|
|
7162
7520
|
/**
|
|
7163
7521
|
* Deep wiring healthcheck — snapshot of active reachability probes across
|
|
7164
7522
|
* every declared capability + widget of every installed plugin, on every
|
|
@@ -7209,6 +7567,105 @@ object({
|
|
|
7209
7567
|
})
|
|
7210
7568
|
});
|
|
7211
7569
|
/**
|
|
7570
|
+
* Per-camera FUNCTION SWITCHES — the one coherent on/off surface over the
|
|
7571
|
+
* pipeline functions an operator thinks in terms of.
|
|
7572
|
+
*
|
|
7573
|
+
* ## This file adds no state
|
|
7574
|
+
*
|
|
7575
|
+
* Every switch here is a VIEW onto an authority that already existed
|
|
7576
|
+
* ([D61](../../../../docs/decisions/adr-0062.md)). The whole point of the
|
|
7577
|
+
* group is that there is exactly one place each function is turned off, and
|
|
7578
|
+
* the group routes to it:
|
|
7579
|
+
*
|
|
7580
|
+
* | Switch | Authority | Proven "off stops the work" gate |
|
|
7581
|
+
* | --- | --- | --- |
|
|
7582
|
+
* | `stream-broker` | `deviceManager.setDisabled` | `StreamBrokerManager.reconcileAllCatalogs` releases the brokers; `ensureBroker` refuses re-creation |
|
|
7583
|
+
* | `object-detection` | `deviceManager.setWrapperActive('detection-pipeline')` | `PipelineSettingsStore.resolvePipelineForDevice` returns `{ steps: [], audio: null }` |
|
|
7584
|
+
* | `audio-analysis` | `deviceManager.setWrapperActive('audio-analysis')` | `AudioSubscriptionController.subscribeAudioStream` returns `null` before opening the stream |
|
|
7585
|
+
* | `recording` | `recording.setDeviceConfig` → `RecordingConfig.enabled` | `band-decision.shouldRecord` returns false; the controller detaches the device |
|
|
7586
|
+
* | `notifications` | `notificationRules.setDeviceMuted` | `NotificationCenter.evaluateAndEnqueue` returns before any rule is evaluated |
|
|
7587
|
+
*
|
|
7588
|
+
* The wrapper-binding pair is not a new idea: `legacy-migrations.ts` already
|
|
7589
|
+
* migrated the legacy `audioEnabled` / `pipelineEnabled` /
|
|
7590
|
+
* `motionDetectionEnabled` booleans ONTO `setWrapperActive`. The group is the
|
|
7591
|
+
* surface that decision never got.
|
|
7592
|
+
*
|
|
7593
|
+
* ## Two rules that are load-bearing
|
|
7594
|
+
*
|
|
7595
|
+
* - **Recording's switch is `enabled`, never the bands.** `bands` is the only
|
|
7596
|
+
* authored intent and `mode` is derived from it (`deriveRecordingMode`).
|
|
7597
|
+
* Expressing "off" by clearing bands destroys the operator's schedule and
|
|
7598
|
+
* turning the camera back on would then silently record nothing.
|
|
7599
|
+
* - **A switch that is off must be reported as off**, not merely produce
|
|
7600
|
+
* nothing. {@link CameraSwitch.enabled} is what a status surface renders as
|
|
7601
|
+
* "disabled by an operator" instead of "broken" — see
|
|
7602
|
+
* `CameraStatus.switchedOff`.
|
|
7603
|
+
*/
|
|
7604
|
+
/**
|
|
7605
|
+
* The five functions the operator named (2026-08-05). Deliberately NOT one id
|
|
7606
|
+
* per pipeline step: face recognition and plate/LPR are per-step toggles on
|
|
7607
|
+
* `pipelineOrchestrator.setCameraStepToggle` and belong in the pipeline
|
|
7608
|
+
* editor, not in a five-button safety group.
|
|
7609
|
+
*/
|
|
7610
|
+
var CameraSwitchIdSchema = _enum([
|
|
7611
|
+
"stream-broker",
|
|
7612
|
+
"object-detection",
|
|
7613
|
+
"audio-analysis",
|
|
7614
|
+
"recording",
|
|
7615
|
+
"notifications"
|
|
7616
|
+
]);
|
|
7617
|
+
/**
|
|
7618
|
+
* WHERE the switch's state actually lives. A discriminated union rather than a
|
|
7619
|
+
* string so both the writer (the orchestrator's `setCameraSwitch`) and any
|
|
7620
|
+
* reader can exhaustively narrow — and so "the group added a parallel map" is
|
|
7621
|
+
* a compile error rather than a review comment.
|
|
7622
|
+
*/
|
|
7623
|
+
var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
|
|
7624
|
+
object({ kind: literal("device-disabled") }),
|
|
7625
|
+
object({
|
|
7626
|
+
kind: literal("wrapper-binding"),
|
|
7627
|
+
capName: string()
|
|
7628
|
+
}),
|
|
7629
|
+
object({ kind: literal("recording-config") }),
|
|
7630
|
+
object({ kind: literal("notification-mute") })
|
|
7631
|
+
]);
|
|
7632
|
+
/**
|
|
7633
|
+
* Why a switch is not offered for this camera. Rendered instead of the
|
|
7634
|
+
* control, never as a dead control — an absent function and a broken one must
|
|
7635
|
+
* not look the same.
|
|
7636
|
+
*/
|
|
7637
|
+
var CameraSwitchUnavailableReasonSchema = _enum(["no-provider", "source-unreachable"]);
|
|
7638
|
+
/**
|
|
7639
|
+
* One switch, resolved for one camera.
|
|
7640
|
+
*
|
|
7641
|
+
* `label` and `costWhenOff` travel ON THE WIRE rather than being looked up
|
|
7642
|
+
* client-side: the viewer is a separate repository that does not import
|
|
7643
|
+
* `@camstack/types`, and a cost line duplicated in two clients is a cost line
|
|
7644
|
+
* that will disagree with itself. Five rows per camera is nothing.
|
|
7645
|
+
*/
|
|
7646
|
+
var CameraSwitchSchema = object({
|
|
7647
|
+
id: CameraSwitchIdSchema,
|
|
7648
|
+
label: string(),
|
|
7649
|
+
/**
|
|
7650
|
+
* What the operator LOSES while this is off, in one sentence. Required, not
|
|
7651
|
+
* optional: a switch that cannot say what it costs should not ship.
|
|
7652
|
+
*/
|
|
7653
|
+
costWhenOff: string(),
|
|
7654
|
+
/** False = do not render a control. `unavailableReason` says why. */
|
|
7655
|
+
available: boolean(),
|
|
7656
|
+
unavailableReason: CameraSwitchUnavailableReasonSchema.optional(),
|
|
7657
|
+
/** Current state. Meaningless when `available` is false — read it as `true`. */
|
|
7658
|
+
enabled: boolean(),
|
|
7659
|
+
authority: CameraSwitchAuthoritySchema
|
|
7660
|
+
});
|
|
7661
|
+
/** The whole group for one camera. */
|
|
7662
|
+
var CameraSwitchGroupSchema = object({
|
|
7663
|
+
deviceId: number().int(),
|
|
7664
|
+
switches: array(CameraSwitchSchema).readonly(),
|
|
7665
|
+
/** Unix ms when the group was composed server-side. */
|
|
7666
|
+
fetchedAt: number()
|
|
7667
|
+
});
|
|
7668
|
+
/**
|
|
7212
7669
|
* Ops-log — the durable, append-only operations audit shared by the
|
|
7213
7670
|
* recordings and events management surfaces.
|
|
7214
7671
|
*
|
|
@@ -7227,14 +7684,16 @@ var OpsLogOpSchema = _enum([
|
|
|
7227
7684
|
"manual-delete",
|
|
7228
7685
|
"rescan",
|
|
7229
7686
|
"retention-run",
|
|
7230
|
-
"relocate"
|
|
7687
|
+
"relocate",
|
|
7688
|
+
"orphan-audit"
|
|
7231
7689
|
]);
|
|
7232
7690
|
/** Why the operation ran. */
|
|
7233
7691
|
var OpsLogReasonSchema = _enum([
|
|
7234
7692
|
"retention",
|
|
7235
7693
|
"quota",
|
|
7236
7694
|
"manual",
|
|
7237
|
-
"operator"
|
|
7695
|
+
"operator",
|
|
7696
|
+
"maintenance"
|
|
7238
7697
|
]);
|
|
7239
7698
|
/** One audit row, shared verbatim by both domains. */
|
|
7240
7699
|
var OpsLogEntrySchema = object({
|
|
@@ -9221,6 +9680,100 @@ var RtpSourceSchema = object({
|
|
|
9221
9680
|
encoder: string(),
|
|
9222
9681
|
pipelineKey: string()
|
|
9223
9682
|
});
|
|
9683
|
+
/**
|
|
9684
|
+
* The encode request — **structured and serialisable, with NO raw-flag escape
|
|
9685
|
+
* hatch.** This is deliberate and it is the one lesson taken from
|
|
9686
|
+
* `getStreamWithCodec`: that method's `outputArgs: string[]` is simultaneously
|
|
9687
|
+
* its extensibility mechanism AND part of `pipelineKeyFor`'s sharing key, so
|
|
9688
|
+
* adding a flag silently forks the shared child, and two consumers that mean
|
|
9689
|
+
* the same thing but spell it differently never share. Here every knob is a
|
|
9690
|
+
* NAMED field: a new requirement becomes a schema field (and a codegen run),
|
|
9691
|
+
* never an opaque array.
|
|
9692
|
+
*
|
|
9693
|
+
* `inputArgs` / `outputArgs` are omitted from the profile for the same reason.
|
|
9694
|
+
* The operator-facing derived-stream transform editor still has them — that is
|
|
9695
|
+
* a different surface (`publishCameraStream({ kind: 'derived' })`) with a
|
|
9696
|
+
* different purpose (reshaping a badly-behaved SOURCE), and it is unchanged.
|
|
9697
|
+
*/
|
|
9698
|
+
var EgressEncodeSchema = EncodeProfileSchema.omit({
|
|
9699
|
+
inputArgs: true,
|
|
9700
|
+
outputArgs: true
|
|
9701
|
+
});
|
|
9702
|
+
/**
|
|
9703
|
+
* How the encoder is bounded. `'tight'` is a one-second VBV window for a
|
|
9704
|
+
* consumer whose budget is enforced per second (HomeKit); `'relaxed'` is two
|
|
9705
|
+
* seconds, letting a keyframe spike borrow from the next second (a browser,
|
|
9706
|
+
* an Echo). Named rather than numeric so the INTENT survives.
|
|
9707
|
+
*/
|
|
9708
|
+
var EgressRateControlSchema = _enum(["tight", "relaxed"]);
|
|
9709
|
+
var EgressTranscodeRequestSchema = object({
|
|
9710
|
+
deviceId: number().int().nonnegative(),
|
|
9711
|
+
/** Which published stream to read. */
|
|
9712
|
+
source: discriminatedUnion("kind", [object({
|
|
9713
|
+
kind: literal("profile"),
|
|
9714
|
+
profile: CamProfileSchema
|
|
9715
|
+
}), object({
|
|
9716
|
+
kind: literal("cam-stream"),
|
|
9717
|
+
camStreamId: string().min(1)
|
|
9718
|
+
})]),
|
|
9719
|
+
encode: EgressEncodeSchema,
|
|
9720
|
+
rateControl: EgressRateControlSchema.optional(),
|
|
9721
|
+
/**
|
|
9722
|
+
* `-bsf:v`. A consumer that negotiates its OWN SDP (HomeKit) cannot carry
|
|
9723
|
+
* out-of-band extradata and needs `dump_extra` on both the copy and encode
|
|
9724
|
+
* branches. Enumerated, not free text.
|
|
9725
|
+
*/
|
|
9726
|
+
bitstreamFilter: _enum([
|
|
9727
|
+
"dump_extra",
|
|
9728
|
+
"h264_mp4toannexb",
|
|
9729
|
+
"hevc_mp4toannexb"
|
|
9730
|
+
]).optional(),
|
|
9731
|
+
pixelFormat: _enum(["yuv420p", "nv12"]).optional(),
|
|
9732
|
+
/**
|
|
9733
|
+
* Operator/consumer override for decode hardware. ABSENT is the normal case
|
|
9734
|
+
* and the one that matters: the broker then resolves the backend from the
|
|
9735
|
+
* DECODER ADDON's per-node `probedBestHwaccel` (see
|
|
9736
|
+
* `@camstack/types` `ffmpeg/hwaccel.ts`), which is the ranking known to work
|
|
9737
|
+
* on this hardware — never the raw kernel resolver's qsv-first order.
|
|
9738
|
+
*/
|
|
9739
|
+
decodeHwAccel: _enum([
|
|
9740
|
+
"auto",
|
|
9741
|
+
"none",
|
|
9742
|
+
"videotoolbox",
|
|
9743
|
+
"vaapi",
|
|
9744
|
+
"qsv",
|
|
9745
|
+
"cuda"
|
|
9746
|
+
]).optional(),
|
|
9747
|
+
/**
|
|
9748
|
+
* Host to embed in the returned restream `url`. The broker mints hub-local
|
|
9749
|
+
* `127.0.0.1` URLs; a consumer on another node passes a cluster-resolvable
|
|
9750
|
+
* host (`NodeTopologyService.reachableHostByNode`) so the returned URL is
|
|
9751
|
+
* dialable from there. Same contract as `getStreamWithCodec.hostname` —
|
|
9752
|
+
* `substituteRtspHost` rewrites only the dial address, never the restreamer.
|
|
9753
|
+
*/
|
|
9754
|
+
hostname: string().optional(),
|
|
9755
|
+
/** Attribution for the broker panel. Never part of the sharing key. */
|
|
9756
|
+
tag: string().optional()
|
|
9757
|
+
});
|
|
9758
|
+
var EgressTranscodeSchema = object({
|
|
9759
|
+
/** Dial-able RTSP url (host-substituted when `hostname` was supplied). */
|
|
9760
|
+
url: string(),
|
|
9761
|
+
/** Release handle. Refcounted — the child dies when the last holder releases. */
|
|
9762
|
+
pipelineKey: string(),
|
|
9763
|
+
videoCodec: _enum(["H264", "H265"]),
|
|
9764
|
+
resolution: object({
|
|
9765
|
+
width: number().int().positive(),
|
|
9766
|
+
height: number().int().positive()
|
|
9767
|
+
}),
|
|
9768
|
+
transcoded: boolean(),
|
|
9769
|
+
encoder: string(),
|
|
9770
|
+
/**
|
|
9771
|
+
* The decode backend the child ACTUALLY ran with — `null` for software.
|
|
9772
|
+
* Returned rather than assumed: a consumer that asked for hardware and got
|
|
9773
|
+
* software needs to be able to see that without reading the broker's logs.
|
|
9774
|
+
*/
|
|
9775
|
+
decodeHwAccel: string().nullable()
|
|
9776
|
+
});
|
|
9224
9777
|
var streamBrokerCapability = {
|
|
9225
9778
|
name: "stream-broker",
|
|
9226
9779
|
scope: "system",
|
|
@@ -9392,6 +9945,40 @@ var streamBrokerCapability = {
|
|
|
9392
9945
|
auth: "admin"
|
|
9393
9946
|
}),
|
|
9394
9947
|
/**
|
|
9948
|
+
* THE ffmpeg primitive. Acquire an encoded stream matching a structured
|
|
9949
|
+
* encode plan; the broker builds the argv through the ONE builder
|
|
9950
|
+
* (`@camstack/types` `ffmpeg/invocation.ts`), resolves decode hardware from
|
|
9951
|
+
* the DECODER ADDON's per-node ranking, and returns a dialable RTSP url.
|
|
9952
|
+
*
|
|
9953
|
+
* **Refcounted and deduplicated on EXACT match.** Two requesters whose
|
|
9954
|
+
* requests produce the same `egressTranscodeSharingKey` receive the SAME
|
|
9955
|
+
* `pipelineKey` and the same child process. Nearly-identical requests are
|
|
9956
|
+
* NOT merged.
|
|
9957
|
+
*
|
|
9958
|
+
* **The handle is immutable.** There is deliberately no `reconfigure`
|
|
9959
|
+
* method and this one never accepts a `pipelineKey` alongside encode
|
|
9960
|
+
* parameters: a consumer whose requirements change releases and
|
|
9961
|
+
* re-acquires. A mutable shared handle is exactly what made Alexa's old
|
|
9962
|
+
* `derived:alexa-<id>` stream a co-tenant hazard — one consumer's
|
|
9963
|
+
* downgrade dragged every other consumer down with it.
|
|
9964
|
+
*
|
|
9965
|
+
* Placement: unpinned, `classifyCapRoute` serves this hub-in-process
|
|
9966
|
+
* (Priority 1). A caller that wants the transcode elsewhere passes
|
|
9967
|
+
* `nodePin(nodeId)` and a `hostname` it can dial.
|
|
9968
|
+
*/
|
|
9969
|
+
acquireEgressTranscode: method(EgressTranscodeRequestSchema, EgressTranscodeSchema, {
|
|
9970
|
+
kind: "mutation",
|
|
9971
|
+
auth: "admin"
|
|
9972
|
+
}),
|
|
9973
|
+
/** Drop one reference. The child dies when the last holder releases. */
|
|
9974
|
+
releaseEgressTranscode: method(object({ pipelineKey: string() }), object({
|
|
9975
|
+
released: boolean(),
|
|
9976
|
+
refcount: number().int().nonnegative()
|
|
9977
|
+
}), {
|
|
9978
|
+
kind: "mutation",
|
|
9979
|
+
auth: "admin"
|
|
9980
|
+
}),
|
|
9981
|
+
/**
|
|
9395
9982
|
* ── Decoded audio-chunk plane (Phase 5 / D9) ──────────────────────
|
|
9396
9983
|
*
|
|
9397
9984
|
* The serialisable replacement for the live-object `IStreamBroker.
|
|
@@ -14271,12 +14858,13 @@ var NcConditionsSchema = object({
|
|
|
14271
14858
|
* source; otherwise the subject's source must equal it. Legacy records
|
|
14272
14859
|
* with no stamped source are treated as `pipeline`. The union spans both
|
|
14273
14860
|
* record kinds — object events carry `pipeline` | `onboard`, synthetic
|
|
14274
|
-
* tracks carry `sensor
|
|
14861
|
+
* tracks carry `sensor` (a linked device) or `audio` (a D62 audio marker).
|
|
14275
14862
|
*/
|
|
14276
14863
|
source: _enum([
|
|
14277
14864
|
"pipeline",
|
|
14278
14865
|
"onboard",
|
|
14279
14866
|
"sensor",
|
|
14867
|
+
"audio",
|
|
14280
14868
|
"any"
|
|
14281
14869
|
]).optional(),
|
|
14282
14870
|
/**
|
|
@@ -14852,6 +15440,12 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
14852
15440
|
}), object({ success: literal(true) }), {
|
|
14853
15441
|
kind: "mutation",
|
|
14854
15442
|
auth: "admin"
|
|
15443
|
+
}), method(object({}), object({ mutedDeviceIds: array(number().int()).readonly() }), { auth: "admin" }), method(object({
|
|
15444
|
+
deviceId: number().int(),
|
|
15445
|
+
muted: boolean()
|
|
15446
|
+
}), object({ success: literal(true) }), {
|
|
15447
|
+
kind: "mutation",
|
|
15448
|
+
auth: "admin"
|
|
14855
15449
|
}), method(object({
|
|
14856
15450
|
rule: NcRuleInputSchema,
|
|
14857
15451
|
lookbackMinutes: number().int().min(1).max(1440).default(60)
|
|
@@ -15190,12 +15784,60 @@ var TrackAudioLabelSchema = object({
|
|
|
15190
15784
|
});
|
|
15191
15785
|
/**
|
|
15192
15786
|
* How a track was produced. `pipeline` (default / absent) = the spatial
|
|
15193
|
-
* detection+tracking pipeline.
|
|
15194
|
-
*
|
|
15195
|
-
*
|
|
15196
|
-
*
|
|
15787
|
+
* detection+tracking pipeline. Every OTHER value is a SYNTHETIC projection —
|
|
15788
|
+
* no positions, a single snapshot, and no bbox trajectory at all:
|
|
15789
|
+
*
|
|
15790
|
+
* - `sensor` — a linked sensor/control device state change.
|
|
15791
|
+
* - `audio` — an audio event on the camera itself that was anomalous for
|
|
15792
|
+
* THAT camera, loud, and heard while nothing visual was happening (D62).
|
|
15793
|
+
*
|
|
15794
|
+
* The spatial subsystems (tracker association, occupancy count, re-id /
|
|
15795
|
+
* embedding, resurrection) MUST skip every synthetic source. Test for that
|
|
15796
|
+
* with `isSpatialTrack`, which allow-lists `pipeline` — a `!== 'sensor'`
|
|
15797
|
+
* check silently readmits every source added after it was written.
|
|
15197
15798
|
*/
|
|
15198
|
-
var TrackSourceSchema = _enum([
|
|
15799
|
+
var TrackSourceSchema = _enum([
|
|
15800
|
+
"pipeline",
|
|
15801
|
+
"sensor",
|
|
15802
|
+
"audio"
|
|
15803
|
+
]);
|
|
15804
|
+
/**
|
|
15805
|
+
* Per-track OPERATOR flags — set by hand from the admin UI or the viewer, never
|
|
15806
|
+
* by the pipeline. Spread into `TrackSchema` and `KeyEventSchema` from one place
|
|
15807
|
+
* so the two surfaces cannot drift.
|
|
15808
|
+
*
|
|
15809
|
+
* **Absent ≠ false.** A track that has never been touched omits the field; an
|
|
15810
|
+
* explicitly un-flagged track carries `false`. Legacy rows written before the
|
|
15811
|
+
* columns existed read as absent, and a consumer that needs a boolean should say
|
|
15812
|
+
* `flag === true`, not `flag !== false`.
|
|
15813
|
+
*
|
|
15814
|
+
* What the flags DO is deliberately UNDEFINED at the time of writing: they are
|
|
15815
|
+
* operator curation, and the behaviour they drive will be specified separately.
|
|
15816
|
+
* In particular a `markForTrain` track is NOT pinned against retention — see
|
|
15817
|
+
* `docs/decisions/adr-0059.md` for why that is a store-level change, not a flag.
|
|
15818
|
+
*/
|
|
15819
|
+
var TrackFlagFields = {
|
|
15820
|
+
/** Operator marked this track as training material. */
|
|
15821
|
+
markForTrain: boolean().optional(),
|
|
15822
|
+
/** Operator marked this track for diagnostic attention. */
|
|
15823
|
+
debug: boolean().optional()
|
|
15824
|
+
};
|
|
15825
|
+
/**
|
|
15826
|
+
* The write half: a PARTIAL patch. An omitted key is left untouched, so setting
|
|
15827
|
+
* one flag can never clear the other — the toggles are independent and are
|
|
15828
|
+
* driven from three surfaces that do not know about each other.
|
|
15829
|
+
*/
|
|
15830
|
+
var TrackFlagsPatchSchema = object(TrackFlagFields);
|
|
15831
|
+
/**
|
|
15832
|
+
* The resolved flag state after a write. Both fields are REQUIRED here (absent
|
|
15833
|
+
* collapses to `false`) so a caller can drive a toggle's checked state off the
|
|
15834
|
+
* mutation result without a re-fetch.
|
|
15835
|
+
*/
|
|
15836
|
+
var TrackFlagsSchema = object({
|
|
15837
|
+
trackId: string(),
|
|
15838
|
+
markForTrain: boolean(),
|
|
15839
|
+
debug: boolean()
|
|
15840
|
+
});
|
|
15199
15841
|
var TrackSchema = object({
|
|
15200
15842
|
trackId: string(),
|
|
15201
15843
|
deviceId: number(),
|
|
@@ -15238,7 +15880,8 @@ var TrackSchema = object({
|
|
|
15238
15880
|
/** Normalized 0..1 trajectory envelope (see {@link TrackEnvelopeSchema}).
|
|
15239
15881
|
* Populated from the persisted envelope columns on historical reads;
|
|
15240
15882
|
* absent on legacy rows, dims-less tracks and active (in-RAM) tracks. */
|
|
15241
|
-
envelope: TrackEnvelopeSchema.optional()
|
|
15883
|
+
envelope: TrackEnvelopeSchema.optional(),
|
|
15884
|
+
...TrackFlagFields
|
|
15242
15885
|
});
|
|
15243
15886
|
var BaseEventFields = {
|
|
15244
15887
|
id: string(),
|
|
@@ -15451,7 +16094,8 @@ var KeyEventSchema = object({
|
|
|
15451
16094
|
/** Highest-confidence ObjectEvent id for the track (empty when none). */
|
|
15452
16095
|
bestEventId: string(),
|
|
15453
16096
|
/** Track lifetime in ms (lastSeen - firstSeen). */
|
|
15454
|
-
windowMs: number().optional()
|
|
16097
|
+
windowMs: number().optional(),
|
|
16098
|
+
...TrackFlagFields
|
|
15455
16099
|
});
|
|
15456
16100
|
object({
|
|
15457
16101
|
trackId: string(),
|
|
@@ -15537,7 +16181,31 @@ var RebuildObjectEmbeddingsInput = object({
|
|
|
15537
16181
|
since: number().optional(),
|
|
15538
16182
|
until: number().optional(),
|
|
15539
16183
|
/** Stop after this many tracks; the result reports whether more remain. */
|
|
15540
|
-
maxTracks: number().int().positive().optional()
|
|
16184
|
+
maxTracks: number().int().positive().optional(),
|
|
16185
|
+
/**
|
|
16186
|
+
* Run every embedding on THIS node instead of round-robining the fleet.
|
|
16187
|
+
*
|
|
16188
|
+
* Named `executeOnNodeId` and not `nodeId` on purpose: an inline `nodeId`
|
|
16189
|
+
* field in cap args is read by `parent-unowned-call.ts` as a ROUTING PIN, so
|
|
16190
|
+
* calling it that would pin the rebuild REQUEST itself to that node — the
|
|
16191
|
+
* rebuild orchestration lives on the hub, and only the per-track step runs
|
|
16192
|
+
* remotely. This field is data; the per-track pin is applied inside.
|
|
16193
|
+
*
|
|
16194
|
+
* Absent ⇒ round-robin over every online node whose runner can serve the
|
|
16195
|
+
* pinned model.
|
|
16196
|
+
*/
|
|
16197
|
+
executeOnNodeId: string().optional(),
|
|
16198
|
+
/**
|
|
16199
|
+
* Milliseconds to wait between tracks; omit for the built-in default, `0` to
|
|
16200
|
+
* run flat out.
|
|
16201
|
+
*
|
|
16202
|
+
* A rebuild is bulk maintenance on hub-main's single thread. Measured
|
|
16203
|
+
* 2026-08-06, an unpaced pass held that thread busy 82.2 s out of 120 and
|
|
16204
|
+
* pushed `nodes.topology` from 0.25 s to 26 s for 43 minutes. The value in
|
|
16205
|
+
* force is logged at start and finish so a deliberately slow pass reads
|
|
16206
|
+
* differently from a stalled one.
|
|
16207
|
+
*/
|
|
16208
|
+
pacingMs: number().int().nonnegative().optional()
|
|
15541
16209
|
});
|
|
15542
16210
|
/**
|
|
15543
16211
|
* Result of emptying the CLIP index.
|
|
@@ -15571,13 +16239,23 @@ var RebuildStatusSchema = object({
|
|
|
15571
16239
|
/** Tracks with no usable detection box. */
|
|
15572
16240
|
missingBbox: number(),
|
|
15573
16241
|
/**
|
|
15574
|
-
* Tracks
|
|
15575
|
-
*
|
|
15576
|
-
*
|
|
15577
|
-
*
|
|
15578
|
-
* otherwise read as a total engine outage.
|
|
16242
|
+
* Tracks an executing node REFUSED rather than broke on — an unreadable key
|
|
16243
|
+
* frame, a step that threw. Separate from `failed` because the remedy is
|
|
16244
|
+
* different, and because a whole camera silently contributing zero vectors
|
|
16245
|
+
* is the shape of failure a rebuild must never hide.
|
|
15579
16246
|
*/
|
|
15580
16247
|
notRunnable: number(),
|
|
16248
|
+
/**
|
|
16249
|
+
* The pass stopped because NO node could serve the pinned model.
|
|
16250
|
+
*
|
|
16251
|
+
* Distinct from `notRunnable` on purpose: that one says "this track was
|
|
16252
|
+
* refused", this one says "the cluster cannot do this work at all" — every
|
|
16253
|
+
* candidate node either lacks the `clip-embedding` step, lacks a build of the
|
|
16254
|
+
* pinned model for its engine format, or dropped out. The remedy is a model /
|
|
16255
|
+
* engine change, not a per-camera one. Non-zero here always comes with
|
|
16256
|
+
* `complete: false`.
|
|
16257
|
+
*/
|
|
16258
|
+
noCapableNode: number(),
|
|
15581
16259
|
failed: number(),
|
|
15582
16260
|
/** Set once a pass ends: true only when EVERYTHING was covered. */
|
|
15583
16261
|
complete: boolean().nullable(),
|
|
@@ -15649,7 +16327,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
15649
16327
|
}), {
|
|
15650
16328
|
kind: "mutation",
|
|
15651
16329
|
auth: "admin"
|
|
15652
|
-
}), method(object({
|
|
16330
|
+
}), method(object({
|
|
16331
|
+
/** Log/audit scope only — the trackId is globally unique on its own. */
|
|
16332
|
+
deviceId: number(),
|
|
16333
|
+
trackId: string(),
|
|
16334
|
+
flags: TrackFlagsPatchSchema
|
|
16335
|
+
}), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
|
|
15653
16336
|
kind: "query",
|
|
15654
16337
|
auth: "admin"
|
|
15655
16338
|
}), method(object({
|
|
@@ -16411,6 +17094,53 @@ var DetailResultSchema = object({
|
|
|
16411
17094
|
nativeFaceShortSidePx: number().optional()
|
|
16412
17095
|
});
|
|
16413
17096
|
/**
|
|
17097
|
+
* Why an executing node REFUSED a stateless step run (`runStatelessStep`).
|
|
17098
|
+
*
|
|
17099
|
+
* A refusal is a first-class answer, not an error, because the caller's next
|
|
17100
|
+
* move depends on WHICH one it is — and because "the pass produced nothing"
|
|
17101
|
+
* must never be reachable without a named, counted cause. The two tiers:
|
|
17102
|
+
*
|
|
17103
|
+
* - **node-level** (`unknown-step`, `model-not-servable`) — this node can
|
|
17104
|
+
* never serve this (step, model) pair. The caller drops it from its rotation
|
|
17105
|
+
* and retries the same work elsewhere; nothing about the work changes.
|
|
17106
|
+
* - **work-level** (`unreadable-frame`, `execution-failed`) — this node is
|
|
17107
|
+
* fine, this one request is not. Retrying it on another node would only
|
|
17108
|
+
* spread the same failure.
|
|
17109
|
+
*/
|
|
17110
|
+
var StatelessStepRefusalSchema = _enum([
|
|
17111
|
+
"unknown-step",
|
|
17112
|
+
"model-not-servable",
|
|
17113
|
+
"unreadable-frame",
|
|
17114
|
+
"execution-failed"
|
|
17115
|
+
]);
|
|
17116
|
+
/**
|
|
17117
|
+
* Answer to `runStatelessStep` — a discriminated union rather than a nullable
|
|
17118
|
+
* result, because `null` is exactly what made the camera-bound detail path
|
|
17119
|
+
* unable to tell "refused" from "never asked".
|
|
17120
|
+
*/
|
|
17121
|
+
var RunStatelessStepResultSchema = discriminatedUnion("kind", [object({
|
|
17122
|
+
kind: literal("ran"),
|
|
17123
|
+
/** The node that actually executed it — the pin, echoed back for the log. */
|
|
17124
|
+
nodeId: string(),
|
|
17125
|
+
/**
|
|
17126
|
+
* The model the step ran with.
|
|
17127
|
+
*
|
|
17128
|
+
* The node verified this exact id has a build for the format it dispatched
|
|
17129
|
+
* on BEFORE running, so the executor's format resolution returns it
|
|
17130
|
+
* unchanged. A caller that pinned a model must compare this field and
|
|
17131
|
+
* treat a mismatch as a refusal — the whole point of the pin is that a
|
|
17132
|
+
* pass writes one feature space.
|
|
17133
|
+
*/
|
|
17134
|
+
modelId: string(),
|
|
17135
|
+
details: array(DetailResultSchema)
|
|
17136
|
+
}), object({
|
|
17137
|
+
kind: literal("refused"),
|
|
17138
|
+
nodeId: string(),
|
|
17139
|
+
reason: StatelessStepRefusalSchema,
|
|
17140
|
+
/** Human-readable specifics — the format tried, the formats shipped, etc. */
|
|
17141
|
+
detail: string()
|
|
17142
|
+
})]);
|
|
17143
|
+
/**
|
|
16414
17144
|
* Per-camera tunable ranges + defaults. Single source of truth used
|
|
16415
17145
|
* by both the Zod data schema (validation + default fallback) and
|
|
16416
17146
|
* the device settings UI (slider min/max/step). Touch one place and
|
|
@@ -16855,7 +17585,77 @@ var pipelineRunnerCapability = {
|
|
|
16855
17585
|
cropJpeg: string().optional(),
|
|
16856
17586
|
parent: DetailParentSchema,
|
|
16857
17587
|
steps: array(string()).optional()
|
|
16858
|
-
}), object({ details: array(DetailResultSchema) }).nullable(), { kind: "mutation" })
|
|
17588
|
+
}), object({ details: array(DetailResultSchema) }).nullable(), { kind: "mutation" }),
|
|
17589
|
+
/**
|
|
17590
|
+
* Run ONE enrichment step against caller-supplied pixels, with NO camera
|
|
17591
|
+
* session — no attach, no frame handle, no device affinity.
|
|
17592
|
+
*
|
|
17593
|
+
* ## Why this exists next to `runDetailSubtree` and not inside it
|
|
17594
|
+
*
|
|
17595
|
+
* `runDetailSubtree` resolves its step tree from the LIVE ATTACH STATE. That
|
|
17596
|
+
* is correct for it: its reason to exist is a frame HANDLE, and a handle is
|
|
17597
|
+
* only resolvable while a decode session is open. But it makes the method
|
|
17598
|
+
* useless to anything walking history — cameras run `detectionMode:
|
|
17599
|
+
* 'on-motion'` and detach ~30 s after motion stops, so a fleet-wide
|
|
17600
|
+
* embedding rebuild found most cameras detached and rebuilt only whatever
|
|
17601
|
+
* happened to be attached at that second (measured 2026-08-06: 1125 tracks
|
|
17602
|
+
* scanned, 121 rebuilt, 601 refused as "camera is not attached").
|
|
17603
|
+
*
|
|
17604
|
+
* A caller that ships its own pixels needs none of that machinery. So this
|
|
17605
|
+
* method takes the three things a step actually needs — an image, a box, a
|
|
17606
|
+
* step id — resolves the step from the CATALOG rather than from an
|
|
17607
|
+
* attachment, and runs it on whichever node the caller pinned.
|
|
17608
|
+
*
|
|
17609
|
+
* ## What it deliberately keeps
|
|
17610
|
+
*
|
|
17611
|
+
* The crop rectangle is derived by the SAME `deriveDetailCropRect` call the
|
|
17612
|
+
* live detail plane uses, from the same cluster-wide convention
|
|
17613
|
+
* ([D52](../../../docs/decisions/adr-0052.md)). `frameJpeg` is a FULL frame,
|
|
17614
|
+
* never a pre-cut tile, for exactly the reason `runDetailSubtree` documents:
|
|
17615
|
+
* a caller that cuts is a second feature space.
|
|
17616
|
+
*
|
|
17617
|
+
* ## What it deliberately drops
|
|
17618
|
+
*
|
|
17619
|
+
* No device jump and no `deviceKey`: the jump roster comes from the attach
|
|
17620
|
+
* config, which does not exist here. The step runs on the node's default
|
|
17621
|
+
* engine. This is a maintenance path, not a latency-sensitive one.
|
|
17622
|
+
*
|
|
17623
|
+
* ## Routing
|
|
17624
|
+
*
|
|
17625
|
+
* NOT device-bound. `sourceDeviceId` is DIAGNOSTIC — the camera whose track
|
|
17626
|
+
* these pixels came from, so every log line on the executing node can carry
|
|
17627
|
+
* `tags: { deviceId }`. It is deliberately not named `deviceId`: that field
|
|
17628
|
+
* is a routing hint that would send the call to the camera's owning node,
|
|
17629
|
+
* which is the placement constraint this method exists to remove. Callers
|
|
17630
|
+
* choose the node with `nodePin(nodeId)`; unpinned, an unowned call is
|
|
17631
|
+
* served hub-in-process like any other singleton.
|
|
17632
|
+
*/
|
|
17633
|
+
runStatelessStep: method(object({
|
|
17634
|
+
/** Catalog step id, e.g. `clip-embedding`. */
|
|
17635
|
+
stepId: string(),
|
|
17636
|
+
/**
|
|
17637
|
+
* REQUIRED model pin. The node runs this exact model or refuses with
|
|
17638
|
+
* `model-not-servable` — it never substitutes a format default, because
|
|
17639
|
+
* a fleet pass that round-robins across nodes would then fill one index
|
|
17640
|
+
* from several encoders.
|
|
17641
|
+
*/
|
|
17642
|
+
modelId: string(),
|
|
17643
|
+
/** FULL FRAME, base64 JPEG. The runner cuts — do NOT pre-crop. */
|
|
17644
|
+
frameJpeg: string(),
|
|
17645
|
+
/**
|
|
17646
|
+
* The subject box, NORMALISED [0,1] against `frameJpeg`. Normalised on
|
|
17647
|
+
* purpose: the caller stores boxes against a downscaled analysis frame
|
|
17648
|
+
* while the stored key frame is native-resolution, and the only side
|
|
17649
|
+
* that reliably knows the image's pixel dimensions is the side that
|
|
17650
|
+
* decodes it. Denormalising here removes a second reader of the
|
|
17651
|
+
* dimensions and the class of mismatch that comes with it.
|
|
17652
|
+
*/
|
|
17653
|
+
bbox: NativeCropBboxSchema,
|
|
17654
|
+
/** Parent class of the subject (`person`, `vehicle`, …) — carried into the result. */
|
|
17655
|
+
className: string(),
|
|
17656
|
+
/** Camera the pixels came from. Diagnostics + log tags ONLY — never routing. */
|
|
17657
|
+
sourceDeviceId: number()
|
|
17658
|
+
}), RunStatelessStepResultSchema, { kind: "mutation" })
|
|
16859
17659
|
}
|
|
16860
17660
|
};
|
|
16861
17661
|
var CameraPipelineConfigSchema = object({
|
|
@@ -17155,6 +17955,20 @@ var CameraStatusSchema = object({
|
|
|
17155
17955
|
detection: CameraDetectionStatusSchema.nullable(),
|
|
17156
17956
|
audio: CameraAudioStatusSchema.nullable(),
|
|
17157
17957
|
recording: CameraRecordingStatusSchema.nullable(),
|
|
17958
|
+
/**
|
|
17959
|
+
* Per-camera function switches an OPERATOR has turned off
|
|
17960
|
+
* ([D61](../../../../docs/decisions/adr-0067.md)).
|
|
17961
|
+
*
|
|
17962
|
+
* This is the difference between DISABLED and BROKEN. A camera whose
|
|
17963
|
+
* `detection` block reports zero fps and whose `switchedOff` contains
|
|
17964
|
+
* `'object-detection'` was switched off by a person; the same camera with an
|
|
17965
|
+
* empty list is failing. Every status surface must render the two
|
|
17966
|
+
* differently — a quiet camera that looks identical to a dead one is the
|
|
17967
|
+
* silence-reads-as-never-happened trap this repo keeps paying for.
|
|
17968
|
+
*
|
|
17969
|
+
* Empty when nothing is off. Never contains a switch no provider offers.
|
|
17970
|
+
*/
|
|
17971
|
+
switchedOff: array(CameraSwitchIdSchema).readonly(),
|
|
17158
17972
|
/** Unix timestamp (ms) when this snapshot was composed server-side. */
|
|
17159
17973
|
fetchedAt: number()
|
|
17160
17974
|
});
|
|
@@ -17323,7 +18137,14 @@ method(object({
|
|
|
17323
18137
|
}), method(object({
|
|
17324
18138
|
deviceId: number(),
|
|
17325
18139
|
agentNodeId: string().optional()
|
|
17326
|
-
}), CameraPipelineConfigSchema), method(object({ deviceId: number() }),
|
|
18140
|
+
}), CameraPipelineConfigSchema), method(object({ deviceId: number() }), CameraSwitchGroupSchema), method(object({
|
|
18141
|
+
deviceId: number(),
|
|
18142
|
+
switchId: CameraSwitchIdSchema,
|
|
18143
|
+
enabled: boolean()
|
|
18144
|
+
}), CameraSwitchGroupSchema, {
|
|
18145
|
+
kind: "mutation",
|
|
18146
|
+
auth: "admin"
|
|
18147
|
+
}), method(object({ deviceId: number() }), CameraStatusSchema), method(object({ deviceIds: array(number()).optional() }), array(CameraStatusSchema).readonly()), method(_void(), array(PipelineTemplateSchema).readonly()), method(object({
|
|
17327
18148
|
name: string(),
|
|
17328
18149
|
description: string().optional(),
|
|
17329
18150
|
config: CameraPipelineConfigSchema
|
|
@@ -26472,6 +27293,12 @@ Object.freeze({
|
|
|
26472
27293
|
addonId: null,
|
|
26473
27294
|
access: "view"
|
|
26474
27295
|
},
|
|
27296
|
+
"notificationRules.listDeviceMutes": {
|
|
27297
|
+
capName: "notification-rules",
|
|
27298
|
+
capScope: "system",
|
|
27299
|
+
addonId: null,
|
|
27300
|
+
access: "view"
|
|
27301
|
+
},
|
|
26475
27302
|
"notificationRules.listRules": {
|
|
26476
27303
|
capName: "notification-rules",
|
|
26477
27304
|
capScope: "system",
|
|
@@ -26490,6 +27317,12 @@ Object.freeze({
|
|
|
26490
27317
|
addonId: null,
|
|
26491
27318
|
access: "create"
|
|
26492
27319
|
},
|
|
27320
|
+
"notificationRules.setDeviceMuted": {
|
|
27321
|
+
capName: "notification-rules",
|
|
27322
|
+
capScope: "system",
|
|
27323
|
+
addonId: null,
|
|
27324
|
+
access: "create"
|
|
27325
|
+
},
|
|
26493
27326
|
"notificationRules.setRuleEnabled": {
|
|
26494
27327
|
capName: "notification-rules",
|
|
26495
27328
|
capScope: "system",
|
|
@@ -26766,6 +27599,12 @@ Object.freeze({
|
|
|
26766
27599
|
addonId: null,
|
|
26767
27600
|
access: "view"
|
|
26768
27601
|
},
|
|
27602
|
+
"pipelineAnalytics.setTrackFlags": {
|
|
27603
|
+
capName: "pipeline-analytics",
|
|
27604
|
+
capScope: "device",
|
|
27605
|
+
addonId: null,
|
|
27606
|
+
access: "create"
|
|
27607
|
+
},
|
|
26769
27608
|
"pipelineAnalytics.wipeAllAnalytics": {
|
|
26770
27609
|
capName: "pipeline-analytics",
|
|
26771
27610
|
capScope: "device",
|
|
@@ -27072,6 +27911,12 @@ Object.freeze({
|
|
|
27072
27911
|
addonId: null,
|
|
27073
27912
|
access: "view"
|
|
27074
27913
|
},
|
|
27914
|
+
"pipelineOrchestrator.getCameraSwitches": {
|
|
27915
|
+
capName: "pipeline-orchestrator",
|
|
27916
|
+
capScope: "system",
|
|
27917
|
+
addonId: null,
|
|
27918
|
+
access: "view"
|
|
27919
|
+
},
|
|
27075
27920
|
"pipelineOrchestrator.getCapabilityBindings": {
|
|
27076
27921
|
capName: "pipeline-orchestrator",
|
|
27077
27922
|
capScope: "system",
|
|
@@ -27204,6 +28049,12 @@ Object.freeze({
|
|
|
27204
28049
|
addonId: null,
|
|
27205
28050
|
access: "create"
|
|
27206
28051
|
},
|
|
28052
|
+
"pipelineOrchestrator.setCameraSwitch": {
|
|
28053
|
+
capName: "pipeline-orchestrator",
|
|
28054
|
+
capScope: "system",
|
|
28055
|
+
addonId: null,
|
|
28056
|
+
access: "create"
|
|
28057
|
+
},
|
|
27207
28058
|
"pipelineOrchestrator.setCapabilityBinding": {
|
|
27208
28059
|
capName: "pipeline-orchestrator",
|
|
27209
28060
|
capScope: "system",
|
|
@@ -27294,6 +28145,12 @@ Object.freeze({
|
|
|
27294
28145
|
addonId: null,
|
|
27295
28146
|
access: "create"
|
|
27296
28147
|
},
|
|
28148
|
+
"pipelineRunner.runStatelessStep": {
|
|
28149
|
+
capName: "pipeline-runner",
|
|
28150
|
+
capScope: "system",
|
|
28151
|
+
addonId: null,
|
|
28152
|
+
access: "create"
|
|
28153
|
+
},
|
|
27297
28154
|
"plateGallery.assignPlate": {
|
|
27298
28155
|
capName: "plate-gallery",
|
|
27299
28156
|
capScope: "system",
|
|
@@ -28110,6 +28967,12 @@ Object.freeze({
|
|
|
28110
28967
|
addonId: null,
|
|
28111
28968
|
access: "create"
|
|
28112
28969
|
},
|
|
28970
|
+
"streamBroker.acquireEgressTranscode": {
|
|
28971
|
+
capName: "stream-broker",
|
|
28972
|
+
capScope: "system",
|
|
28973
|
+
addonId: null,
|
|
28974
|
+
access: "create"
|
|
28975
|
+
},
|
|
28113
28976
|
"streamBroker.assignProfile": {
|
|
28114
28977
|
capName: "stream-broker",
|
|
28115
28978
|
capScope: "system",
|
|
@@ -28218,6 +29081,12 @@ Object.freeze({
|
|
|
28218
29081
|
addonId: null,
|
|
28219
29082
|
access: "create"
|
|
28220
29083
|
},
|
|
29084
|
+
"streamBroker.releaseEgressTranscode": {
|
|
29085
|
+
capName: "stream-broker",
|
|
29086
|
+
capScope: "system",
|
|
29087
|
+
addonId: null,
|
|
29088
|
+
access: "create"
|
|
29089
|
+
},
|
|
28221
29090
|
"streamBroker.releaseStreamWithCodec": {
|
|
28222
29091
|
capName: "stream-broker",
|
|
28223
29092
|
capScope: "system",
|
|
@@ -29528,4 +30397,4 @@ function enumerateInferenceDevices(hw) {
|
|
|
29528
30397
|
return out;
|
|
29529
30398
|
}
|
|
29530
30399
|
//#endregion
|
|
29531
|
-
export {
|
|
30400
|
+
export { isEvent as $, evaluateZoneRules as A, recordingExportCapability as B, customAction as C, deriveRecordingMode as D, deriveDetailCropRect as E, motionDetectionCapability as F, webrtcSessionCapability as G, storageEvictableCapability as H, pickDetailCropConvention as I, CAM_PROFILE_ORDER as J, errMsg as K, pipelineExecutorCapability as L, invocationFromEncodeProfile as M, mapAudioLabelToMacro as N, detectionPipelineCapability as O, maskUrlCredentials as P, hydrateSchema as Q, pipelineRunnerCapability as R, cameraStreamsCapability as S, defineCustomActions as T, streamBrokerCapability as U, runtimeDevices as V, supportedRuntimes as W, DeviceType as X, DeviceFeature as Y, createEvent as Z, YAMNET_TO_MACRO as _, union as _t, COCO_80_LABELS as a, selectAssignedProfileSlots as at, audioAnalyzerCapability as b, DEFAULT_DETAIL_CROP_CONVENTION as c, array as ct, EncodeProfileSchema as d, lazy as dt, makeProfileBrokerId as et, ExportRecordSchema as f, literal as ft, RingBuffer as g, string as gt, RecordingConfigSchema as h, record as ht, AUDIO_PRESETS as i, parseProfileBrokerId as it, hfModelUrl as j, enumerateInferenceDevices as k, DEVICE_BACKEND_TO_FORMAT as l, boolean as lt, OpsLogEntrySchema as m, object as mt, AUDIO_BACKEND_CHOICES as n, nodePin as nt, COCO_TO_MACRO as o, sleep as ot, HF_BASE_URL as p, number as pt, BaseAddon as q, AUDIO_MACRO_LABELS as r, parseJsonUnknown as rt, DEFAULT_AUDIO_ANALYZER_CONFIG as s, _enum as st, APPLE_SA_TO_MACRO as t, makeSourceBrokerId as tt, EVENT_PAD_MS as u, discriminatedUnion as ut, addonWidgetsSourceCapability as v, EventCategory as vt, defaultDeviceFor as w, buildFfmpegArgs as x, audioAnalysisCapability as y, recordingCapability as z };
|