@camstack/addon-post-analysis 1.2.50 → 1.2.51
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/{dist-Bm5GpbGn.js → dist-CnSzKo7x.js} +993 -4
- package/dist/{dist-BhadCxSQ.mjs → dist-DTeVOZBt.mjs} +970 -4
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +2 -1
- package/dist/node-CnEKzHgS.js +653 -0
- package/dist/node-pIpcQ_aN.mjs +649 -0
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-B0B7ZGiM.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D-6lLgLp.mjs} +2 -2
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CJ3AVT6B.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-JSo2V9IT.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-BahNk1yZ.mjs → hostInit-DPOdauxs.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +14754 -11416
- package/dist/pipeline-analytics/index.mjs +14755 -11417
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/node-Co0YpEFr.mjs +0 -148
- package/dist/node-HEkeoPPw.js +0 -151
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "node:crypto";
|
|
1
2
|
//#region ../types/dist/event-category-41fKf-q9.mjs
|
|
2
3
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
3
4
|
EventCategory["SystemBoot"] = "system.boot";
|
|
@@ -7135,6 +7136,473 @@ function sleep(ms) {
|
|
|
7135
7136
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
7136
7137
|
}
|
|
7137
7138
|
//#endregion
|
|
7139
|
+
//#region ../types/dist/fmp4-box-splitter-B53u9-Nu.mjs
|
|
7140
|
+
var AUDIO_ENCODER_BY_CODEC = {
|
|
7141
|
+
opus: "libopus",
|
|
7142
|
+
aac: "aac",
|
|
7143
|
+
pcmu: "pcm_mulaw",
|
|
7144
|
+
pcma: "pcm_alaw"
|
|
7145
|
+
};
|
|
7146
|
+
/**
|
|
7147
|
+
* Camera-microphone audio, per codec. Lives HERE rather than in
|
|
7148
|
+
* `encode-defaults.ts` only to avoid an import cycle (`encode-defaults` depends
|
|
7149
|
+
* on these types); it is re-exported from there, which is where to read it.
|
|
7150
|
+
*
|
|
7151
|
+
* Every source in this repo is a mono camera mic. The former broker preset
|
|
7152
|
+
* encoded Opus at `channels: 2`, spending bitrate duplicating one channel —
|
|
7153
|
+
* that is the value this consolidation changed.
|
|
7154
|
+
*/
|
|
7155
|
+
var AUDIO_PRESETS = {
|
|
7156
|
+
aac: {
|
|
7157
|
+
kind: "encode",
|
|
7158
|
+
codec: "aac",
|
|
7159
|
+
bitrateKbps: 128,
|
|
7160
|
+
sampleRateHz: 48e3,
|
|
7161
|
+
channels: 1
|
|
7162
|
+
},
|
|
7163
|
+
opus: {
|
|
7164
|
+
kind: "encode",
|
|
7165
|
+
codec: "opus",
|
|
7166
|
+
bitrateKbps: 64,
|
|
7167
|
+
sampleRateHz: 48e3,
|
|
7168
|
+
channels: 1
|
|
7169
|
+
},
|
|
7170
|
+
pcmu: {
|
|
7171
|
+
kind: "encode",
|
|
7172
|
+
codec: "pcmu",
|
|
7173
|
+
sampleRateHz: 8e3,
|
|
7174
|
+
channels: 1
|
|
7175
|
+
}
|
|
7176
|
+
};
|
|
7177
|
+
/** `-hide_banner -loglevel <level>` — every ffmpeg site opens with this. */
|
|
7178
|
+
function logBannerArgs(level) {
|
|
7179
|
+
return [
|
|
7180
|
+
"-hide_banner",
|
|
7181
|
+
"-loglevel",
|
|
7182
|
+
level
|
|
7183
|
+
];
|
|
7184
|
+
}
|
|
7185
|
+
/** `true` when the resolved value means "decode in software" (⇒ no `-hwaccel`). */
|
|
7186
|
+
function isSoftwareDecode(decodeHwAccel) {
|
|
7187
|
+
return !decodeHwAccel || decodeHwAccel === "none" || decodeHwAccel === "copy";
|
|
7188
|
+
}
|
|
7189
|
+
/**
|
|
7190
|
+
* Every INPUT option, in order, terminated by `-i <url>`. Nothing may be
|
|
7191
|
+
* appended to this list by a caller — that is the whole point of the function.
|
|
7192
|
+
*/
|
|
7193
|
+
function buildInputArgs(input, decodeHwAccel) {
|
|
7194
|
+
const args = [];
|
|
7195
|
+
if (!isSoftwareDecode(decodeHwAccel)) args.push("-hwaccel", String(decodeHwAccel));
|
|
7196
|
+
if (input.extraArgs?.length) args.push(...input.extraArgs);
|
|
7197
|
+
if (input.analyzeDurationUs !== void 0) args.push("-analyzeduration", String(input.analyzeDurationUs));
|
|
7198
|
+
if (input.probeSizeBytes !== void 0) args.push("-probesize", String(input.probeSizeBytes));
|
|
7199
|
+
if (input.fflags?.length) for (const flag of input.fflags) args.push("-fflags", flag);
|
|
7200
|
+
if (input.rtspTransport) args.push("-rtsp_transport", input.rtspTransport);
|
|
7201
|
+
args.push("-i", input.url);
|
|
7202
|
+
return args;
|
|
7203
|
+
}
|
|
7204
|
+
/** The `-vf` filter args, or `[]` when a consumer `-vf` already claims the slot. */
|
|
7205
|
+
function buildVideoFilterArgs(scale, outputArgs) {
|
|
7206
|
+
if (!scale) return [];
|
|
7207
|
+
if (outputArgs.some((a) => a === "-vf")) return [];
|
|
7208
|
+
if (scale.mode === "exact") return ["-vf", `scale=${scale.width}:${scale.height}`];
|
|
7209
|
+
return ["-vf", `scale='min(${scale.width},iw)':'min(${scale.height},ih)':force_original_aspect_ratio=decrease:force_divisible_by=2`];
|
|
7210
|
+
}
|
|
7211
|
+
/** Rate-control args for an encode plan. */
|
|
7212
|
+
function buildRateControlArgs(video) {
|
|
7213
|
+
const kbps = video.bitrateKbps;
|
|
7214
|
+
if (kbps === void 0) return [];
|
|
7215
|
+
const rc = video.rateControl ?? {
|
|
7216
|
+
kind: "cap",
|
|
7217
|
+
vbvSeconds: 2
|
|
7218
|
+
};
|
|
7219
|
+
const bufsize = Math.max(1, Math.round(kbps * rc.vbvSeconds));
|
|
7220
|
+
return [
|
|
7221
|
+
...rc.kind === "cbr" ? ["-b:v", `${kbps}k`] : [],
|
|
7222
|
+
"-maxrate",
|
|
7223
|
+
`${kbps}k`,
|
|
7224
|
+
"-bufsize",
|
|
7225
|
+
`${bufsize}k`
|
|
7226
|
+
];
|
|
7227
|
+
}
|
|
7228
|
+
/** The whole video block (`-vf` … `-c:v` … knobs), after `-i`. */
|
|
7229
|
+
function buildVideoArgs(video, outputArgs) {
|
|
7230
|
+
if (video.kind === "copy") return [
|
|
7231
|
+
"-c:v",
|
|
7232
|
+
"copy",
|
|
7233
|
+
...video.bitstreamFilter ? ["-bsf:v", video.bitstreamFilter] : []
|
|
7234
|
+
];
|
|
7235
|
+
const args = [
|
|
7236
|
+
...buildVideoFilterArgs(video.scale, outputArgs),
|
|
7237
|
+
"-c:v",
|
|
7238
|
+
video.encoder
|
|
7239
|
+
];
|
|
7240
|
+
if (video.preset !== void 0) args.push("-preset", video.preset);
|
|
7241
|
+
if (video.tune !== void 0) args.push("-tune", video.tune);
|
|
7242
|
+
if (video.profile !== void 0) args.push("-profile:v", video.profile);
|
|
7243
|
+
if (video.level !== void 0) args.push("-level", video.level);
|
|
7244
|
+
if (video.pixelFormat !== void 0) args.push("-pix_fmt", video.pixelFormat);
|
|
7245
|
+
if (video.fps !== void 0) args.push("-r", String(video.fps));
|
|
7246
|
+
if (video.gopFrames !== void 0) args.push("-g", String(video.gopFrames));
|
|
7247
|
+
if (video.forceKeyFramesSeconds !== void 0) args.push("-force_key_frames", `expr:gte(t,n_forced*${video.forceKeyFramesSeconds})`);
|
|
7248
|
+
if (video.bf !== void 0) args.push("-bf", String(video.bf));
|
|
7249
|
+
args.push(...buildRateControlArgs(video));
|
|
7250
|
+
if (video.bitstreamFilter !== void 0) args.push("-bsf:v", video.bitstreamFilter);
|
|
7251
|
+
return args;
|
|
7252
|
+
}
|
|
7253
|
+
/** The whole audio block, after `-i`. */
|
|
7254
|
+
function buildAudioArgs(audio) {
|
|
7255
|
+
if (audio.kind === "none") return ["-an"];
|
|
7256
|
+
if (audio.kind === "copy") return ["-c:a", "copy"];
|
|
7257
|
+
const args = [];
|
|
7258
|
+
if (audio.filter !== void 0) args.push("-af", audio.filter);
|
|
7259
|
+
args.push("-c:a", AUDIO_ENCODER_BY_CODEC[audio.codec]);
|
|
7260
|
+
if (audio.application !== void 0) args.push("-application", audio.application);
|
|
7261
|
+
if (audio.frameDurationMs !== void 0) args.push("-frame_duration", String(audio.frameDurationMs));
|
|
7262
|
+
if (audio.globalHeader === true) args.push("-flags", "+global_header");
|
|
7263
|
+
if (audio.sampleRateHz !== void 0) args.push("-ar", String(audio.sampleRateHz));
|
|
7264
|
+
if (audio.bitrateKbps !== void 0) args.push("-b:a", `${audio.bitrateKbps}k`);
|
|
7265
|
+
if (audio.vbvBufferKbits !== void 0) args.push("-bufsize", `${audio.vbvBufferKbits}k`);
|
|
7266
|
+
if (audio.channels !== void 0) args.push("-ac", String(audio.channels));
|
|
7267
|
+
return args;
|
|
7268
|
+
}
|
|
7269
|
+
/** RTP output-leg args (`-payload_type`, `-ssrc`, `-sdp_file`, `-f rtp <url>`). */
|
|
7270
|
+
function buildRtpOutputArgs(out) {
|
|
7271
|
+
const args = [];
|
|
7272
|
+
if (out.payloadType !== void 0) args.push("-payload_type", String(out.payloadType));
|
|
7273
|
+
if (out.ssrc !== void 0) args.push("-ssrc", String(out.ssrc));
|
|
7274
|
+
if (out.sdpFile !== void 0) args.push("-sdp_file", out.sdpFile);
|
|
7275
|
+
args.push("-f", "rtp", out.url);
|
|
7276
|
+
return args;
|
|
7277
|
+
}
|
|
7278
|
+
/** `true` when the sink is a raw elementary bytestream that cannot mux audio. */
|
|
7279
|
+
function isElementaryVideoSink(sink) {
|
|
7280
|
+
return sink.kind === "stdout" && (sink.container === "h264" || sink.container === "hevc");
|
|
7281
|
+
}
|
|
7282
|
+
/**
|
|
7283
|
+
* The fragmented-MP4 muxer flags, in the order the recorder has proven them
|
|
7284
|
+
* (`recorder/addon/ffmpeg-args.ts` passes the same `movflags` string through
|
|
7285
|
+
* `-segment_format_options`, across every vendor in the fleet):
|
|
7286
|
+
*
|
|
7287
|
+
* - `frag_keyframe` — cut a fragment at each key frame, so every fragment
|
|
7288
|
+
* opens on a sync sample. HKSV's whole requirement.
|
|
7289
|
+
* - `empty_moov` — write `ftyp`+`moov` up front with no samples in it, which
|
|
7290
|
+
* is what makes the head a standalone INITIALISATION segment.
|
|
7291
|
+
* - `default_base_moof` — fragment offsets are self-relative, so a fragment is
|
|
7292
|
+
* demuxable without the bytes that preceded it. D31's byte-range read path
|
|
7293
|
+
* depends on exactly this property of the recorder's segments.
|
|
7294
|
+
*/
|
|
7295
|
+
var FMP4_MOVFLAGS = "+frag_keyframe+empty_moov+default_base_moof";
|
|
7296
|
+
/**
|
|
7297
|
+
* The terminal sink args for every non-`rtp-outputs` sink. Exhaustive over the
|
|
7298
|
+
* union so a new member cannot fall through to `['-f', container, 'pipe:1']`,
|
|
7299
|
+
* which is what a plain `container` read would have done for `mp4` — a valid
|
|
7300
|
+
* argv that writes a NON-fragmented, unseekable-to-a-pipe MP4 and produces one
|
|
7301
|
+
* unusable byte stream.
|
|
7302
|
+
*/
|
|
7303
|
+
function buildStdoutOrRtspSinkArgs(sink) {
|
|
7304
|
+
if (sink.kind === "rtsp-listen") return [
|
|
7305
|
+
"-f",
|
|
7306
|
+
"rtsp",
|
|
7307
|
+
"-rtsp_transport",
|
|
7308
|
+
"tcp",
|
|
7309
|
+
"-rtsp_flags",
|
|
7310
|
+
"listen",
|
|
7311
|
+
sink.url
|
|
7312
|
+
];
|
|
7313
|
+
if (sink.kind === "rtp-outputs") return [];
|
|
7314
|
+
return sink.container === "mp4" ? buildFmp4SinkArgs(sink) : [
|
|
7315
|
+
"-f",
|
|
7316
|
+
sink.container,
|
|
7317
|
+
"pipe:1"
|
|
7318
|
+
];
|
|
7319
|
+
}
|
|
7320
|
+
/**
|
|
7321
|
+
* How far BELOW the negotiated fragment length `-min_frag_duration` is set.
|
|
7322
|
+
*
|
|
7323
|
+
* `-min_frag_duration` refuses to cut before that much media has accumulated,
|
|
7324
|
+
* and then waits for the next key frame. Set to exactly `fragmentMs`, the
|
|
7325
|
+
* commonest camera configuration in existence — a key-frame grid EQUAL to the
|
|
7326
|
+
* requested fragment length — lands the deadline on the same instant as the key
|
|
7327
|
+
* frame, loses the race, and skips to the following one: **every fragment comes
|
|
7328
|
+
* out at twice the requested length.**
|
|
7329
|
+
*
|
|
7330
|
+
* Measured on the live fleet 2026-08-07, camera 615, `-c:v copy` (D84):
|
|
7331
|
+
*
|
|
7332
|
+
* | slot | GOP | `-min_frag_duration` | median gap |
|
|
7333
|
+
* | --- | --- | --- | --- |
|
|
7334
|
+
* | 1280×720 | 40 f @ 10 fps = 4.0 s | 4000 ms | **7944 ms** |
|
|
7335
|
+
* | 1280×720 | 40 f @ 10 fps = 4.0 s | 3600 ms | 3973 ms |
|
|
7336
|
+
* | 3840×2160 | 100 f @ 25 fps = 4.0 s | 4000 ms | 8042 ms |
|
|
7337
|
+
* | 3840×2160 | 100 f @ 25 fps = 4.0 s | 3600 ms | 3998 ms |
|
|
7338
|
+
*
|
|
7339
|
+
* A doubled fragment is not a cosmetic overshoot: HKSV requires every fragment
|
|
7340
|
+
* to be no longer than the length the controller SELECTED, so the shipped-but-
|
|
7341
|
+
* inert phase-1 sink would have violated the contract on its first real clip.
|
|
7342
|
+
*
|
|
7343
|
+
* 10 % is chosen against the two failures either side of it. Too small and
|
|
7344
|
+
* ordinary jitter (measured spread 3953-4096 ms) re-loses the race; too large
|
|
7345
|
+
* and a source with a key frame slightly EARLY than the grid gets cut there,
|
|
7346
|
+
* yielding a short fragment for no reason.
|
|
7347
|
+
*/
|
|
7348
|
+
var FMP4_MIN_FRAG_MARGIN = .9;
|
|
7349
|
+
/** `-movflags … -min_frag_duration <us> -f mp4 pipe:1`. */
|
|
7350
|
+
function buildFmp4SinkArgs(sink) {
|
|
7351
|
+
return [
|
|
7352
|
+
"-movflags",
|
|
7353
|
+
FMP4_MOVFLAGS,
|
|
7354
|
+
"-min_frag_duration",
|
|
7355
|
+
String(Math.max(0, Math.round(sink.fragmentMs * FMP4_MIN_FRAG_MARGIN * 1e3))),
|
|
7356
|
+
"-f",
|
|
7357
|
+
"mp4",
|
|
7358
|
+
"pipe:1"
|
|
7359
|
+
];
|
|
7360
|
+
}
|
|
7361
|
+
/**
|
|
7362
|
+
* A second output mapping source audio to RTP-over-UDP. `0:a:0?` makes the
|
|
7363
|
+
* audio optional so a source with no audio skips it instead of failing the
|
|
7364
|
+
* whole invocation.
|
|
7365
|
+
*/
|
|
7366
|
+
function buildAudioSidecarArgs(sidecar) {
|
|
7367
|
+
return [
|
|
7368
|
+
"-map",
|
|
7369
|
+
"0:a:0?",
|
|
7370
|
+
...buildAudioArgs(sidecar.codec === "pcma" ? {
|
|
7371
|
+
kind: "encode",
|
|
7372
|
+
codec: "pcma",
|
|
7373
|
+
sampleRateHz: 8e3,
|
|
7374
|
+
channels: 1
|
|
7375
|
+
} : AUDIO_PRESETS[sidecar.codec]),
|
|
7376
|
+
...buildRtpOutputArgs({
|
|
7377
|
+
url: sidecar.rtpUrl,
|
|
7378
|
+
sdpFile: sidecar.sdpFile
|
|
7379
|
+
})
|
|
7380
|
+
];
|
|
7381
|
+
}
|
|
7382
|
+
/**
|
|
7383
|
+
* Assemble the full ffmpeg argument list. Layout:
|
|
7384
|
+
*
|
|
7385
|
+
* -hide_banner -loglevel <level>
|
|
7386
|
+
* [-hwaccel <backend|auto>] ─┐ INPUT options — strictly before -i.
|
|
7387
|
+
* [<input.extraArgs>] │
|
|
7388
|
+
* [-fflags <flag>…] │
|
|
7389
|
+
* [-rtsp_transport tcp] │
|
|
7390
|
+
* -i <url> ─┘
|
|
7391
|
+
* <video block> <threads> <audio block> ─┐ OUTPUT options.
|
|
7392
|
+
* <consumer outputArgs verbatim> │
|
|
7393
|
+
* <sink> ─┘ terminal
|
|
7394
|
+
*/
|
|
7395
|
+
function buildFfmpegArgs(inv) {
|
|
7396
|
+
const head = [...logBannerArgs(inv.logLevel), ...buildInputArgs(inv.input, inv.decodeHwAccel)];
|
|
7397
|
+
const threadArgs = inv.threadCount > 0 ? ["-threads", String(inv.threadCount)] : [];
|
|
7398
|
+
if (inv.sink.kind === "rtp-outputs") {
|
|
7399
|
+
const videoLeg = inv.sink.video ? [
|
|
7400
|
+
"-an",
|
|
7401
|
+
"-map",
|
|
7402
|
+
"0:v:0",
|
|
7403
|
+
...buildVideoArgs(inv.video, inv.outputArgs),
|
|
7404
|
+
...threadArgs,
|
|
7405
|
+
...inv.outputArgs,
|
|
7406
|
+
...buildRtpOutputArgs(inv.sink.video)
|
|
7407
|
+
] : [];
|
|
7408
|
+
const audioLeg = inv.sink.audio ? [
|
|
7409
|
+
"-vn",
|
|
7410
|
+
"-map",
|
|
7411
|
+
"0:a:0?",
|
|
7412
|
+
...buildAudioArgs(inv.audio),
|
|
7413
|
+
...buildRtpOutputArgs(inv.sink.audio)
|
|
7414
|
+
] : [];
|
|
7415
|
+
return [
|
|
7416
|
+
...head,
|
|
7417
|
+
...videoLeg,
|
|
7418
|
+
...audioLeg
|
|
7419
|
+
];
|
|
7420
|
+
}
|
|
7421
|
+
const audioArgs = isElementaryVideoSink(inv.sink) ? ["-an"] : buildAudioArgs(inv.audio);
|
|
7422
|
+
const sinkArgs = buildStdoutOrRtspSinkArgs(inv.sink);
|
|
7423
|
+
return [
|
|
7424
|
+
...head,
|
|
7425
|
+
...buildVideoArgs(inv.video, inv.outputArgs),
|
|
7426
|
+
...threadArgs,
|
|
7427
|
+
...audioArgs,
|
|
7428
|
+
...inv.outputArgs,
|
|
7429
|
+
...sinkArgs,
|
|
7430
|
+
...inv.audioSidecar ? buildAudioSidecarArgs(inv.audioSidecar) : []
|
|
7431
|
+
];
|
|
7432
|
+
}
|
|
7433
|
+
var DEFAULT_MAX_UNIT_BYTES = 16 * 1024 * 1024;
|
|
7434
|
+
/** Header size for a normal box, and for one carrying a 64-bit `largesize`. */
|
|
7435
|
+
var BOX_HEADER_BYTES = 8;
|
|
7436
|
+
var LARGE_BOX_HEADER_BYTES = 16;
|
|
7437
|
+
var Fmp4BoxSplitter = class {
|
|
7438
|
+
maxUnitBytes;
|
|
7439
|
+
/** Bytes of the CURRENT unit plus any partial box after it. */
|
|
7440
|
+
buffer = new Uint8Array(0);
|
|
7441
|
+
/** Where the current unit starts inside {@link buffer}. */
|
|
7442
|
+
unitStart = 0;
|
|
7443
|
+
/** Where the box scanner has reached inside {@link buffer}. */
|
|
7444
|
+
cursor = 0;
|
|
7445
|
+
state = "init";
|
|
7446
|
+
nextSequence = 0;
|
|
7447
|
+
faultReason = null;
|
|
7448
|
+
interstitial = /* @__PURE__ */ new Set();
|
|
7449
|
+
constructor(options = {}) {
|
|
7450
|
+
this.maxUnitBytes = options.maxUnitBytes ?? DEFAULT_MAX_UNIT_BYTES;
|
|
7451
|
+
}
|
|
7452
|
+
/**
|
|
7453
|
+
* Non-null once the stream cannot be split. The splitter emits nothing
|
|
7454
|
+
* further, so a caller polls this to kill the child rather than watching a
|
|
7455
|
+
* silent stall — a fragmenter that quietly stops producing looks exactly like
|
|
7456
|
+
* a camera with no motion.
|
|
7457
|
+
*/
|
|
7458
|
+
get fault() {
|
|
7459
|
+
return this.faultReason;
|
|
7460
|
+
}
|
|
7461
|
+
/** Bytes currently held. The memory bound, observable rather than asserted. */
|
|
7462
|
+
get pendingBytes() {
|
|
7463
|
+
return this.buffer.length - this.unitStart;
|
|
7464
|
+
}
|
|
7465
|
+
/**
|
|
7466
|
+
* Top-level box types seen BETWEEN fragments and discarded — `mfra`, `free`,
|
|
7467
|
+
* a stray `sidx`. Reported rather than dropped in silence: they are legal and
|
|
7468
|
+
* useless to a fragment consumer, but a type nobody expected showing up here
|
|
7469
|
+
* is the first symptom of a muxer that is not writing what we think it is.
|
|
7470
|
+
*/
|
|
7471
|
+
get discardedInterstitialTypes() {
|
|
7472
|
+
return [...this.interstitial];
|
|
7473
|
+
}
|
|
7474
|
+
/**
|
|
7475
|
+
* Feed bytes; get back whatever units completed. Returns `[]` once faulted.
|
|
7476
|
+
*/
|
|
7477
|
+
push(chunk) {
|
|
7478
|
+
if (this.faultReason !== null || chunk.length === 0) return [];
|
|
7479
|
+
this.append(chunk);
|
|
7480
|
+
if (this.pendingBytes > this.maxUnitBytes) return this.fail(`a single fMP4 unit exceeded ${this.maxUnitBytes} bytes — this stream is not fragmented`);
|
|
7481
|
+
return this.drainBoxes();
|
|
7482
|
+
}
|
|
7483
|
+
append(chunk) {
|
|
7484
|
+
if (this.buffer.length === 0) {
|
|
7485
|
+
this.buffer = chunk.slice();
|
|
7486
|
+
return;
|
|
7487
|
+
}
|
|
7488
|
+
const next = new Uint8Array(this.buffer.length + chunk.length);
|
|
7489
|
+
next.set(this.buffer, 0);
|
|
7490
|
+
next.set(chunk, this.buffer.length);
|
|
7491
|
+
this.buffer = next;
|
|
7492
|
+
}
|
|
7493
|
+
/** Consume every COMPLETE top-level box now in the buffer. */
|
|
7494
|
+
drainBoxes() {
|
|
7495
|
+
const units = [];
|
|
7496
|
+
for (;;) {
|
|
7497
|
+
const header = this.readHeader();
|
|
7498
|
+
if (this.faultReason !== null) return units;
|
|
7499
|
+
if (header === null) break;
|
|
7500
|
+
if (this.cursor + header.totalBytes > this.buffer.length) break;
|
|
7501
|
+
const boxStart = this.cursor;
|
|
7502
|
+
const boxEnd = boxStart + header.totalBytes;
|
|
7503
|
+
this.cursor = boxEnd;
|
|
7504
|
+
const unit = this.consumeBox(header.type, boxStart, boxEnd);
|
|
7505
|
+
if (this.faultReason !== null) return units;
|
|
7506
|
+
if (unit !== null) units.push(unit);
|
|
7507
|
+
}
|
|
7508
|
+
this.compact();
|
|
7509
|
+
return units;
|
|
7510
|
+
}
|
|
7511
|
+
/**
|
|
7512
|
+
* Apply one box to the state machine. Returns a unit when this box CLOSED
|
|
7513
|
+
* one, `null` otherwise.
|
|
7514
|
+
*/
|
|
7515
|
+
consumeBox(type, boxStart, boxEnd) {
|
|
7516
|
+
if (this.state === "init") {
|
|
7517
|
+
if (type !== "moof") return null;
|
|
7518
|
+
if (boxStart === this.unitStart) {
|
|
7519
|
+
this.fail("a moof arrived before any initialisation box — there is no ftyp/moov to send");
|
|
7520
|
+
return null;
|
|
7521
|
+
}
|
|
7522
|
+
const init = this.emit("init", this.unitStart, boxStart);
|
|
7523
|
+
this.unitStart = boxStart;
|
|
7524
|
+
this.state = "fragment";
|
|
7525
|
+
return init;
|
|
7526
|
+
}
|
|
7527
|
+
if (this.state === "idle") {
|
|
7528
|
+
if (type !== "moof") {
|
|
7529
|
+
this.interstitial.add(type);
|
|
7530
|
+
this.unitStart = boxEnd;
|
|
7531
|
+
return null;
|
|
7532
|
+
}
|
|
7533
|
+
this.unitStart = boxStart;
|
|
7534
|
+
this.state = "fragment";
|
|
7535
|
+
return null;
|
|
7536
|
+
}
|
|
7537
|
+
if (type !== "mdat") return null;
|
|
7538
|
+
const fragment = this.emit("fragment", this.unitStart, boxEnd);
|
|
7539
|
+
this.unitStart = boxEnd;
|
|
7540
|
+
this.state = "idle";
|
|
7541
|
+
return fragment;
|
|
7542
|
+
}
|
|
7543
|
+
/**
|
|
7544
|
+
* Parse the header at {@link cursor}, or `null` when too few bytes have
|
|
7545
|
+
* arrived to know. Faults on a size the splitter cannot honour.
|
|
7546
|
+
*/
|
|
7547
|
+
readHeader() {
|
|
7548
|
+
const available = this.buffer.length - this.cursor;
|
|
7549
|
+
if (available < BOX_HEADER_BYTES) return null;
|
|
7550
|
+
const view = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength);
|
|
7551
|
+
const size = view.getUint32(this.cursor);
|
|
7552
|
+
const type = String.fromCharCode(this.buffer[this.cursor + 4] ?? 0, this.buffer[this.cursor + 5] ?? 0, this.buffer[this.cursor + 6] ?? 0, this.buffer[this.cursor + 7] ?? 0);
|
|
7553
|
+
if (size === 0) {
|
|
7554
|
+
this.fail(`box "${type}" declares size 0 (to EOF) — an unbounded box cannot be fragmented`);
|
|
7555
|
+
return null;
|
|
7556
|
+
}
|
|
7557
|
+
if (size === 1) {
|
|
7558
|
+
if (available < LARGE_BOX_HEADER_BYTES) return null;
|
|
7559
|
+
const large = view.getBigUint64(this.cursor + BOX_HEADER_BYTES);
|
|
7560
|
+
if (large > BigInt(this.maxUnitBytes)) {
|
|
7561
|
+
this.fail(`box "${type}" declares ${large} bytes, over the ${this.maxUnitBytes} byte bound`);
|
|
7562
|
+
return null;
|
|
7563
|
+
}
|
|
7564
|
+
return {
|
|
7565
|
+
type,
|
|
7566
|
+
totalBytes: Number(large)
|
|
7567
|
+
};
|
|
7568
|
+
}
|
|
7569
|
+
if (size < BOX_HEADER_BYTES) {
|
|
7570
|
+
this.fail(`box "${type}" declares an impossible size of ${size} bytes`);
|
|
7571
|
+
return null;
|
|
7572
|
+
}
|
|
7573
|
+
return {
|
|
7574
|
+
type,
|
|
7575
|
+
totalBytes: size
|
|
7576
|
+
};
|
|
7577
|
+
}
|
|
7578
|
+
emit(kind, start, end) {
|
|
7579
|
+
const sequence = this.nextSequence;
|
|
7580
|
+
this.nextSequence += 1;
|
|
7581
|
+
return {
|
|
7582
|
+
kind,
|
|
7583
|
+
data: this.buffer.slice(start, end),
|
|
7584
|
+
sequence
|
|
7585
|
+
};
|
|
7586
|
+
}
|
|
7587
|
+
/**
|
|
7588
|
+
* Drop everything already emitted or discarded. Without this the buffer is
|
|
7589
|
+
* the whole stream and the process dies in hours, not minutes.
|
|
7590
|
+
*/
|
|
7591
|
+
compact() {
|
|
7592
|
+
if (this.unitStart === 0) return;
|
|
7593
|
+
this.buffer = this.buffer.slice(this.unitStart);
|
|
7594
|
+
this.cursor -= this.unitStart;
|
|
7595
|
+
this.unitStart = 0;
|
|
7596
|
+
}
|
|
7597
|
+
fail(reason) {
|
|
7598
|
+
this.faultReason = reason;
|
|
7599
|
+
this.buffer = new Uint8Array(0);
|
|
7600
|
+
this.unitStart = 0;
|
|
7601
|
+
this.cursor = 0;
|
|
7602
|
+
return [];
|
|
7603
|
+
}
|
|
7604
|
+
};
|
|
7605
|
+
//#endregion
|
|
7138
7606
|
//#region ../types/dist/err-msg-IQTHeDzc.mjs
|
|
7139
7607
|
/**
|
|
7140
7608
|
import { errMsg } from '@camstack/types'
|
|
@@ -15679,6 +16147,60 @@ var TrackFlagsSchema = object({
|
|
|
15679
16147
|
* `trained` without a re-fetch. */
|
|
15680
16148
|
retrainStatus: RetrainStatusSchema
|
|
15681
16149
|
});
|
|
16150
|
+
union([literal(1), literal(2)]);
|
|
16151
|
+
/**
|
|
16152
|
+
* WHO decided a label, and when. Carried per tier so a value can be traced to
|
|
16153
|
+
* the step and model that produced it — which is what makes the write rule
|
|
16154
|
+
* arguable after the fact ("why is 592's label `dog` and not `Canis lupus`?")
|
|
16155
|
+
* and what lets a migrated, UNATTRIBUTED value be told apart from a real one.
|
|
16156
|
+
*
|
|
16157
|
+
* `stepId` is the pipeline step id (`animal-classifier`, `bird-classifier`,
|
|
16158
|
+
* `plate-ocr`, `face-embedding`, `object-detection`), or the sentinel
|
|
16159
|
+
* `migration:4g` for a value the 4g migration moved from the single-slot era —
|
|
16160
|
+
* that value has no provenance, and the write rule lets ANY properly-attributed
|
|
16161
|
+
* write of the same tier replace it regardless of score.
|
|
16162
|
+
*/
|
|
16163
|
+
var LabelAttributionSchema = object({
|
|
16164
|
+
stepId: string(),
|
|
16165
|
+
modelId: string().optional(),
|
|
16166
|
+
decidedAt: number()
|
|
16167
|
+
});
|
|
16168
|
+
/**
|
|
16169
|
+
* The TIERED label model (roadmap 4g), spread into `TrackSchema` and
|
|
16170
|
+
* `ObjectEventSchema` from ONE place so the two surfaces cannot drift — a
|
|
16171
|
+
* track and its events always answer the same question the same way.
|
|
16172
|
+
*
|
|
16173
|
+
* Two scalar columns, not an array: every consumer wants "the coarse one" or
|
|
16174
|
+
* "the fine one", and an array made both a scan. `label` is tier 1, `subLabel`
|
|
16175
|
+
* is tier 2, and each carries its own score + attribution.
|
|
16176
|
+
*
|
|
16177
|
+
* **Reading it.** What a human should be shown is `subLabel ?? label` — the
|
|
16178
|
+
* finest thing known. Before 4g the single `label` column held the finest
|
|
16179
|
+
* value, so a consumer that has not been updated reads the tier-1 slot and
|
|
16180
|
+
* shows nothing on a species-only row; that is why the migration puts every
|
|
16181
|
+
* pre-4g value in tier 2 (it cannot regress a display that reads the fallback)
|
|
16182
|
+
* and why the read surfaces were changed in the same train.
|
|
16183
|
+
*
|
|
16184
|
+
* **Writing it.** The slots are independent, which is the whole point: a
|
|
16185
|
+
* tier-1 write (`bird`) can never overwrite a tier-2 value (`Turdus
|
|
16186
|
+
* migratorius`), so fineness cannot regress by construction. Within a tier the
|
|
16187
|
+
* higher score wins. One rule, one implementation — see
|
|
16188
|
+
* `pipeline/label-tier.ts` in addon-post-analysis.
|
|
16189
|
+
*/
|
|
16190
|
+
var TieredLabelFields = {
|
|
16191
|
+
/** Tier 1 — the sub-class. See {@link LabelTierSchema}. */
|
|
16192
|
+
label: string().optional(),
|
|
16193
|
+
/** Confidence of the tier-1 value, as reported by the deciding step. */
|
|
16194
|
+
labelScore: number().optional(),
|
|
16195
|
+
/** Provenance of the tier-1 value. See {@link LabelAttributionSchema}. */
|
|
16196
|
+
labelMeta: LabelAttributionSchema.optional(),
|
|
16197
|
+
/** Tier 2 — the instance. See {@link LabelTierSchema}. */
|
|
16198
|
+
subLabel: string().optional(),
|
|
16199
|
+
/** Confidence of the tier-2 value, as reported by the deciding step. */
|
|
16200
|
+
subLabelScore: number().optional(),
|
|
16201
|
+
/** Provenance of the tier-2 value. See {@link LabelAttributionSchema}. */
|
|
16202
|
+
subLabelMeta: LabelAttributionSchema.optional()
|
|
16203
|
+
};
|
|
15682
16204
|
/** Per-camera slice of a training-export estimate. */
|
|
15683
16205
|
var TrainingExportDeviceTotalsSchema = object({
|
|
15684
16206
|
deviceId: number(),
|
|
@@ -15703,7 +16225,7 @@ var TrackSchema = object({
|
|
|
15703
16225
|
trackId: string(),
|
|
15704
16226
|
deviceId: number(),
|
|
15705
16227
|
className: string(),
|
|
15706
|
-
|
|
16228
|
+
...TieredLabelFields,
|
|
15707
16229
|
producingDeviceName: string().optional(),
|
|
15708
16230
|
/** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
|
|
15709
16231
|
source: TrackSourceSchema.optional(),
|
|
@@ -15816,7 +16338,7 @@ var ObjectEventSchema = object({
|
|
|
15816
16338
|
/** Omitted in slim projection. */
|
|
15817
16339
|
trackId: string().optional(),
|
|
15818
16340
|
className: string(),
|
|
15819
|
-
|
|
16341
|
+
...TieredLabelFields,
|
|
15820
16342
|
/** Omitted in slim projection. */
|
|
15821
16343
|
confidence: number().optional(),
|
|
15822
16344
|
/** Heavy JSON — omitted in slim projection. */
|
|
@@ -15897,6 +16419,173 @@ var MediaFileSchema = object({
|
|
|
15897
16419
|
* stored blob and a `?variant=thumb` rendering without fetching either.
|
|
15898
16420
|
*/
|
|
15899
16421
|
var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
|
|
16422
|
+
/**
|
|
16423
|
+
* The MACRO tier of an annotation — a CLOSED set.
|
|
16424
|
+
*
|
|
16425
|
+
* This is what the exported detector predicts, so a typo here is a new class
|
|
16426
|
+
* with one example in it. `label` and `subLabel` are open strings by contrast:
|
|
16427
|
+
* the whole point of the page is teaching the model things it does not know
|
|
16428
|
+
* yet, and constraining that vocabulary would make it useless.
|
|
16429
|
+
*
|
|
16430
|
+
* A macro class is NEVER a label. The provider refuses a write whose `label` or
|
|
16431
|
+
* `subLabel` is one of these values, in any casing, because once `person`
|
|
16432
|
+
* exists in both tiers "every person box" stops being answerable without
|
|
16433
|
+
* knowing every string anyone ever typed — and the damage is retroactive.
|
|
16434
|
+
*/
|
|
16435
|
+
var RetrainMacroClassSchema = _enum([
|
|
16436
|
+
"person",
|
|
16437
|
+
"vehicle",
|
|
16438
|
+
"animal",
|
|
16439
|
+
"package",
|
|
16440
|
+
"face",
|
|
16441
|
+
"plate"
|
|
16442
|
+
]);
|
|
16443
|
+
/** A subject to learn, or a phantom to unlearn (taught by OMISSION). */
|
|
16444
|
+
var RetrainAnnotationKindSchema = _enum(["subject", "model_error"]);
|
|
16445
|
+
/** Did a human draw this box, or did the assist propose it? */
|
|
16446
|
+
var RetrainAnnotationSourceSchema = _enum(["operator", "assist"]);
|
|
16447
|
+
/** Normalised `[0,1]` rectangle against the FULL frame — the canonical form. */
|
|
16448
|
+
var RetrainBboxSchema = object({
|
|
16449
|
+
x: number(),
|
|
16450
|
+
y: number(),
|
|
16451
|
+
w: number(),
|
|
16452
|
+
h: number()
|
|
16453
|
+
});
|
|
16454
|
+
/**
|
|
16455
|
+
* One annotated subject.
|
|
16456
|
+
*
|
|
16457
|
+
* `bbox` is normalised against the full frame, ALWAYS. The per-model shapes
|
|
16458
|
+
* (letterboxed root / zone-cropped package / subject-cropped classifier) are
|
|
16459
|
+
* derived from it at export and never stored — storing them is how one feature
|
|
16460
|
+
* space ends up holding two crops of the same subject (D52).
|
|
16461
|
+
*/
|
|
16462
|
+
var RetrainAnnotationSchema = object({
|
|
16463
|
+
id: string(),
|
|
16464
|
+
trackId: string(),
|
|
16465
|
+
deviceId: number(),
|
|
16466
|
+
/** The COPY in retrain storage — never the source track's media key. */
|
|
16467
|
+
mediaKey: string(),
|
|
16468
|
+
bbox: RetrainBboxSchema,
|
|
16469
|
+
macroClass: RetrainMacroClassSchema,
|
|
16470
|
+
label: string().optional(),
|
|
16471
|
+
subLabel: string().optional(),
|
|
16472
|
+
kind: RetrainAnnotationKindSchema,
|
|
16473
|
+
source: RetrainAnnotationSourceSchema,
|
|
16474
|
+
/** Which model proposed this box — or, on a `model_error`, drew the phantom. */
|
|
16475
|
+
assistModelId: string().optional(),
|
|
16476
|
+
assistScore: number().optional(),
|
|
16477
|
+
exportedInBatch: string().optional(),
|
|
16478
|
+
createdAt: number()
|
|
16479
|
+
});
|
|
16480
|
+
/** The write form — the server owns `id`, `createdAt` and the frame binding. */
|
|
16481
|
+
var RetrainAnnotationDraftSchema = RetrainAnnotationSchema.omit({
|
|
16482
|
+
id: true,
|
|
16483
|
+
trackId: true,
|
|
16484
|
+
deviceId: true,
|
|
16485
|
+
mediaKey: true,
|
|
16486
|
+
createdAt: true,
|
|
16487
|
+
exportedInBatch: true
|
|
16488
|
+
});
|
|
16489
|
+
/** A track sitting in `staging`, with everything the worklist needs to rank it. */
|
|
16490
|
+
var RetrainTrackSchema = object({
|
|
16491
|
+
trackId: string(),
|
|
16492
|
+
deviceId: number(),
|
|
16493
|
+
className: string(),
|
|
16494
|
+
label: string().optional(),
|
|
16495
|
+
firstSeen: number(),
|
|
16496
|
+
lastSeen: number(),
|
|
16497
|
+
/** How many frames the dataset already holds from this track. */
|
|
16498
|
+
frameCount: number().int(),
|
|
16499
|
+
/** How many subjects have been annotated on those frames. `0` with
|
|
16500
|
+
* `frameCount: 0` is exactly "staging, still to work". */
|
|
16501
|
+
annotationCount: number().int()
|
|
16502
|
+
});
|
|
16503
|
+
/** A frame the picker may offer — an index row, no blob was read to produce it. */
|
|
16504
|
+
var RetrainFrameCandidateSchema = object({
|
|
16505
|
+
mediaKey: string(),
|
|
16506
|
+
kind: MediaFileKindEnum,
|
|
16507
|
+
timestamp: number(),
|
|
16508
|
+
sizeBytes: number().int(),
|
|
16509
|
+
/** A copy of this original already exists — selecting it is free and cannot
|
|
16510
|
+
* fail, whatever became of the original. */
|
|
16511
|
+
copied: boolean()
|
|
16512
|
+
});
|
|
16513
|
+
/** A frame the dataset OWNS: bytes copied at selection time. */
|
|
16514
|
+
var RetrainFrameSchema = object({
|
|
16515
|
+
frameId: string(),
|
|
16516
|
+
deviceId: number(),
|
|
16517
|
+
trackId: string(),
|
|
16518
|
+
/** Provenance only. It may already point at nothing — that is expected. */
|
|
16519
|
+
sourceMediaKey: string(),
|
|
16520
|
+
sourceKind: MediaFileKindEnum,
|
|
16521
|
+
sizeBytes: number().int(),
|
|
16522
|
+
width: number().int(),
|
|
16523
|
+
height: number().int(),
|
|
16524
|
+
copiedAt: number()
|
|
16525
|
+
});
|
|
16526
|
+
/** Why a copy-on-select could not be honoured — named, never a silent skip. */
|
|
16527
|
+
var RetrainCopyRefusalSchema = _enum([
|
|
16528
|
+
"source-missing",
|
|
16529
|
+
"unreadable-image",
|
|
16530
|
+
"write-failed"
|
|
16531
|
+
]);
|
|
16532
|
+
var RetrainFrameSelectionSchema = object({
|
|
16533
|
+
copied: array(RetrainFrameSchema).readonly(),
|
|
16534
|
+
refused: array(object({
|
|
16535
|
+
sourceMediaKey: string(),
|
|
16536
|
+
reason: RetrainCopyRefusalSchema
|
|
16537
|
+
})).readonly()
|
|
16538
|
+
});
|
|
16539
|
+
var RetrainFrameListSchema = object({
|
|
16540
|
+
candidates: array(RetrainFrameCandidateSchema).readonly(),
|
|
16541
|
+
copies: array(RetrainFrameSchema).readonly(),
|
|
16542
|
+
/** What the page pre-selects — the native key frame when one survives. */
|
|
16543
|
+
autoPickMediaKey: string().optional()
|
|
16544
|
+
});
|
|
16545
|
+
/** What the operator asked the assist to look for. */
|
|
16546
|
+
var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
|
|
16547
|
+
kind: literal("package"),
|
|
16548
|
+
zone: RetrainBboxSchema.optional()
|
|
16549
|
+
}), object({
|
|
16550
|
+
kind: literal("objects"),
|
|
16551
|
+
modelId: string(),
|
|
16552
|
+
minScore: number().optional()
|
|
16553
|
+
})]);
|
|
16554
|
+
/**
|
|
16555
|
+
* The assist's answer — a discriminated union, because "the model saw nothing"
|
|
16556
|
+
* and "this node cannot run that model" lead to different next moves and a
|
|
16557
|
+
* nullable result cannot tell them apart.
|
|
16558
|
+
*/
|
|
16559
|
+
var RetrainAssistResultSchema = discriminatedUnion("kind", [object({
|
|
16560
|
+
kind: literal("proposed"),
|
|
16561
|
+
modelId: string(),
|
|
16562
|
+
stepId: string(),
|
|
16563
|
+
minScore: number(),
|
|
16564
|
+
/** Drafts, ready to edit. `source: 'assist'` until the operator touches one. */
|
|
16565
|
+
proposals: array(RetrainAnnotationDraftSchema).readonly(),
|
|
16566
|
+
/** Returned by the runner but removed by the threshold. */
|
|
16567
|
+
belowThreshold: number().int()
|
|
16568
|
+
}), object({
|
|
16569
|
+
kind: literal("refused"),
|
|
16570
|
+
/** `no-zone` is ours; the rest are the runner's own refusal vocabulary. */
|
|
16571
|
+
reason: string(),
|
|
16572
|
+
detail: string().optional()
|
|
16573
|
+
})]);
|
|
16574
|
+
/** The outcome of a lifecycle move owned by the retrain page. */
|
|
16575
|
+
var RetrainTransitionResultSchema = object({
|
|
16576
|
+
trackId: string(),
|
|
16577
|
+
/** Where the track ended up, whatever happened. */
|
|
16578
|
+
retrainStatus: RetrainStatusSchema,
|
|
16579
|
+
/** `false` ⇒ the move was refused or was a no-op; `reason` says which. */
|
|
16580
|
+
changed: boolean(),
|
|
16581
|
+
reason: _enum([
|
|
16582
|
+
"unknown-track",
|
|
16583
|
+
"no-frames-copied",
|
|
16584
|
+
"not-staging",
|
|
16585
|
+
"not-trained",
|
|
16586
|
+
"unchanged"
|
|
16587
|
+
]).optional()
|
|
16588
|
+
});
|
|
15900
16589
|
var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
|
|
15901
16590
|
var MAX_EVENT_QUERY_LIMIT = 5e3;
|
|
15902
16591
|
var DeviceEventQueryInput = object({
|
|
@@ -15951,7 +16640,7 @@ var KeyEventSchema = object({
|
|
|
15951
16640
|
/** Track start time (firstSeen). */
|
|
15952
16641
|
timestamp: number(),
|
|
15953
16642
|
className: string(),
|
|
15954
|
-
|
|
16643
|
+
...TieredLabelFields,
|
|
15955
16644
|
importance: number(),
|
|
15956
16645
|
/** Highest-confidence ObjectEvent id for the track (empty when none). */
|
|
15957
16646
|
bestEventId: string(),
|
|
@@ -16442,6 +17131,201 @@ var pipelineAnalyticsCapability = {
|
|
|
16442
17131
|
kind: "query",
|
|
16443
17132
|
auth: "admin"
|
|
16444
17133
|
}),
|
|
17134
|
+
/**
|
|
17135
|
+
* The staging worklist for one camera, or for every camera that has one.
|
|
17136
|
+
*
|
|
17137
|
+
* Fetched ON DEMAND, over the staging set only — the page never scans
|
|
17138
|
+
* history, because making the working set small is the entire purpose of
|
|
17139
|
+
* the mark. Each row carries how many frames the dataset already holds from
|
|
17140
|
+
* the track and how many subjects were annotated on them, so
|
|
17141
|
+
* `frameCount: 0` reads as "still to work" without a second call per track.
|
|
17142
|
+
*
|
|
17143
|
+
* `auth: 'admin'`, unlike the viewer-level mark itself: marking a track is
|
|
17144
|
+
* curation you do while looking at it, but building the training set the
|
|
17145
|
+
* fleet's models are fine-tuned on is not.
|
|
17146
|
+
*/
|
|
17147
|
+
listRetrainStaging: method(object({
|
|
17148
|
+
/** Empty ⇒ every camera that has staging tracks. A LIST, not a single
|
|
17149
|
+
* `deviceId`, deliberately: `deviceId` would make this device-bound and
|
|
17150
|
+
* route it at one camera's owner, and "every camera" would stop being
|
|
17151
|
+
* expressible at all. */
|
|
17152
|
+
deviceIds: array(number()).optional(),
|
|
17153
|
+
limit: number().int().min(1).max(500).optional()
|
|
17154
|
+
}), array(RetrainTrackSchema).readonly(), {
|
|
17155
|
+
kind: "query",
|
|
17156
|
+
auth: "admin"
|
|
17157
|
+
}),
|
|
17158
|
+
/**
|
|
17159
|
+
* What a track can contribute, and what it already has.
|
|
17160
|
+
*
|
|
17161
|
+
* `candidates` are the track's whole, unannotated frames — index rows only,
|
|
17162
|
+
* so this is cheap. `copies` are the frames already inside the dataset, and
|
|
17163
|
+
* a candidate whose copy exists is marked `copied: true`: selecting it again
|
|
17164
|
+
* is free and CANNOT fail, whatever became of the original.
|
|
17165
|
+
*
|
|
17166
|
+
* A crop, a thumbnail and `fullFrameBoxed` are never candidates. The last
|
|
17167
|
+
* one matters most: it has the model's own rectangle burned into the pixels,
|
|
17168
|
+
* and a detector trained on it learns to find a green line.
|
|
17169
|
+
*/
|
|
17170
|
+
listRetrainFrames: method(object({ trackId: string() }), RetrainFrameListSchema, {
|
|
17171
|
+
kind: "query",
|
|
17172
|
+
auth: "admin"
|
|
17173
|
+
}),
|
|
17174
|
+
/**
|
|
17175
|
+
* COPY-ON-SELECT — the write that makes `trained` safe to evict.
|
|
17176
|
+
*
|
|
17177
|
+
* Selecting a frame copies its bytes into retrain storage immediately: not
|
|
17178
|
+
* a reference, not a lease. Once the copy exists the dataset no longer
|
|
17179
|
+
* depends on the track's media, which is exactly what lets D81 hand a
|
|
17180
|
+
* `trained` track back to retention.
|
|
17181
|
+
*
|
|
17182
|
+
* The order inside is load-bearing and is pinned by a test: an EXISTING
|
|
17183
|
+
* copy is returned without touching the source, so an original that
|
|
17184
|
+
* evaporated blocks the selection of THAT ORIGINAL and never the copy
|
|
17185
|
+
* already taken. Every refusal comes back named — a dropped selection is
|
|
17186
|
+
* never silent, on the wire or in the log.
|
|
17187
|
+
*/
|
|
17188
|
+
selectRetrainFrames: method(object({
|
|
17189
|
+
deviceId: number(),
|
|
17190
|
+
trackId: string(),
|
|
17191
|
+
mediaKeys: array(string()).min(1)
|
|
17192
|
+
}), RetrainFrameSelectionSchema, {
|
|
17193
|
+
kind: "mutation",
|
|
17194
|
+
auth: "admin"
|
|
17195
|
+
}),
|
|
17196
|
+
/** Un-select a frame: its annotations go first, then the copy and its blob.
|
|
17197
|
+
* Deliberately destructive and deliberately explicit — it is the only way
|
|
17198
|
+
* a frame leaves the dataset before export. */
|
|
17199
|
+
deselectRetrainFrame: method(object({
|
|
17200
|
+
deviceId: number(),
|
|
17201
|
+
trackId: string(),
|
|
17202
|
+
frameId: string()
|
|
17203
|
+
}), object({
|
|
17204
|
+
removed: boolean(),
|
|
17205
|
+
removedAnnotations: number().int()
|
|
17206
|
+
}), {
|
|
17207
|
+
kind: "mutation",
|
|
17208
|
+
auth: "admin"
|
|
17209
|
+
}),
|
|
17210
|
+
/**
|
|
17211
|
+
* The pixels of ONE copied frame, base64.
|
|
17212
|
+
*
|
|
17213
|
+
* Through the cap rather than a data plane because it is genuinely one
|
|
17214
|
+
* frame at a time, on demand, at human speed — the shape D9/D18 permit
|
|
17215
|
+
* (what they forbid is frames crossing a boundary at frame RATE). The
|
|
17216
|
+
* annotation canvas needs the image and its exact dimensions in the same
|
|
17217
|
+
* answer: a canvas that places a normalised box against a size it guessed
|
|
17218
|
+
* draws every box in the wrong place.
|
|
17219
|
+
*/
|
|
17220
|
+
getRetrainFrameImage: method(object({ frameId: string() }), object({
|
|
17221
|
+
base64: string(),
|
|
17222
|
+
width: number().int(),
|
|
17223
|
+
height: number().int()
|
|
17224
|
+
}), {
|
|
17225
|
+
kind: "query",
|
|
17226
|
+
auth: "admin"
|
|
17227
|
+
}),
|
|
17228
|
+
/**
|
|
17229
|
+
* Ask the pipeline what it sees, as a PROPOSAL.
|
|
17230
|
+
*
|
|
17231
|
+
* Runs through `pipelineRunner.runStatelessStep` on the COPIED frame, and
|
|
17232
|
+
* every box comes back as a draft with `source: 'assist'` plus the model and
|
|
17233
|
+
* score that produced it. The operator confirms, edits, adds and deletes;
|
|
17234
|
+
* nothing is stored until `saveRetrainAnnotations`.
|
|
17235
|
+
*
|
|
17236
|
+
* For packages the request is `rfdetr-package` on the ZONE CROP at 0.35 —
|
|
17237
|
+
* never the whole frame, where a package detector at that threshold proposes
|
|
17238
|
+
* furniture. A package request with no zone is REFUSED rather than widened,
|
|
17239
|
+
* because the silent widening would look like a bad model for as long as
|
|
17240
|
+
* nobody checked which rectangle it ran on.
|
|
17241
|
+
*/
|
|
17242
|
+
proposeRetrainAnnotations: method(object({
|
|
17243
|
+
deviceId: number(),
|
|
17244
|
+
trackId: string(),
|
|
17245
|
+
frameId: string(),
|
|
17246
|
+
subject: RetrainAssistSubjectSchema,
|
|
17247
|
+
/** Which node runs it. Absent ⇒ wherever an unowned call lands. */
|
|
17248
|
+
nodeId: string().optional()
|
|
17249
|
+
}), RetrainAssistResultSchema, {
|
|
17250
|
+
kind: "mutation",
|
|
17251
|
+
auth: "admin"
|
|
17252
|
+
}),
|
|
17253
|
+
/** Every annotation on a track, oldest first. */
|
|
17254
|
+
listRetrainAnnotations: method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
|
|
17255
|
+
kind: "query",
|
|
17256
|
+
auth: "admin"
|
|
17257
|
+
}),
|
|
17258
|
+
/**
|
|
17259
|
+
* Replace EVERY annotation on one frame with the supplied set.
|
|
17260
|
+
*
|
|
17261
|
+
* Whole-frame replacement, not per-box upsert: the unit of ground truth is
|
|
17262
|
+
* the frame, and "the operator deleted a box" must be the same durable
|
|
17263
|
+
* outcome as "the operator never drew it". A per-box patch would let a frame
|
|
17264
|
+
* keep a box the operator removed on a surface that only knew about the
|
|
17265
|
+
* boxes it sent.
|
|
17266
|
+
*
|
|
17267
|
+
* Refuses a macro class typed into `label` or `subLabel` — the tiers are
|
|
17268
|
+
* separate and the guard is at the WRITE, because a mixed taxonomy cannot
|
|
17269
|
+
* be un-mixed by reading it.
|
|
17270
|
+
*/
|
|
17271
|
+
saveRetrainAnnotations: method(object({
|
|
17272
|
+
deviceId: number(),
|
|
17273
|
+
trackId: string(),
|
|
17274
|
+
frameId: string(),
|
|
17275
|
+
annotations: array(RetrainAnnotationDraftSchema)
|
|
17276
|
+
}), array(RetrainAnnotationSchema).readonly(), {
|
|
17277
|
+
kind: "mutation",
|
|
17278
|
+
auth: "admin"
|
|
17279
|
+
}),
|
|
17280
|
+
/**
|
|
17281
|
+
* Finish with a track: `staging → trained`. **The only writer of that
|
|
17282
|
+
* state** — D81 shipped the column with it deliberately unreachable.
|
|
17283
|
+
*
|
|
17284
|
+
* Refuses a track the dataset holds no copies from. `trained` un-pins the
|
|
17285
|
+
* track's media, so completing without a copy is a delete order for material
|
|
17286
|
+
* nothing ever extracted anything from; that refusal IS the safety argument
|
|
17287
|
+
* of D81, expressed as a precondition.
|
|
17288
|
+
*/
|
|
17289
|
+
completeRetrainTrack: method(object({
|
|
17290
|
+
deviceId: number(),
|
|
17291
|
+
trackId: string()
|
|
17292
|
+
}), RetrainTransitionResultSchema, {
|
|
17293
|
+
kind: "mutation",
|
|
17294
|
+
auth: "admin"
|
|
17295
|
+
}),
|
|
17296
|
+
/**
|
|
17297
|
+
* The deliberate return: `trained → staging`, for the rare case.
|
|
17298
|
+
*
|
|
17299
|
+
* The generic `setTrackFlags` toggle refuses this in both directions by
|
|
17300
|
+
* design (D81) — re-staging from a checkbox is how the same material gets
|
|
17301
|
+
* annotated twice under two ground truths. Doing it here means the operator
|
|
17302
|
+
* is looking at the annotations that already exist while they decide, and
|
|
17303
|
+
* those annotations are LEFT ALONE: "put this back" must not be a
|
|
17304
|
+
* destructive act wearing a navigational name.
|
|
17305
|
+
*/
|
|
17306
|
+
restageRetrainTrack: method(object({
|
|
17307
|
+
deviceId: number(),
|
|
17308
|
+
trackId: string()
|
|
17309
|
+
}), RetrainTransitionResultSchema, {
|
|
17310
|
+
kind: "mutation",
|
|
17311
|
+
auth: "admin"
|
|
17312
|
+
}),
|
|
17313
|
+
/**
|
|
17314
|
+
* Where to download the ANNOTATED dataset.
|
|
17315
|
+
*
|
|
17316
|
+
* The sibling of `getTrainingExportUrl` and deliberately not the same
|
|
17317
|
+
* archive: that one streams a marked track's stored media verbatim, this one
|
|
17318
|
+
* streams the retrain COPIES plus an `annotations.json` carrying, for every
|
|
17319
|
+
* subject, the canonical full-frame box AND the geometry derived for each
|
|
17320
|
+
* model shape (letterboxed root / zone-cropped package / subject-cropped
|
|
17321
|
+
* classifier). Derived at export, never stored — one box in, three shapes
|
|
17322
|
+
* out, so two crops of the same subject can never end up in one feature
|
|
17323
|
+
* space (D52).
|
|
17324
|
+
*/
|
|
17325
|
+
getRetrainExportUrl: method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
|
|
17326
|
+
kind: "query",
|
|
17327
|
+
auth: "admin"
|
|
17328
|
+
}),
|
|
16445
17329
|
getEventMedia: method(object({
|
|
16446
17330
|
eventId: string(),
|
|
16447
17331
|
kind: MediaFileKindEnum.optional()
|
|
@@ -17106,6 +17990,22 @@ var DetailResultSchema = object({
|
|
|
17106
17990
|
bbox: NativeCropBboxSchema.optional(),
|
|
17107
17991
|
embedding: string().optional(),
|
|
17108
17992
|
label: string().optional(),
|
|
17993
|
+
/**
|
|
17994
|
+
* The tier `label` occupies, copied VERBATIM from the producing step's
|
|
17995
|
+
* `StepDefinition.labelTier` (roadmap 4g). Present only when `label` is.
|
|
17996
|
+
*
|
|
17997
|
+
* It rides the wire rather than being resolved by the consumer because the
|
|
17998
|
+
* declaration lives with the step definition, which only the executing node
|
|
17999
|
+
* has: post-analysis holds no step registry, and re-deriving the tier from
|
|
18000
|
+
* `className` there would be exactly the inference this model exists to
|
|
18001
|
+
* forbid. A `label` that arrives WITHOUT this field is refused by the write
|
|
18002
|
+
* rule and logged (`label tier undeclared`) — an older runner therefore
|
|
18003
|
+
* stops enriching rather than guessing, which is why addon-pipeline is
|
|
18004
|
+
* deployed BEFORE addon-post-analysis.
|
|
18005
|
+
*/
|
|
18006
|
+
labelTier: union([literal(1), literal(2)]).optional(),
|
|
18007
|
+
/** Model that produced `label` — carried into the tier's attribution. */
|
|
18008
|
+
labelModelId: string().optional(),
|
|
17109
18009
|
alignedCropJpeg: string().optional(),
|
|
17110
18010
|
/** Face short side (px) measured on the NATIVE crop surface. The `bbox`
|
|
17111
18011
|
* above is detection-frame px (≈6× smaller on a 4K camera) — min-face-size
|
|
@@ -30055,6 +30955,12 @@ Object.freeze({
|
|
|
30055
30955
|
addonId: null,
|
|
30056
30956
|
access: "delete"
|
|
30057
30957
|
},
|
|
30958
|
+
"pipelineAnalytics.completeRetrainTrack": {
|
|
30959
|
+
capName: "pipeline-analytics",
|
|
30960
|
+
capScope: "device",
|
|
30961
|
+
addonId: null,
|
|
30962
|
+
access: "create"
|
|
30963
|
+
},
|
|
30058
30964
|
"pipelineAnalytics.deleteDeviceEvents": {
|
|
30059
30965
|
capName: "pipeline-analytics",
|
|
30060
30966
|
capScope: "device",
|
|
@@ -30067,6 +30973,12 @@ Object.freeze({
|
|
|
30067
30973
|
addonId: null,
|
|
30068
30974
|
access: "delete"
|
|
30069
30975
|
},
|
|
30976
|
+
"pipelineAnalytics.deselectRetrainFrame": {
|
|
30977
|
+
capName: "pipeline-analytics",
|
|
30978
|
+
capScope: "device",
|
|
30979
|
+
addonId: null,
|
|
30980
|
+
access: "create"
|
|
30981
|
+
},
|
|
30070
30982
|
"pipelineAnalytics.getActiveTracks": {
|
|
30071
30983
|
capName: "pipeline-analytics",
|
|
30072
30984
|
capScope: "device",
|
|
@@ -30127,6 +31039,18 @@ Object.freeze({
|
|
|
30127
31039
|
addonId: null,
|
|
30128
31040
|
access: "view"
|
|
30129
31041
|
},
|
|
31042
|
+
"pipelineAnalytics.getRetrainExportUrl": {
|
|
31043
|
+
capName: "pipeline-analytics",
|
|
31044
|
+
capScope: "device",
|
|
31045
|
+
addonId: null,
|
|
31046
|
+
access: "view"
|
|
31047
|
+
},
|
|
31048
|
+
"pipelineAnalytics.getRetrainFrameImage": {
|
|
31049
|
+
capName: "pipeline-analytics",
|
|
31050
|
+
capScope: "device",
|
|
31051
|
+
addonId: null,
|
|
31052
|
+
access: "view"
|
|
31053
|
+
},
|
|
30130
31054
|
"pipelineAnalytics.getSensorEvents": {
|
|
30131
31055
|
capName: "pipeline-analytics",
|
|
30132
31056
|
capScope: "device",
|
|
@@ -30181,6 +31105,24 @@ Object.freeze({
|
|
|
30181
31105
|
addonId: null,
|
|
30182
31106
|
access: "view"
|
|
30183
31107
|
},
|
|
31108
|
+
"pipelineAnalytics.listRetrainAnnotations": {
|
|
31109
|
+
capName: "pipeline-analytics",
|
|
31110
|
+
capScope: "device",
|
|
31111
|
+
addonId: null,
|
|
31112
|
+
access: "view"
|
|
31113
|
+
},
|
|
31114
|
+
"pipelineAnalytics.listRetrainFrames": {
|
|
31115
|
+
capName: "pipeline-analytics",
|
|
31116
|
+
capScope: "device",
|
|
31117
|
+
addonId: null,
|
|
31118
|
+
access: "view"
|
|
31119
|
+
},
|
|
31120
|
+
"pipelineAnalytics.listRetrainStaging": {
|
|
31121
|
+
capName: "pipeline-analytics",
|
|
31122
|
+
capScope: "device",
|
|
31123
|
+
addonId: null,
|
|
31124
|
+
access: "view"
|
|
31125
|
+
},
|
|
30184
31126
|
"pipelineAnalytics.listTrackMedia": {
|
|
30185
31127
|
capName: "pipeline-analytics",
|
|
30186
31128
|
capScope: "device",
|
|
@@ -30193,6 +31135,12 @@ Object.freeze({
|
|
|
30193
31135
|
addonId: null,
|
|
30194
31136
|
access: "view"
|
|
30195
31137
|
},
|
|
31138
|
+
"pipelineAnalytics.proposeRetrainAnnotations": {
|
|
31139
|
+
capName: "pipeline-analytics",
|
|
31140
|
+
capScope: "device",
|
|
31141
|
+
addonId: null,
|
|
31142
|
+
access: "create"
|
|
31143
|
+
},
|
|
30196
31144
|
"pipelineAnalytics.pruneEvents": {
|
|
30197
31145
|
capName: "pipeline-analytics",
|
|
30198
31146
|
capScope: "device",
|
|
@@ -30223,12 +31171,30 @@ Object.freeze({
|
|
|
30223
31171
|
addonId: null,
|
|
30224
31172
|
access: "create"
|
|
30225
31173
|
},
|
|
31174
|
+
"pipelineAnalytics.restageRetrainTrack": {
|
|
31175
|
+
capName: "pipeline-analytics",
|
|
31176
|
+
capScope: "device",
|
|
31177
|
+
addonId: null,
|
|
31178
|
+
access: "create"
|
|
31179
|
+
},
|
|
31180
|
+
"pipelineAnalytics.saveRetrainAnnotations": {
|
|
31181
|
+
capName: "pipeline-analytics",
|
|
31182
|
+
capScope: "device",
|
|
31183
|
+
addonId: null,
|
|
31184
|
+
access: "create"
|
|
31185
|
+
},
|
|
30226
31186
|
"pipelineAnalytics.searchObjectEvents": {
|
|
30227
31187
|
capName: "pipeline-analytics",
|
|
30228
31188
|
capScope: "device",
|
|
30229
31189
|
addonId: null,
|
|
30230
31190
|
access: "view"
|
|
30231
31191
|
},
|
|
31192
|
+
"pipelineAnalytics.selectRetrainFrames": {
|
|
31193
|
+
capName: "pipeline-analytics",
|
|
31194
|
+
capScope: "device",
|
|
31195
|
+
addonId: null,
|
|
31196
|
+
access: "create"
|
|
31197
|
+
},
|
|
30232
31198
|
"pipelineAnalytics.setTrackFlags": {
|
|
30233
31199
|
capName: "pipeline-analytics",
|
|
30234
31200
|
capScope: "device",
|
|
@@ -32714,4 +33680,4 @@ DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
|
|
|
32714
33680
|
DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
|
|
32715
33681
|
DEFAULT_NATIVE_LEASE_SETTINGS.admission;
|
|
32716
33682
|
//#endregion
|
|
32717
|
-
export {
|
|
33683
|
+
export { sleep as $, encodeVectorBase64 as A, vectorDimFromBase64 as B, alarmPanelCapability as C, customAction as D, cosineSimilarity as E, notificationRulesCapability as F, buildFfmpegArgs as G, zoneAnalyticsCapability as H, pipelineAnalyticsCapability as I, DeviceType as J, isSoftwareDecode as K, plateGalleryCapability as L, hfModelUrl as M, isScheduleActive as N, defineCustomActions as O, kebabToCamel as P, nodePin as Q, readDeviceStateFrom as R, addonWidgetsSourceCapability as S, buildEventKindDescriptor as T, errMsg as U, videoclipsCapability as V, Fmp4BoxSplitter as W, hydrateSchema as X, createEvent as Y, isDeviceScopedCap as Z, OpsLogEntrySchema as _, LabelAttributionSchema as a, object as at, TimelapseRuleSchema as b, NC_TAXONOMY as c, unknown as ct, NcRulePatchSchema as d, _enum as et, NcRuleSchema as f, NcTaxonomySchema as g, NcSnoozeSuppressedSchema as h, EVENT_PAD_MS as i, number as it, faceGalleryCapability as j, embeddingEncoderCapability as k, NcConditionDescriptorSchema as l, EventCategory as lt, NcSnoozeSchema as m, DEFAULT_EVENT_COLOR as n, boolean as nt, MACRO_LABELS as o, record as ot, NcSnoozeInputSchema as p, BaseAddon as q, EVENT_KIND_BY_CAP as r, literal as rt, NC_CONDITION_CATALOG as s, string as st, BaseDevice as t, array as tt, NcRuleInputSchema as u, RetrainStatusSchema as v, audioMetricsCapability as w, TrackSourceSchema as x, TimelapseRuleInputSchema as y, subKindsOf as z };
|