@camstack/types 1.2.158 → 1.2.160
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon.js +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/capabilities/pipeline-orchestrator.cap.d.ts +8 -1
- package/dist/capabilities/recording.cap.d.ts +9 -3
- package/dist/generated/device-scoped-caps.d.ts +1 -1
- package/dist/index.js +209 -66
- package/dist/index.mjs +208 -67
- package/dist/interfaces/recording-config.d.ts +85 -28
- package/dist/{sleep-IaOr7P-z.mjs → sleep-Cfij6Jj9.mjs} +2 -1
- package/dist/{sleep-BGiOgxye.js → sleep-DDIFZGbc.js} +2 -1
- package/dist/types/detection.d.ts +70 -0
- package/package.json +1 -1
|
@@ -29,17 +29,29 @@ export type RecordingWeekday = z.infer<typeof RecordingWeekdaySchema>;
|
|
|
29
29
|
/**
|
|
30
30
|
* DERIVED per-camera storage summary — the single field cheap consumers read
|
|
31
31
|
* (the viewer's status dot, the camera list) instead of walking `bands`:
|
|
32
|
-
* - `off`
|
|
33
|
-
* - `events`
|
|
34
|
-
* - `continuous`
|
|
32
|
+
* - `off` — no band covers the camera (or it is disabled).
|
|
33
|
+
* - `events` — every band records around triggers only.
|
|
34
|
+
* - `continuous` — at least one band records continuously.
|
|
35
|
+
* - `on-device-decision`— the DEVICE decides: recording runs for as long as the
|
|
36
|
+
* camera raises its own `recording-signal` level (a robot that cleans). There
|
|
37
|
+
* is no schedule to author, because there is no hour to program — see
|
|
38
|
+
* {@link RecordingConfigSchema}`.deviceDecision`.
|
|
35
39
|
*
|
|
36
|
-
* NEVER authored: the recorder stamps it from the authoritative
|
|
37
|
-
* every save (`activeModeForConfig`). Writing it
|
|
40
|
+
* NEVER authored: the recorder stamps it from the authoritative intent
|
|
41
|
+
* (`bands` + `deviceDecision`) on every save (`activeModeForConfig`). Writing it
|
|
42
|
+
* has no effect.
|
|
43
|
+
*
|
|
44
|
+
* `on-device-decision` is named for WHO decides, not for how the recording is
|
|
45
|
+
* requested. `on-demand` was rejected: in this repo's vocabulary a "demand" is
|
|
46
|
+
* something the operator makes (the live gate is the recorder's own "demand
|
|
47
|
+
* window"), and a knob whose name suggests the operator starts it while the
|
|
48
|
+
* device actually does is the D62 shape — a control nobody can predict.
|
|
38
49
|
*/
|
|
39
50
|
export declare const RecordingStorageModeSchema: z.ZodEnum<{
|
|
40
51
|
off: "off";
|
|
41
52
|
events: "events";
|
|
42
53
|
continuous: "continuous";
|
|
54
|
+
"on-device-decision": "on-device-decision";
|
|
43
55
|
}>;
|
|
44
56
|
export type RecordingStorageMode = z.infer<typeof RecordingStorageModeSchema>;
|
|
45
57
|
/**
|
|
@@ -72,7 +84,6 @@ export declare const RecordingTriggersSchema: z.ZodObject<{
|
|
|
72
84
|
animal: "animal";
|
|
73
85
|
}>>>;
|
|
74
86
|
sensorDeviceIds: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
75
|
-
deviceSignal: z.ZodOptional<z.ZodBoolean>;
|
|
76
87
|
}, z.core.$strip>;
|
|
77
88
|
export type RecordingTriggers = z.infer<typeof RecordingTriggersSchema>;
|
|
78
89
|
/**
|
|
@@ -101,7 +112,6 @@ export declare const RecordingBandTriggersSchema: z.ZodObject<{
|
|
|
101
112
|
animal: "animal";
|
|
102
113
|
}>>>;
|
|
103
114
|
sensorDeviceIds: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
104
|
-
deviceSignal: z.ZodOptional<z.ZodBoolean>;
|
|
105
115
|
}, z.core.$strip>;
|
|
106
116
|
export type RecordingBandTriggers = z.infer<typeof RecordingBandTriggersSchema>;
|
|
107
117
|
/**
|
|
@@ -129,7 +139,6 @@ export declare const RecordingBandSchema: z.ZodObject<{
|
|
|
129
139
|
animal: "animal";
|
|
130
140
|
}>>>;
|
|
131
141
|
sensorDeviceIds: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
132
|
-
deviceSignal: z.ZodOptional<z.ZodBoolean>;
|
|
133
142
|
}, z.core.$strip>>;
|
|
134
143
|
preBufferSec: z.ZodOptional<z.ZodNumber>;
|
|
135
144
|
postBufferSec: z.ZodOptional<z.ZodNumber>;
|
|
@@ -149,30 +158,44 @@ export type RecordingBand = z.infer<typeof RecordingBandSchema>;
|
|
|
149
158
|
* `postBufferSec` must therefore exceed one segment length plus the attach
|
|
150
159
|
* latency (ffmpeg spawn + RTSP dial), or the window closes before the first
|
|
151
160
|
* segment is ever finalized and the camera records nothing anyway. At the 10 s
|
|
152
|
-
* default segment length, 30 s leaves room for two full segments.
|
|
153
|
-
* is retroactive only — it costs nothing at record time, it merely keeps
|
|
154
|
-
* already-written segments.
|
|
155
|
-
*
|
|
156
|
-
* ## Why `preBufferSec` is 15 and not one segment length
|
|
161
|
+
* default segment length, 30 s leaves room for two full segments.
|
|
157
162
|
*
|
|
158
|
-
*
|
|
159
|
-
* writer from BEFORE the trigger (D192/D193). If this bound were narrower, the
|
|
160
|
-
* keep gate would delete the very seconds the ring just supplied — two
|
|
161
|
-
* authorities disagreeing about the same footage, which is the failure D191 was
|
|
162
|
-
* written against. It is therefore set to `RECORDING_PRE_ROLL_MAX_MS` (15 s),
|
|
163
|
-
* the widest window the broker can be configured to serve, so an operator
|
|
164
|
-
* raising the cluster pre-roll can never walk past the gate silently. The
|
|
165
|
-
* addon-side guard is
|
|
166
|
-
* `packages/addon-pipeline/src/recorder/addon/__tests__/band-decision.spec.ts`.
|
|
163
|
+
* ## There is no `preBufferSec` here any more (D381)
|
|
167
164
|
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
165
|
+
* There used to be, at 15 s, and its whole justification was that it had to
|
|
166
|
+
* cover the widest pre-roll the broker could serve. A number whose only
|
|
167
|
+
* correct value is another subsystem's ceiling is not a setting; it is that
|
|
168
|
+
* ceiling, restated where it can drift. The keep bound is derived from the
|
|
169
|
+
* prebuffer ceiling directly now, in `band-decision.ts`, so an operator
|
|
170
|
+
* raising the prebuffer cannot walk past the gate at all rather than merely
|
|
171
|
+
* being warned not to.
|
|
170
172
|
*/
|
|
171
173
|
export declare const DEFAULT_EVENTS_BAND_BUFFER_SEC: {
|
|
172
|
-
readonly preBufferSec: 15;
|
|
173
174
|
readonly postBufferSec: 30;
|
|
174
175
|
};
|
|
175
176
|
export type DefaultEventsBandBufferSec = typeof DEFAULT_EVENTS_BAND_BUFFER_SEC;
|
|
177
|
+
/**
|
|
178
|
+
* Drop the RETIRED band `preBufferSec` from a persisted recording config.
|
|
179
|
+
*
|
|
180
|
+
* Explicit, and it reports what it removed, because the alternative is a
|
|
181
|
+
* silent strip: `RecordingBandSchema` is a plain `z.object`, so the key
|
|
182
|
+
* survives a parse and disappears on the next save with no line anywhere. An
|
|
183
|
+
* operator who had typed a number would find it gone and nothing would say
|
|
184
|
+
* when or why.
|
|
185
|
+
*
|
|
186
|
+
* Nothing is carried forward: the value it held was always required to be at
|
|
187
|
+
* least the broker's widest pre-roll, and the gate is now derived from exactly
|
|
188
|
+
* that ceiling. Every configuration that was legal therefore keeps a keep-gate
|
|
189
|
+
* at least as wide as the one it had — including an explicit `0`, which widens.
|
|
190
|
+
* That is deliberate and it is the only behaviour change: a band that kept
|
|
191
|
+
* nothing before its trigger now keeps up to the prebuffer ceiling of
|
|
192
|
+
* already-written footage, which is media that exists either way.
|
|
193
|
+
*/
|
|
194
|
+
export declare function stripRetiredBandPreBufferSec(config: RecordingConfig): {
|
|
195
|
+
readonly config: RecordingConfig;
|
|
196
|
+
/** Index of every band a value was removed from. Empty = nothing to do. */
|
|
197
|
+
readonly strippedBands: readonly number[];
|
|
198
|
+
};
|
|
176
199
|
/**
|
|
177
200
|
* Adjacent recorded ranges closer than this belong to the SAME videoclip visit.
|
|
178
201
|
*
|
|
@@ -223,6 +246,7 @@ export declare const RecordingConfigSchema: z.ZodObject<{
|
|
|
223
246
|
off: "off";
|
|
224
247
|
events: "events";
|
|
225
248
|
continuous: "continuous";
|
|
249
|
+
"on-device-decision": "on-device-decision";
|
|
226
250
|
}>>;
|
|
227
251
|
profiles: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
228
252
|
high: "high";
|
|
@@ -247,19 +271,19 @@ export declare const RecordingConfigSchema: z.ZodObject<{
|
|
|
247
271
|
animal: "animal";
|
|
248
272
|
}>>>;
|
|
249
273
|
sensorDeviceIds: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
250
|
-
deviceSignal: z.ZodOptional<z.ZodBoolean>;
|
|
251
274
|
}, z.core.$strip>>;
|
|
252
275
|
preBufferSec: z.ZodOptional<z.ZodNumber>;
|
|
253
276
|
postBufferSec: z.ZodOptional<z.ZodNumber>;
|
|
254
277
|
}, z.core.$strip>>>;
|
|
278
|
+
deviceDecision: z.ZodOptional<z.ZodBoolean>;
|
|
255
279
|
retention: z.ZodOptional<z.ZodObject<{
|
|
256
280
|
maxAgeDays: z.ZodOptional<z.ZodNumber>;
|
|
257
281
|
maxSizeGb: z.ZodOptional<z.ZodNumber>;
|
|
258
282
|
}, z.core.$strip>>;
|
|
259
283
|
}, z.core.$strict>;
|
|
260
284
|
export type RecordingConfig = z.infer<typeof RecordingConfigSchema>;
|
|
261
|
-
/** The
|
|
262
|
-
export type RecordingModeSource = Pick<RecordingConfig, 'enabled' | 'bands'>;
|
|
285
|
+
/** The authored intent a mode is derived from — all {@link deriveRecordingMode} reads. */
|
|
286
|
+
export type RecordingModeSource = Pick<RecordingConfig, 'enabled' | 'bands' | 'deviceDecision'>;
|
|
263
287
|
/**
|
|
264
288
|
* Derive the {@link RecordingStorageModeSchema} summary from the authoritative
|
|
265
289
|
* bands: `continuous` when any band records continuously, `events` when a band
|
|
@@ -289,3 +313,36 @@ export declare const DEFAULT_RECORDING_PROFILES: readonly CamProfile[];
|
|
|
289
313
|
* still records something). Empty assigned → empty result.
|
|
290
314
|
*/
|
|
291
315
|
export declare function resolveRecordingProfiles(assigned: readonly CamProfile[], override: readonly CamProfile[] | undefined): CamProfile[];
|
|
316
|
+
/** What {@link migrateLegacyDeviceSignalConfig} did to one stored row. */
|
|
317
|
+
export interface LegacyDeviceSignalMigration {
|
|
318
|
+
/** The row to persist/parse. The SAME reference when nothing was legacy. */
|
|
319
|
+
readonly value: unknown;
|
|
320
|
+
/** The config now says `deviceDecision: true` — the mode was recovered. */
|
|
321
|
+
readonly migrated: boolean;
|
|
322
|
+
/**
|
|
323
|
+
* The legacy key was on a band that ALSO listened to something else (motion,
|
|
324
|
+
* a sensor …), so the key was dropped and the band kept. The camera keeps its
|
|
325
|
+
* other triggers and LOSES the device signal until an operator picks the new
|
|
326
|
+
* mode — the honest outcome: folding a mixed band into a mode would silently
|
|
327
|
+
* delete a schedule the operator authored.
|
|
328
|
+
*/
|
|
329
|
+
readonly strippedOnly: boolean;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Turn a config stored under D371 (`bands[].triggers.deviceSignal: true`) into
|
|
333
|
+
* a D380 one (`deviceDecision: true`, no bands). Pure, total and IDEMPOTENT —
|
|
334
|
+
* a row with no legacy key comes back as the SAME reference, `migrated: false`.
|
|
335
|
+
*
|
|
336
|
+
* ## Why the schema cannot be left to do this
|
|
337
|
+
*
|
|
338
|
+
* `RecordingTriggersSchema` is a non-strict `z.object`, so the retired key is
|
|
339
|
+
* SILENTLY STRIPPED on read. The camera whose only trigger it was would parse
|
|
340
|
+
* cleanly into an `events` band that lists nothing — "enabled, and records
|
|
341
|
+
* nothing, forever", the exact shape `eventsBandCanEverDemand` exists to shout
|
|
342
|
+
* about, reached without a single line in the log. On the reference hub that is
|
|
343
|
+
* camera 4374, the one camera an operator had configured by hand.
|
|
344
|
+
*
|
|
345
|
+
* `enabled` is carried through untouched: a camera the operator had switched
|
|
346
|
+
* OFF comes out of a migration still off (D62).
|
|
347
|
+
*/
|
|
348
|
+
export declare function migrateLegacyDeviceSignalConfig(raw: unknown): LegacyDeviceSignalMigration;
|
|
@@ -3859,7 +3859,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
3859
3859
|
* can answer "may a rule actuate this?" without importing the schema barrel
|
|
3860
3860
|
* (~144MB RSS per runner, D28).
|
|
3861
3861
|
*
|
|
3862
|
-
* Coverage:
|
|
3862
|
+
* Coverage: 83 device-scoped capabilities.
|
|
3863
3863
|
*/
|
|
3864
3864
|
var DEVICE_SCOPED_CAPS = new Set([
|
|
3865
3865
|
"accessories",
|
|
@@ -3924,6 +3924,7 @@ var DEVICE_SCOPED_CAPS = new Set([
|
|
|
3924
3924
|
"ptz",
|
|
3925
3925
|
"ptz-autotrack",
|
|
3926
3926
|
"reboot",
|
|
3927
|
+
"recording-signal",
|
|
3927
3928
|
"scene-monitor",
|
|
3928
3929
|
"script-runner",
|
|
3929
3930
|
"smoke",
|
|
@@ -3859,7 +3859,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
3859
3859
|
* can answer "may a rule actuate this?" without importing the schema barrel
|
|
3860
3860
|
* (~144MB RSS per runner, D28).
|
|
3861
3861
|
*
|
|
3862
|
-
* Coverage:
|
|
3862
|
+
* Coverage: 83 device-scoped capabilities.
|
|
3863
3863
|
*/
|
|
3864
3864
|
var DEVICE_SCOPED_CAPS = new Set([
|
|
3865
3865
|
"accessories",
|
|
@@ -3924,6 +3924,7 @@ var DEVICE_SCOPED_CAPS = new Set([
|
|
|
3924
3924
|
"ptz",
|
|
3925
3925
|
"ptz-autotrack",
|
|
3926
3926
|
"reboot",
|
|
3927
|
+
"recording-signal",
|
|
3927
3928
|
"scene-monitor",
|
|
3928
3929
|
"script-runner",
|
|
3929
3930
|
"smoke",
|
|
@@ -16,6 +16,12 @@ export interface SpatialDetection {
|
|
|
16
16
|
* measured at native resolution is not the thing they exist to kill.
|
|
17
17
|
*/
|
|
18
18
|
readonly nativeRecovery?: NativeRecoveryEvidence;
|
|
19
|
+
/**
|
|
20
|
+
* Mirrored from {@link ObjectDetection.birthEvidence} when this detection
|
|
21
|
+
* enters the tracker, so a birth the tracker mints carries the pixels that
|
|
22
|
+
* produced it all the way to the confirmation gate (D379).
|
|
23
|
+
*/
|
|
24
|
+
readonly birthEvidence?: BirthEvidence;
|
|
19
25
|
}
|
|
20
26
|
export interface CropInput {
|
|
21
27
|
readonly frame: FrameInput;
|
|
@@ -230,6 +236,12 @@ export interface DetectionBase {
|
|
|
230
236
|
* {@link NativeRecoveryEvidence}.
|
|
231
237
|
*/
|
|
232
238
|
readonly nativeRecovery?: NativeRecoveryEvidence;
|
|
239
|
+
/**
|
|
240
|
+
* The box's own pixels, cut by the runner at the instant of detection, for a
|
|
241
|
+
* box it had not seen recently. See {@link BirthEvidence} — this is what the
|
|
242
|
+
* confirmation gate looks at instead of asking for pixels afterwards.
|
|
243
|
+
*/
|
|
244
|
+
readonly birthEvidence?: BirthEvidence;
|
|
233
245
|
}
|
|
234
246
|
/**
|
|
235
247
|
* What a native-resolution second pass ALREADY measured about a box.
|
|
@@ -255,6 +267,64 @@ export interface NativeRecoveryEvidence {
|
|
|
255
267
|
/** Side of the compressed view the model actually saw, in pixels. */
|
|
256
268
|
readonly viewPx: number;
|
|
257
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* The pixels of a candidate birth, CUT AT THE INSTANT IT WAS DETECTED.
|
|
272
|
+
*
|
|
273
|
+
* ## Why this exists (D379)
|
|
274
|
+
*
|
|
275
|
+
* The confirmation gate asks one question of a new track: re-run the detector
|
|
276
|
+
* on a crop of the box and see whether the subject is really there. Until now
|
|
277
|
+
* it asked for those pixels AFTERWARDS, by `FrameHandle`, across a process
|
|
278
|
+
* boundary — and measured on the live hub over three hours that request fails
|
|
279
|
+
* 7 452 times, 99.9% of them `worker-lease-gone`, at a median handle age of
|
|
280
|
+
* 1 347 ms and misses recorded as early as 254 ms. The frame is not evicted
|
|
281
|
+
* late; it is very often never retained at all (`leaseAdmitted:9` against
|
|
282
|
+
* `leaseMarks:297` on one measured worker, `leaseReleasedEarly:288`). 1 191 of
|
|
283
|
+
* 1 883 gate verdicts in that window (63%) never saw a pixel.
|
|
284
|
+
*
|
|
285
|
+
* So the crop is cut where the pixels are already in hand — in the runner, on
|
|
286
|
+
* the frame the detection was measured on — and RIDES the detection. No store,
|
|
287
|
+
* no handle, no lease, no round trip, and above all no OTHER INSTANT: a subject
|
|
288
|
+
* that has moved is not in a later frame, and its absence there is not evidence
|
|
289
|
+
* it was never here.
|
|
290
|
+
*
|
|
291
|
+
* This is the same discipline Scrypted enforces structurally: its decoded frame
|
|
292
|
+
* is invalidated the moment the `for await` body returns
|
|
293
|
+
* (`plugins/python-codecs/src/libav.py:105-112`), so every second look — face
|
|
294
|
+
* crop, embedding, retained detection image — is taken IN BAND
|
|
295
|
+
* (`plugins/objectdetector/src/main.ts:512-520`).
|
|
296
|
+
*
|
|
297
|
+
* ## What it is NOT
|
|
298
|
+
*
|
|
299
|
+
* Not a detail/CLIP crop, and nothing here calls `deriveDetailCropRect` (D52):
|
|
300
|
+
* this rectangle is the detection box padded for a CLASSIFICATION question and
|
|
301
|
+
* is never fed to an embedder, never stored as media, never indexed. It is also
|
|
302
|
+
* not a frame: it is one small JPEG of one box, cut only for a box the runner
|
|
303
|
+
* has not seen recently, so it never crosses a boundary at frame rate (D9/D18).
|
|
304
|
+
*/
|
|
305
|
+
export interface BirthEvidence {
|
|
306
|
+
/** Base64 JPEG of {@link rect}, cut from the frame this detection was measured on. */
|
|
307
|
+
readonly jpegBase64: string;
|
|
308
|
+
/**
|
|
309
|
+
* The rectangle the JPEG covers, in ABSOLUTE pixels of the analysis frame —
|
|
310
|
+
* the same space as `DetectionBoundingBox`. The gate needs it to put a
|
|
311
|
+
* crop-space re-detection back into the frame (secondary promotion), and it
|
|
312
|
+
* is the padded box, never the bare one.
|
|
313
|
+
*/
|
|
314
|
+
readonly rect: {
|
|
315
|
+
readonly x: number;
|
|
316
|
+
readonly y: number;
|
|
317
|
+
readonly width: number;
|
|
318
|
+
readonly height: number;
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* Which raster it was cut from. `analysis` is the ≤640 detection frame the
|
|
322
|
+
* runner holds in its own address space — the only raster that is present,
|
|
323
|
+
* for free, at the instant of detection. A future native rung would add a
|
|
324
|
+
* value here rather than change the meaning of this one.
|
|
325
|
+
*/
|
|
326
|
+
readonly tier: 'analysis';
|
|
327
|
+
}
|
|
258
328
|
/** Object detection (first-level person/vehicle/animal OR detail face/plate). */
|
|
259
329
|
export interface ObjectDetection extends DetectionBase {
|
|
260
330
|
readonly kind: 'first-level' | 'detail';
|