@camstack/addon-pipeline-orchestrator 1.2.176 → 1.2.179
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/assets/viewer/.viewer-current-files.json +1 -1
- package/assets/viewer/.viewer-version +1 -1
- package/assets/viewer/_expo/static/js/web/{index-80ca8c13110f1ec0a2ccc8edd4923ee8.js → index-f7a4424100c4ea6b027df8c8d78b2710.js} +7 -7
- package/assets/viewer/index.html +1 -1
- package/dist/_stub.js +2 -2
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-CpiA9AcV.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-bn0lQFJM.mjs} +3 -3
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-bQkIh01P.mjs +26 -0
- package/dist/{_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CJHDVENb.mjs → _virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DY6aTgRF.mjs} +1 -1
- package/dist/{hostInit-DzEP81Yo.mjs → hostInit-1TYnYC5S.mjs} +3 -3
- package/dist/index.js +201 -14
- package/dist/index.mjs +201 -14
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DJ-F48X5.mjs +0 -26
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ node_fs = __toESM(node_fs);
|
|
|
30
30
|
let node_path = require("node:path");
|
|
31
31
|
node_path = __toESM(node_path);
|
|
32
32
|
let node_url = require("node:url");
|
|
33
|
-
//#region ../types/dist/event-category-
|
|
33
|
+
//#region ../types/dist/event-category-DAXzJeTX.mjs
|
|
34
34
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
35
35
|
EventCategory["SystemBoot"] = "system.boot";
|
|
36
36
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -622,7 +622,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
622
622
|
* issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
|
|
623
623
|
*/
|
|
624
624
|
EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
|
|
625
|
-
EventCategory["FrigateLiveEvent"] = "frigate.live-event";
|
|
626
625
|
EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
|
|
627
626
|
/**
|
|
628
627
|
* Stream-broker health watchdog. Per-broker (deviceId/profile) emission.
|
|
@@ -13478,7 +13477,7 @@ DeviceType.Camera;
|
|
|
13478
13477
|
var SourceInfoSchema = object({
|
|
13479
13478
|
/** Live dispatch key — mutable when the source system allows rename. */
|
|
13480
13479
|
id: string(),
|
|
13481
|
-
/** Source system tag — e.g. 'homeassistant' | 'reolink' | '
|
|
13480
|
+
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
13482
13481
|
system: string(),
|
|
13483
13482
|
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
13484
13483
|
* Used to detect rename when `id` changes. */
|
|
@@ -13965,7 +13964,7 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
|
|
|
13965
13964
|
* - `device-management.router.ts` (deleted in Phase 2)
|
|
13966
13965
|
* - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
|
|
13967
13966
|
*
|
|
13968
|
-
* All device provider addons (rtsp, onvif,
|
|
13967
|
+
* All device provider addons (rtsp, onvif, …) are hub-local: they may
|
|
13969
13968
|
* fork into separate processes but never run on remote cluster agents. Therefore:
|
|
13970
13969
|
* - No nodeId routing needed — this is a pure hub singleton.
|
|
13971
13970
|
* - The hub's DeviceRegistry is the single source of truth for all live devices.
|
|
@@ -17975,7 +17974,7 @@ var NcRuleInputSchema = object({
|
|
|
17975
17974
|
* The measured seven-person arrival on device 590 spans 110 s with every
|
|
17976
17975
|
* internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
|
|
17977
17976
|
* idle cutoff holds it as one and ends it when the arrival actually ends.
|
|
17978
|
-
* 30 is
|
|
17977
|
+
* 30 s is a widely shipped value for the same decision.
|
|
17979
17978
|
*
|
|
17980
17979
|
* ### What it replaces
|
|
17981
17980
|
*
|
|
@@ -20241,6 +20240,13 @@ var ZoneCrossingSchema = object({
|
|
|
20241
20240
|
/** Zone display name at crossing time (falls back to the id). */
|
|
20242
20241
|
zoneName: string().optional()
|
|
20243
20242
|
});
|
|
20243
|
+
/** One zone a box was stamped with, and how much of the BOX fell inside it. */
|
|
20244
|
+
var ZoneOverlapSchema = object({
|
|
20245
|
+
zoneId: string(),
|
|
20246
|
+
zoneName: string().optional(),
|
|
20247
|
+
/** Percentage of the BOX area inside the zone, 0–100. */
|
|
20248
|
+
overlapPct: number()
|
|
20249
|
+
});
|
|
20244
20250
|
var ObjectEventSchema = object({
|
|
20245
20251
|
...BaseEventFields,
|
|
20246
20252
|
kind: literal("object"),
|
|
@@ -20265,6 +20271,35 @@ var ObjectEventSchema = object({
|
|
|
20265
20271
|
bbox: BoundingBoxSchema.optional(),
|
|
20266
20272
|
/** Heavy JSON — omitted in slim projection. */
|
|
20267
20273
|
zones: array(string()).readonly().optional(),
|
|
20274
|
+
/**
|
|
20275
|
+
* How far inside each stamped zone the box actually was, as a percentage of
|
|
20276
|
+
* the BOX area.
|
|
20277
|
+
*
|
|
20278
|
+
* Membership alone cannot express "at least a third of it inside", which is
|
|
20279
|
+
* the bar an operator argues with — and the number already existed: the
|
|
20280
|
+
* engine computed it, the pipeline logged it, and then discarded it. It
|
|
20281
|
+
* matters most on the events that carry no track, where a rule has nothing
|
|
20282
|
+
* else to judge by.
|
|
20283
|
+
*/
|
|
20284
|
+
zoneOverlaps: array(ZoneOverlapSchema).readonly().optional(),
|
|
20285
|
+
/**
|
|
20286
|
+
* Present when this event describes a detection the TRACKER refused, so it
|
|
20287
|
+
* has no track and never will.
|
|
20288
|
+
*
|
|
20289
|
+
* A zone-scoped immediate rule asks "is there an animal in the flowerbed
|
|
20290
|
+
* NOW?"; tracking answers a different question ("is this the same subject as
|
|
20291
|
+
* before?") whose confirmation threshold presumes continuity. On 2026-09-09 a
|
|
20292
|
+
* dog at 0.7729, 100% inside a watched zone, was refused three times in six
|
|
20293
|
+
* seconds with three different candidate ids — seven analysed frames in the
|
|
20294
|
+
* window, the subject in a handful of them, metres apart — and produced no
|
|
20295
|
+
* event at all.
|
|
20296
|
+
*
|
|
20297
|
+
* It is NAMED rather than merely implied by a missing `trackId`: an event
|
|
20298
|
+
* with no track and no reason is indistinguishable from a track whose media
|
|
20299
|
+
* went missing, and a consumer that asks this one for a best shot or a
|
|
20300
|
+
* history is asking for something that never existed.
|
|
20301
|
+
*/
|
|
20302
|
+
spawnRefused: string().optional(),
|
|
20268
20303
|
/** Omitted in slim projection. */
|
|
20269
20304
|
state: TrackStateSchema.optional(),
|
|
20270
20305
|
/**
|
|
@@ -21338,6 +21373,60 @@ var NativeCropResultSchema = object({
|
|
|
21338
21373
|
*/
|
|
21339
21374
|
tier: _enum(["native", "ram-fullframe"]).optional()
|
|
21340
21375
|
});
|
|
21376
|
+
/**
|
|
21377
|
+
* What a PARKED frame is for — the `kind` half of the `(deviceId, trackId,
|
|
21378
|
+
* kind)` key ([D422](../../../docs/decisions/adr-0422-a-relevant-frame-is-parked-when-it-becomes-relevant.md)).
|
|
21379
|
+
*
|
|
21380
|
+
* - `keyFrame` — the track's clean best FULL frame at native width (capped by
|
|
21381
|
+
* the caller's `maxWidth`). What `keyFrame` + `keyFrameSmall` are written
|
|
21382
|
+
* from, and what the detail plane's full-frame rung cuts from.
|
|
21383
|
+
* - `thumbnail` — the best-shot subject slab: the caller's 16:9 central-square
|
|
21384
|
+
* window, cut by the runner at uncapped native resolution.
|
|
21385
|
+
*
|
|
21386
|
+
* The runner never interprets `trackId`; it is an opaque key the hub owns.
|
|
21387
|
+
*/
|
|
21388
|
+
var ParkedFrameKindSchema = _enum(["keyFrame", "thumbnail"]);
|
|
21389
|
+
/**
|
|
21390
|
+
* Why a park did NOT happen. Every one is logged on the runner with
|
|
21391
|
+
* `tags: { deviceId }` and the handle's measured age, because a handle already
|
|
21392
|
+
* gone at park time IS the defect the 150–500 ms window exists to prevent —
|
|
21393
|
+
* never a reason to retry in a loop.
|
|
21394
|
+
*
|
|
21395
|
+
* - `handle-gone` — the native surface no longer resolves this handle (never
|
|
21396
|
+
* registered, past the lease bound, or the worker released it). `detail`
|
|
21397
|
+
* carries the runner's own miss reason.
|
|
21398
|
+
* - `encode-failed` — pixels resolved and the JPEG encode threw.
|
|
21399
|
+
*/
|
|
21400
|
+
var ParkRefusalSchema = _enum(["handle-gone", "encode-failed"]);
|
|
21401
|
+
/** Answer to `parkTrackFrame` — a discriminated union, so "not parked" always names why. */
|
|
21402
|
+
var ParkTrackFrameResultSchema = discriminatedUnion("parked", [object({
|
|
21403
|
+
parked: literal(true),
|
|
21404
|
+
/** The node holding the parcel — what a later retrieval must be pinned to. */
|
|
21405
|
+
nodeId: string(),
|
|
21406
|
+
width: number().int().positive(),
|
|
21407
|
+
height: number().int().positive(),
|
|
21408
|
+
/** JPEG bytes the parcel costs on the node. */
|
|
21409
|
+
bytes: number().int().nonnegative(),
|
|
21410
|
+
/** Whether this park replaced a parcel already held for the key. */
|
|
21411
|
+
replaced: boolean()
|
|
21412
|
+
}), object({
|
|
21413
|
+
parked: literal(false),
|
|
21414
|
+
nodeId: string(),
|
|
21415
|
+
reason: ParkRefusalSchema,
|
|
21416
|
+
/** Age of the handle at the park, on the runner's clock; null if unknown. */
|
|
21417
|
+
handleAgeMs: number().nullable(),
|
|
21418
|
+
detail: string().optional()
|
|
21419
|
+
})]);
|
|
21420
|
+
/** A retrieved parcel — the runner's own JPEG, base64 for the wire. */
|
|
21421
|
+
var ParkedTrackFrameSchema = object({
|
|
21422
|
+
jpeg: string(),
|
|
21423
|
+
width: number().int().positive(),
|
|
21424
|
+
height: number().int().positive(),
|
|
21425
|
+
/** The frame instant the parcel shows (the caller's clock, echoed back). */
|
|
21426
|
+
timestamp: number(),
|
|
21427
|
+
/** When the node stored it (the node's clock). */
|
|
21428
|
+
parkedAt: number()
|
|
21429
|
+
});
|
|
21341
21430
|
/** Parent detection context passed to `runDetailSubtree` — the crop's
|
|
21342
21431
|
* originating detection, in FRAME-space coordinates. Reuses
|
|
21343
21432
|
* `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
|
|
@@ -21727,7 +21816,7 @@ var RunnerCameraConfigSchema = object({
|
|
|
21727
21816
|
* - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
|
|
21728
21817
|
* analyzer providers) running on decoded frames in this runner.
|
|
21729
21818
|
* - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
|
|
21730
|
-
* analytics
|
|
21819
|
+
* analytics). The provider emits ProviderMotion
|
|
21731
21820
|
* events; the orchestrator forwards to `reportMotion`.
|
|
21732
21821
|
*/
|
|
21733
21822
|
motionSources: MotionSourcesSchema.default(["analyzer"]),
|
|
@@ -22157,7 +22246,26 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
|
|
|
22157
22246
|
className: string(),
|
|
22158
22247
|
/** Camera the pixels came from. Diagnostics + log tags ONLY — never routing. */
|
|
22159
22248
|
sourceDeviceId: number()
|
|
22160
|
-
}), RunStatelessStepResultSchema, { kind: "mutation" })
|
|
22249
|
+
}), RunStatelessStepResultSchema, { kind: "mutation" }), method(object({
|
|
22250
|
+
deviceId: number(),
|
|
22251
|
+
/** Opaque to the runner — the hub's track id. */
|
|
22252
|
+
trackId: string(),
|
|
22253
|
+
kind: ParkedFrameKindSchema,
|
|
22254
|
+
handle: FrameHandleSchema,
|
|
22255
|
+
/** Normalized [0,1] window to cut. `{0,0,1,1}` for a full frame. */
|
|
22256
|
+
bbox: NativeCropBboxSchema,
|
|
22257
|
+
/** Width cap for a full-frame park (the `keyFrame` native cap). */
|
|
22258
|
+
maxWidth: number().int().positive().optional(),
|
|
22259
|
+
/** The frame instant (caller's clock) — orders parks for one key. */
|
|
22260
|
+
timestamp: number()
|
|
22261
|
+
}), ParkTrackFrameResultSchema, { kind: "mutation" }), method(object({
|
|
22262
|
+
deviceId: number(),
|
|
22263
|
+
trackId: string(),
|
|
22264
|
+
kind: ParkedFrameKindSchema
|
|
22265
|
+
}), ParkedTrackFrameSchema.nullable()), method(object({
|
|
22266
|
+
deviceId: number(),
|
|
22267
|
+
trackId: string()
|
|
22268
|
+
}), object({ released: number().int().nonnegative() }), { kind: "mutation" });
|
|
22161
22269
|
var CameraPipelineConfigSchema = object({
|
|
22162
22270
|
engine: PipelineEngineChoiceSchema.optional(),
|
|
22163
22271
|
steps: array(PipelineStepInputSchema).readonly(),
|
|
@@ -25416,7 +25524,7 @@ DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _v
|
|
|
25416
25524
|
*
|
|
25417
25525
|
* Implementations:
|
|
25418
25526
|
* - Each camera-providing addon registers this cap natively per
|
|
25419
|
-
* device (Reolink, Hikvision, RTSP,
|
|
25527
|
+
* device (Reolink, Hikvision, RTSP, ONVIF self).
|
|
25420
25528
|
* - Returns a snapshot from the persisted device config blob — no
|
|
25421
25529
|
* extra round-trip to the camera, no probing.
|
|
25422
25530
|
* - The cap is admin-only (`auth: 'admin'`) — credentials must not
|
|
@@ -26802,7 +26910,7 @@ var AvailableIntegrationTypeSchema = object({
|
|
|
26802
26910
|
* Which integration-marker cap the addon declared, so the wizard can
|
|
26803
26911
|
* branch on CAP — never on addon name. `device-adoption` integrations
|
|
26804
26912
|
* (Home Assistant, …) route through the broker step (Approach A);
|
|
26805
|
-
* `device-provider` integrations (Reolink/
|
|
26913
|
+
* `device-provider` integrations (Reolink/ONVIF) keep the
|
|
26806
26914
|
* legacy config → discovery flow.
|
|
26807
26915
|
*/
|
|
26808
26916
|
kind: _enum(["device-adoption", "device-provider"]),
|
|
@@ -29359,13 +29467,31 @@ var RecordingManifestSchema = object({
|
|
|
29359
29467
|
*/
|
|
29360
29468
|
playbackEndpoints: array(string())
|
|
29361
29469
|
});
|
|
29362
|
-
/**
|
|
29363
|
-
*
|
|
29470
|
+
/**
|
|
29471
|
+
* Recording storage usage for one camera — what the ARCHIVE holds for it,
|
|
29472
|
+
* across every profile and every resolvable location on this node.
|
|
29473
|
+
*
|
|
29474
|
+
* ## Archive vs residency, which is the whole point of this shape (D418)
|
|
29475
|
+
*
|
|
29476
|
+
* `usedBytes` / `oldestMs` / `segments` are the durable hour ledger's numbers:
|
|
29477
|
+
* the same quantity `RecordingLocationUsage.usedBytes` reports for the volume
|
|
29478
|
+
* this camera writes to, so the two can be added up and compared.
|
|
29479
|
+
*
|
|
29480
|
+
* They were not always. Until D418 this row came from the recorder's in-RAM
|
|
29481
|
+
* `RecordingIndex`, which since D287 holds only the live tail plus the hours
|
|
29482
|
+
* somebody hydrated — so a hub with 1.9 TB and 3.3 days of footage reported
|
|
29483
|
+
* 60.5 GB and "oldest: three hours ago" on the same page as the volume row
|
|
29484
|
+
* that said otherwise, and two people read it as retention eating recent
|
|
29485
|
+
* footage. Residency is still reported, under `residentBytes` /
|
|
29486
|
+
* `residentOldestMs`, which answer a different question and say so.
|
|
29487
|
+
*/
|
|
29364
29488
|
var RecordingDeviceUsageSchema = object({
|
|
29365
29489
|
deviceId: number(),
|
|
29490
|
+
/** Bytes the ARCHIVE holds for this camera on this node's resolvable
|
|
29491
|
+
* recordings locations. */
|
|
29366
29492
|
usedBytes: number(),
|
|
29367
29493
|
/**
|
|
29368
|
-
* Start of this camera's OLDEST
|
|
29494
|
+
* Start of this camera's OLDEST ARCHIVED segment, across every profile and
|
|
29369
29495
|
* location — the "Oldest footage" column in Recordings → Storage, and the
|
|
29370
29496
|
* only honest answer to "is retention actually holding?" per camera.
|
|
29371
29497
|
*
|
|
@@ -29375,7 +29501,24 @@ var RecordingDeviceUsageSchema = object({
|
|
|
29375
29501
|
* (types) and the addon ship on different trains, and the addon is usually
|
|
29376
29502
|
* the later of the two.
|
|
29377
29503
|
*/
|
|
29378
|
-
oldestMs: number().nullable().optional()
|
|
29504
|
+
oldestMs: number().nullable().optional(),
|
|
29505
|
+
/** Segments the archive holds for this camera — the "M" beside `usedBytes`.
|
|
29506
|
+
* OPTIONAL for the same train-skew reason as `oldestMs`. */
|
|
29507
|
+
segments: number().optional(),
|
|
29508
|
+
/**
|
|
29509
|
+
* RESIDENCY, not the archive: bytes the recorder process currently holds in
|
|
29510
|
+
* its RAM index — the live tail plus the hours a reader hydrated.
|
|
29511
|
+
*
|
|
29512
|
+
* It answers "what can be served without touching the disk", never "how much
|
|
29513
|
+
* footage this camera has". A UI that renders it must label it as such, and
|
|
29514
|
+
* must never total it as storage. OPTIONAL: a recorder predating D418 omits
|
|
29515
|
+
* it, and so does a node whose ledger could not answer.
|
|
29516
|
+
*/
|
|
29517
|
+
residentBytes: number().optional(),
|
|
29518
|
+
/** The residency counterpart of `oldestMs` — the oldest segment currently in
|
|
29519
|
+
* RAM, which on a healthy recorder is minutes ago and means nothing about
|
|
29520
|
+
* retention. `null` = nothing resident. */
|
|
29521
|
+
residentOldestMs: number().nullable().optional()
|
|
29379
29522
|
});
|
|
29380
29523
|
/** Recording storage usage + capacity for one storage location. */
|
|
29381
29524
|
var RecordingLocationUsageSchema = object({
|
|
@@ -29413,6 +29556,17 @@ var RecordingStorageUsageSchema = object({
|
|
|
29413
29556
|
totalUsedBytes: number(),
|
|
29414
29557
|
devices: array(RecordingDeviceUsageSchema),
|
|
29415
29558
|
/**
|
|
29559
|
+
* WHERE `devices[]` came from, so an empty table is never mistaken for empty
|
|
29560
|
+
* disks (D418).
|
|
29561
|
+
*
|
|
29562
|
+
* `archive` = the durable hour ledger answered. `unavailable` = this node
|
|
29563
|
+
* declared no ledger and the recorder REFUSED rather than answering the
|
|
29564
|
+
* question with the resident index (D295 rule 3) — the rows are UNKNOWN, not
|
|
29565
|
+
* zero, and a UI must say so. OPTIONAL: a recorder predating D418 omits it,
|
|
29566
|
+
* and its rows are residency.
|
|
29567
|
+
*/
|
|
29568
|
+
devicesRead: _enum(["archive", "unavailable"]).optional(),
|
|
29569
|
+
/**
|
|
29416
29570
|
* Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
|
|
29417
29571
|
* of the payload without it: the hub serves this method through a BAKED
|
|
29418
29572
|
* router, so a hub whose framework train predates a change to this array
|
|
@@ -35914,6 +36068,24 @@ Object.freeze({
|
|
|
35914
36068
|
addonId: null,
|
|
35915
36069
|
access: "view"
|
|
35916
36070
|
},
|
|
36071
|
+
"pipelineRunner.getParkedTrackFrame": {
|
|
36072
|
+
capName: "pipeline-runner",
|
|
36073
|
+
capScope: "system",
|
|
36074
|
+
addonId: null,
|
|
36075
|
+
access: "view"
|
|
36076
|
+
},
|
|
36077
|
+
"pipelineRunner.parkTrackFrame": {
|
|
36078
|
+
capName: "pipeline-runner",
|
|
36079
|
+
capScope: "system",
|
|
36080
|
+
addonId: null,
|
|
36081
|
+
access: "create"
|
|
36082
|
+
},
|
|
36083
|
+
"pipelineRunner.releaseParkedTrackFrames": {
|
|
36084
|
+
capName: "pipeline-runner",
|
|
36085
|
+
capScope: "system",
|
|
36086
|
+
addonId: null,
|
|
36087
|
+
access: "create"
|
|
36088
|
+
},
|
|
35917
36089
|
"pipelineRunner.reportMotion": {
|
|
35918
36090
|
capName: "pipeline-runner",
|
|
35919
36091
|
capScope: "system",
|
|
@@ -39191,6 +39363,21 @@ Object.freeze({
|
|
|
39191
39363
|
form: "single",
|
|
39192
39364
|
optional: false
|
|
39193
39365
|
}],
|
|
39366
|
+
"pipelineRunner.getParkedTrackFrame": [{
|
|
39367
|
+
name: "deviceId",
|
|
39368
|
+
form: "single",
|
|
39369
|
+
optional: false
|
|
39370
|
+
}],
|
|
39371
|
+
"pipelineRunner.parkTrackFrame": [{
|
|
39372
|
+
name: "deviceId",
|
|
39373
|
+
form: "single",
|
|
39374
|
+
optional: false
|
|
39375
|
+
}],
|
|
39376
|
+
"pipelineRunner.releaseParkedTrackFrames": [{
|
|
39377
|
+
name: "deviceId",
|
|
39378
|
+
form: "single",
|
|
39379
|
+
optional: false
|
|
39380
|
+
}],
|
|
39194
39381
|
"pipelineRunner.reportMotion": [{
|
|
39195
39382
|
name: "deviceId",
|
|
39196
39383
|
form: "single",
|
|
@@ -39882,7 +40069,7 @@ var PreviewTextField = string().max(200);
|
|
|
39882
40069
|
* Whether the notification's preview is a STILL or a short animation.
|
|
39883
40070
|
*
|
|
39884
40071
|
* The operator's ask, verbatim: *"inviato come gif o video (come per le altre
|
|
39885
|
-
* rule)"* —
|
|
40072
|
+
* rule)"* — the other rules already offer an animated preview, and a ten-hour
|
|
39886
40073
|
* night reads better as three seconds of motion than as one frame of it. Both
|
|
39887
40074
|
* modes get the SAME treatment (blurred frame, large centred title); `'gif'`
|
|
39888
40075
|
* simply applies it to a dozen frames sampled across the render and assembles
|
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
//#region ../types/dist/event-category-
|
|
5
|
+
//#region ../types/dist/event-category-DAXzJeTX.mjs
|
|
6
6
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
7
7
|
EventCategory["SystemBoot"] = "system.boot";
|
|
8
8
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -594,7 +594,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
594
594
|
* issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
|
|
595
595
|
*/
|
|
596
596
|
EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
|
|
597
|
-
EventCategory["FrigateLiveEvent"] = "frigate.live-event";
|
|
598
597
|
EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
|
|
599
598
|
/**
|
|
600
599
|
* Stream-broker health watchdog. Per-broker (deviceId/profile) emission.
|
|
@@ -13450,7 +13449,7 @@ DeviceType.Camera;
|
|
|
13450
13449
|
var SourceInfoSchema = object({
|
|
13451
13450
|
/** Live dispatch key — mutable when the source system allows rename. */
|
|
13452
13451
|
id: string(),
|
|
13453
|
-
/** Source system tag — e.g. 'homeassistant' | 'reolink' | '
|
|
13452
|
+
/** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
|
|
13454
13453
|
system: string(),
|
|
13455
13454
|
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
13456
13455
|
* Used to detect rename when `id` changes. */
|
|
@@ -13937,7 +13936,7 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
|
|
|
13937
13936
|
* - `device-management.router.ts` (deleted in Phase 2)
|
|
13938
13937
|
* - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
|
|
13939
13938
|
*
|
|
13940
|
-
* All device provider addons (rtsp, onvif,
|
|
13939
|
+
* All device provider addons (rtsp, onvif, …) are hub-local: they may
|
|
13941
13940
|
* fork into separate processes but never run on remote cluster agents. Therefore:
|
|
13942
13941
|
* - No nodeId routing needed — this is a pure hub singleton.
|
|
13943
13942
|
* - The hub's DeviceRegistry is the single source of truth for all live devices.
|
|
@@ -17947,7 +17946,7 @@ var NcRuleInputSchema = object({
|
|
|
17947
17946
|
* The measured seven-person arrival on device 590 spans 110 s with every
|
|
17948
17947
|
* internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
|
|
17949
17948
|
* idle cutoff holds it as one and ends it when the arrival actually ends.
|
|
17950
|
-
* 30 is
|
|
17949
|
+
* 30 s is a widely shipped value for the same decision.
|
|
17951
17950
|
*
|
|
17952
17951
|
* ### What it replaces
|
|
17953
17952
|
*
|
|
@@ -20213,6 +20212,13 @@ var ZoneCrossingSchema = object({
|
|
|
20213
20212
|
/** Zone display name at crossing time (falls back to the id). */
|
|
20214
20213
|
zoneName: string().optional()
|
|
20215
20214
|
});
|
|
20215
|
+
/** One zone a box was stamped with, and how much of the BOX fell inside it. */
|
|
20216
|
+
var ZoneOverlapSchema = object({
|
|
20217
|
+
zoneId: string(),
|
|
20218
|
+
zoneName: string().optional(),
|
|
20219
|
+
/** Percentage of the BOX area inside the zone, 0–100. */
|
|
20220
|
+
overlapPct: number()
|
|
20221
|
+
});
|
|
20216
20222
|
var ObjectEventSchema = object({
|
|
20217
20223
|
...BaseEventFields,
|
|
20218
20224
|
kind: literal("object"),
|
|
@@ -20237,6 +20243,35 @@ var ObjectEventSchema = object({
|
|
|
20237
20243
|
bbox: BoundingBoxSchema.optional(),
|
|
20238
20244
|
/** Heavy JSON — omitted in slim projection. */
|
|
20239
20245
|
zones: array(string()).readonly().optional(),
|
|
20246
|
+
/**
|
|
20247
|
+
* How far inside each stamped zone the box actually was, as a percentage of
|
|
20248
|
+
* the BOX area.
|
|
20249
|
+
*
|
|
20250
|
+
* Membership alone cannot express "at least a third of it inside", which is
|
|
20251
|
+
* the bar an operator argues with — and the number already existed: the
|
|
20252
|
+
* engine computed it, the pipeline logged it, and then discarded it. It
|
|
20253
|
+
* matters most on the events that carry no track, where a rule has nothing
|
|
20254
|
+
* else to judge by.
|
|
20255
|
+
*/
|
|
20256
|
+
zoneOverlaps: array(ZoneOverlapSchema).readonly().optional(),
|
|
20257
|
+
/**
|
|
20258
|
+
* Present when this event describes a detection the TRACKER refused, so it
|
|
20259
|
+
* has no track and never will.
|
|
20260
|
+
*
|
|
20261
|
+
* A zone-scoped immediate rule asks "is there an animal in the flowerbed
|
|
20262
|
+
* NOW?"; tracking answers a different question ("is this the same subject as
|
|
20263
|
+
* before?") whose confirmation threshold presumes continuity. On 2026-09-09 a
|
|
20264
|
+
* dog at 0.7729, 100% inside a watched zone, was refused three times in six
|
|
20265
|
+
* seconds with three different candidate ids — seven analysed frames in the
|
|
20266
|
+
* window, the subject in a handful of them, metres apart — and produced no
|
|
20267
|
+
* event at all.
|
|
20268
|
+
*
|
|
20269
|
+
* It is NAMED rather than merely implied by a missing `trackId`: an event
|
|
20270
|
+
* with no track and no reason is indistinguishable from a track whose media
|
|
20271
|
+
* went missing, and a consumer that asks this one for a best shot or a
|
|
20272
|
+
* history is asking for something that never existed.
|
|
20273
|
+
*/
|
|
20274
|
+
spawnRefused: string().optional(),
|
|
20240
20275
|
/** Omitted in slim projection. */
|
|
20241
20276
|
state: TrackStateSchema.optional(),
|
|
20242
20277
|
/**
|
|
@@ -21310,6 +21345,60 @@ var NativeCropResultSchema = object({
|
|
|
21310
21345
|
*/
|
|
21311
21346
|
tier: _enum(["native", "ram-fullframe"]).optional()
|
|
21312
21347
|
});
|
|
21348
|
+
/**
|
|
21349
|
+
* What a PARKED frame is for — the `kind` half of the `(deviceId, trackId,
|
|
21350
|
+
* kind)` key ([D422](../../../docs/decisions/adr-0422-a-relevant-frame-is-parked-when-it-becomes-relevant.md)).
|
|
21351
|
+
*
|
|
21352
|
+
* - `keyFrame` — the track's clean best FULL frame at native width (capped by
|
|
21353
|
+
* the caller's `maxWidth`). What `keyFrame` + `keyFrameSmall` are written
|
|
21354
|
+
* from, and what the detail plane's full-frame rung cuts from.
|
|
21355
|
+
* - `thumbnail` — the best-shot subject slab: the caller's 16:9 central-square
|
|
21356
|
+
* window, cut by the runner at uncapped native resolution.
|
|
21357
|
+
*
|
|
21358
|
+
* The runner never interprets `trackId`; it is an opaque key the hub owns.
|
|
21359
|
+
*/
|
|
21360
|
+
var ParkedFrameKindSchema = _enum(["keyFrame", "thumbnail"]);
|
|
21361
|
+
/**
|
|
21362
|
+
* Why a park did NOT happen. Every one is logged on the runner with
|
|
21363
|
+
* `tags: { deviceId }` and the handle's measured age, because a handle already
|
|
21364
|
+
* gone at park time IS the defect the 150–500 ms window exists to prevent —
|
|
21365
|
+
* never a reason to retry in a loop.
|
|
21366
|
+
*
|
|
21367
|
+
* - `handle-gone` — the native surface no longer resolves this handle (never
|
|
21368
|
+
* registered, past the lease bound, or the worker released it). `detail`
|
|
21369
|
+
* carries the runner's own miss reason.
|
|
21370
|
+
* - `encode-failed` — pixels resolved and the JPEG encode threw.
|
|
21371
|
+
*/
|
|
21372
|
+
var ParkRefusalSchema = _enum(["handle-gone", "encode-failed"]);
|
|
21373
|
+
/** Answer to `parkTrackFrame` — a discriminated union, so "not parked" always names why. */
|
|
21374
|
+
var ParkTrackFrameResultSchema = discriminatedUnion("parked", [object({
|
|
21375
|
+
parked: literal(true),
|
|
21376
|
+
/** The node holding the parcel — what a later retrieval must be pinned to. */
|
|
21377
|
+
nodeId: string(),
|
|
21378
|
+
width: number().int().positive(),
|
|
21379
|
+
height: number().int().positive(),
|
|
21380
|
+
/** JPEG bytes the parcel costs on the node. */
|
|
21381
|
+
bytes: number().int().nonnegative(),
|
|
21382
|
+
/** Whether this park replaced a parcel already held for the key. */
|
|
21383
|
+
replaced: boolean()
|
|
21384
|
+
}), object({
|
|
21385
|
+
parked: literal(false),
|
|
21386
|
+
nodeId: string(),
|
|
21387
|
+
reason: ParkRefusalSchema,
|
|
21388
|
+
/** Age of the handle at the park, on the runner's clock; null if unknown. */
|
|
21389
|
+
handleAgeMs: number().nullable(),
|
|
21390
|
+
detail: string().optional()
|
|
21391
|
+
})]);
|
|
21392
|
+
/** A retrieved parcel — the runner's own JPEG, base64 for the wire. */
|
|
21393
|
+
var ParkedTrackFrameSchema = object({
|
|
21394
|
+
jpeg: string(),
|
|
21395
|
+
width: number().int().positive(),
|
|
21396
|
+
height: number().int().positive(),
|
|
21397
|
+
/** The frame instant the parcel shows (the caller's clock, echoed back). */
|
|
21398
|
+
timestamp: number(),
|
|
21399
|
+
/** When the node stored it (the node's clock). */
|
|
21400
|
+
parkedAt: number()
|
|
21401
|
+
});
|
|
21313
21402
|
/** Parent detection context passed to `runDetailSubtree` — the crop's
|
|
21314
21403
|
* originating detection, in FRAME-space coordinates. Reuses
|
|
21315
21404
|
* `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
|
|
@@ -21699,7 +21788,7 @@ var RunnerCameraConfigSchema = object({
|
|
|
21699
21788
|
* - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
|
|
21700
21789
|
* analyzer providers) running on decoded frames in this runner.
|
|
21701
21790
|
* - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
|
|
21702
|
-
* analytics
|
|
21791
|
+
* analytics). The provider emits ProviderMotion
|
|
21703
21792
|
* events; the orchestrator forwards to `reportMotion`.
|
|
21704
21793
|
*/
|
|
21705
21794
|
motionSources: MotionSourcesSchema.default(["analyzer"]),
|
|
@@ -22129,7 +22218,26 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
|
|
|
22129
22218
|
className: string(),
|
|
22130
22219
|
/** Camera the pixels came from. Diagnostics + log tags ONLY — never routing. */
|
|
22131
22220
|
sourceDeviceId: number()
|
|
22132
|
-
}), RunStatelessStepResultSchema, { kind: "mutation" })
|
|
22221
|
+
}), RunStatelessStepResultSchema, { kind: "mutation" }), method(object({
|
|
22222
|
+
deviceId: number(),
|
|
22223
|
+
/** Opaque to the runner — the hub's track id. */
|
|
22224
|
+
trackId: string(),
|
|
22225
|
+
kind: ParkedFrameKindSchema,
|
|
22226
|
+
handle: FrameHandleSchema,
|
|
22227
|
+
/** Normalized [0,1] window to cut. `{0,0,1,1}` for a full frame. */
|
|
22228
|
+
bbox: NativeCropBboxSchema,
|
|
22229
|
+
/** Width cap for a full-frame park (the `keyFrame` native cap). */
|
|
22230
|
+
maxWidth: number().int().positive().optional(),
|
|
22231
|
+
/** The frame instant (caller's clock) — orders parks for one key. */
|
|
22232
|
+
timestamp: number()
|
|
22233
|
+
}), ParkTrackFrameResultSchema, { kind: "mutation" }), method(object({
|
|
22234
|
+
deviceId: number(),
|
|
22235
|
+
trackId: string(),
|
|
22236
|
+
kind: ParkedFrameKindSchema
|
|
22237
|
+
}), ParkedTrackFrameSchema.nullable()), method(object({
|
|
22238
|
+
deviceId: number(),
|
|
22239
|
+
trackId: string()
|
|
22240
|
+
}), object({ released: number().int().nonnegative() }), { kind: "mutation" });
|
|
22133
22241
|
var CameraPipelineConfigSchema = object({
|
|
22134
22242
|
engine: PipelineEngineChoiceSchema.optional(),
|
|
22135
22243
|
steps: array(PipelineStepInputSchema).readonly(),
|
|
@@ -25388,7 +25496,7 @@ DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _v
|
|
|
25388
25496
|
*
|
|
25389
25497
|
* Implementations:
|
|
25390
25498
|
* - Each camera-providing addon registers this cap natively per
|
|
25391
|
-
* device (Reolink, Hikvision, RTSP,
|
|
25499
|
+
* device (Reolink, Hikvision, RTSP, ONVIF self).
|
|
25392
25500
|
* - Returns a snapshot from the persisted device config blob — no
|
|
25393
25501
|
* extra round-trip to the camera, no probing.
|
|
25394
25502
|
* - The cap is admin-only (`auth: 'admin'`) — credentials must not
|
|
@@ -26774,7 +26882,7 @@ var AvailableIntegrationTypeSchema = object({
|
|
|
26774
26882
|
* Which integration-marker cap the addon declared, so the wizard can
|
|
26775
26883
|
* branch on CAP — never on addon name. `device-adoption` integrations
|
|
26776
26884
|
* (Home Assistant, …) route through the broker step (Approach A);
|
|
26777
|
-
* `device-provider` integrations (Reolink/
|
|
26885
|
+
* `device-provider` integrations (Reolink/ONVIF) keep the
|
|
26778
26886
|
* legacy config → discovery flow.
|
|
26779
26887
|
*/
|
|
26780
26888
|
kind: _enum(["device-adoption", "device-provider"]),
|
|
@@ -29331,13 +29439,31 @@ var RecordingManifestSchema = object({
|
|
|
29331
29439
|
*/
|
|
29332
29440
|
playbackEndpoints: array(string())
|
|
29333
29441
|
});
|
|
29334
|
-
/**
|
|
29335
|
-
*
|
|
29442
|
+
/**
|
|
29443
|
+
* Recording storage usage for one camera — what the ARCHIVE holds for it,
|
|
29444
|
+
* across every profile and every resolvable location on this node.
|
|
29445
|
+
*
|
|
29446
|
+
* ## Archive vs residency, which is the whole point of this shape (D418)
|
|
29447
|
+
*
|
|
29448
|
+
* `usedBytes` / `oldestMs` / `segments` are the durable hour ledger's numbers:
|
|
29449
|
+
* the same quantity `RecordingLocationUsage.usedBytes` reports for the volume
|
|
29450
|
+
* this camera writes to, so the two can be added up and compared.
|
|
29451
|
+
*
|
|
29452
|
+
* They were not always. Until D418 this row came from the recorder's in-RAM
|
|
29453
|
+
* `RecordingIndex`, which since D287 holds only the live tail plus the hours
|
|
29454
|
+
* somebody hydrated — so a hub with 1.9 TB and 3.3 days of footage reported
|
|
29455
|
+
* 60.5 GB and "oldest: three hours ago" on the same page as the volume row
|
|
29456
|
+
* that said otherwise, and two people read it as retention eating recent
|
|
29457
|
+
* footage. Residency is still reported, under `residentBytes` /
|
|
29458
|
+
* `residentOldestMs`, which answer a different question and say so.
|
|
29459
|
+
*/
|
|
29336
29460
|
var RecordingDeviceUsageSchema = object({
|
|
29337
29461
|
deviceId: number(),
|
|
29462
|
+
/** Bytes the ARCHIVE holds for this camera on this node's resolvable
|
|
29463
|
+
* recordings locations. */
|
|
29338
29464
|
usedBytes: number(),
|
|
29339
29465
|
/**
|
|
29340
|
-
* Start of this camera's OLDEST
|
|
29466
|
+
* Start of this camera's OLDEST ARCHIVED segment, across every profile and
|
|
29341
29467
|
* location — the "Oldest footage" column in Recordings → Storage, and the
|
|
29342
29468
|
* only honest answer to "is retention actually holding?" per camera.
|
|
29343
29469
|
*
|
|
@@ -29347,7 +29473,24 @@ var RecordingDeviceUsageSchema = object({
|
|
|
29347
29473
|
* (types) and the addon ship on different trains, and the addon is usually
|
|
29348
29474
|
* the later of the two.
|
|
29349
29475
|
*/
|
|
29350
|
-
oldestMs: number().nullable().optional()
|
|
29476
|
+
oldestMs: number().nullable().optional(),
|
|
29477
|
+
/** Segments the archive holds for this camera — the "M" beside `usedBytes`.
|
|
29478
|
+
* OPTIONAL for the same train-skew reason as `oldestMs`. */
|
|
29479
|
+
segments: number().optional(),
|
|
29480
|
+
/**
|
|
29481
|
+
* RESIDENCY, not the archive: bytes the recorder process currently holds in
|
|
29482
|
+
* its RAM index — the live tail plus the hours a reader hydrated.
|
|
29483
|
+
*
|
|
29484
|
+
* It answers "what can be served without touching the disk", never "how much
|
|
29485
|
+
* footage this camera has". A UI that renders it must label it as such, and
|
|
29486
|
+
* must never total it as storage. OPTIONAL: a recorder predating D418 omits
|
|
29487
|
+
* it, and so does a node whose ledger could not answer.
|
|
29488
|
+
*/
|
|
29489
|
+
residentBytes: number().optional(),
|
|
29490
|
+
/** The residency counterpart of `oldestMs` — the oldest segment currently in
|
|
29491
|
+
* RAM, which on a healthy recorder is minutes ago and means nothing about
|
|
29492
|
+
* retention. `null` = nothing resident. */
|
|
29493
|
+
residentOldestMs: number().nullable().optional()
|
|
29351
29494
|
});
|
|
29352
29495
|
/** Recording storage usage + capacity for one storage location. */
|
|
29353
29496
|
var RecordingLocationUsageSchema = object({
|
|
@@ -29385,6 +29528,17 @@ var RecordingStorageUsageSchema = object({
|
|
|
29385
29528
|
totalUsedBytes: number(),
|
|
29386
29529
|
devices: array(RecordingDeviceUsageSchema),
|
|
29387
29530
|
/**
|
|
29531
|
+
* WHERE `devices[]` came from, so an empty table is never mistaken for empty
|
|
29532
|
+
* disks (D418).
|
|
29533
|
+
*
|
|
29534
|
+
* `archive` = the durable hour ledger answered. `unavailable` = this node
|
|
29535
|
+
* declared no ledger and the recorder REFUSED rather than answering the
|
|
29536
|
+
* question with the resident index (D295 rule 3) — the rows are UNKNOWN, not
|
|
29537
|
+
* zero, and a UI must say so. OPTIONAL: a recorder predating D418 omits it,
|
|
29538
|
+
* and its rows are residency.
|
|
29539
|
+
*/
|
|
29540
|
+
devicesRead: _enum(["archive", "unavailable"]).optional(),
|
|
29541
|
+
/**
|
|
29388
29542
|
* Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
|
|
29389
29543
|
* of the payload without it: the hub serves this method through a BAKED
|
|
29390
29544
|
* router, so a hub whose framework train predates a change to this array
|
|
@@ -35886,6 +36040,24 @@ Object.freeze({
|
|
|
35886
36040
|
addonId: null,
|
|
35887
36041
|
access: "view"
|
|
35888
36042
|
},
|
|
36043
|
+
"pipelineRunner.getParkedTrackFrame": {
|
|
36044
|
+
capName: "pipeline-runner",
|
|
36045
|
+
capScope: "system",
|
|
36046
|
+
addonId: null,
|
|
36047
|
+
access: "view"
|
|
36048
|
+
},
|
|
36049
|
+
"pipelineRunner.parkTrackFrame": {
|
|
36050
|
+
capName: "pipeline-runner",
|
|
36051
|
+
capScope: "system",
|
|
36052
|
+
addonId: null,
|
|
36053
|
+
access: "create"
|
|
36054
|
+
},
|
|
36055
|
+
"pipelineRunner.releaseParkedTrackFrames": {
|
|
36056
|
+
capName: "pipeline-runner",
|
|
36057
|
+
capScope: "system",
|
|
36058
|
+
addonId: null,
|
|
36059
|
+
access: "create"
|
|
36060
|
+
},
|
|
35889
36061
|
"pipelineRunner.reportMotion": {
|
|
35890
36062
|
capName: "pipeline-runner",
|
|
35891
36063
|
capScope: "system",
|
|
@@ -39163,6 +39335,21 @@ Object.freeze({
|
|
|
39163
39335
|
form: "single",
|
|
39164
39336
|
optional: false
|
|
39165
39337
|
}],
|
|
39338
|
+
"pipelineRunner.getParkedTrackFrame": [{
|
|
39339
|
+
name: "deviceId",
|
|
39340
|
+
form: "single",
|
|
39341
|
+
optional: false
|
|
39342
|
+
}],
|
|
39343
|
+
"pipelineRunner.parkTrackFrame": [{
|
|
39344
|
+
name: "deviceId",
|
|
39345
|
+
form: "single",
|
|
39346
|
+
optional: false
|
|
39347
|
+
}],
|
|
39348
|
+
"pipelineRunner.releaseParkedTrackFrames": [{
|
|
39349
|
+
name: "deviceId",
|
|
39350
|
+
form: "single",
|
|
39351
|
+
optional: false
|
|
39352
|
+
}],
|
|
39166
39353
|
"pipelineRunner.reportMotion": [{
|
|
39167
39354
|
name: "deviceId",
|
|
39168
39355
|
form: "single",
|
|
@@ -39854,7 +40041,7 @@ var PreviewTextField = string().max(200);
|
|
|
39854
40041
|
* Whether the notification's preview is a STILL or a short animation.
|
|
39855
40042
|
*
|
|
39856
40043
|
* The operator's ask, verbatim: *"inviato come gif o video (come per le altre
|
|
39857
|
-
* rule)"* —
|
|
40044
|
+
* rule)"* — the other rules already offer an animated preview, and a ten-hour
|
|
39858
40045
|
* night reads better as three seconds of motion than as one frame of it. Both
|
|
39859
40046
|
* modes get the SAME treatment (blurred frame, large centred title); `'gif'`
|
|
39860
40047
|
* simply applies it to a dozen frames sampled across the render and assembles
|