@camstack/system 1.2.272 → 1.2.273
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/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/camera-grid/addon.d.ts +13 -0
- package/dist/builtins/camera-grid/grid-canvas-seed.d.ts +62 -0
- package/dist/builtins/camera-grid/grid-canvas.d.ts +4 -2
- package/dist/builtins/camera-grid/grid-child.d.ts +10 -0
- package/dist/builtins/camera-grid/grid-compositor.d.ts +10 -0
- package/dist/builtins/camera-grid/index.js +321 -10
- package/dist/builtins/camera-grid/index.mjs +321 -10
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/cgroup-pids.d.ts +112 -0
- package/dist/builtins/native-metrics/native-metrics.addon.d.ts +1 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +209 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +209 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-_YWIPtUH.js → dist-D5ONNqL6.js} +187 -11
- package/dist/{dist-Diz59Dhi.mjs → dist-DrLhdcwI.mjs} +187 -11
- package/dist/index.js +12 -5
- package/dist/index.mjs +12 -5
- package/dist/kernel/moleculer/process-service.d.ts +54 -0
- package/dist/{retired-settings-keys-C2ocoH_l.js → retired-settings-keys-1686ou3s.js} +1 -1
- package/dist/{retired-settings-keys-C_Hg5d37.mjs → retired-settings-keys-Du_0qedo.mjs} +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $t as streamCatalogCapability, At as motionCapability, Jt as snapshotCapability, Pn as EventCategory, U as cameraGridLayoutCapability, Y as declarationOwnerNodeId, an as errMsg, b as MOTION_CLOSE_AFTER_MS, bn as createEvent, dn as DeviceFeature, f as DERIVED_CAMERA_SILENCED_CAP_NAMES, i as BaseDevice, on as buildFfmpegArgs, p as DeclaredDevices, pn as DeviceType, sn as BaseAddon, wn as hydrateSchema } from "../../dist-
|
|
1
|
+
import { $t as streamCatalogCapability, At as motionCapability, Jt as snapshotCapability, Pn as EventCategory, U as cameraGridLayoutCapability, Y as declarationOwnerNodeId, an as errMsg, b as MOTION_CLOSE_AFTER_MS, bn as createEvent, dn as DeviceFeature, f as DERIVED_CAMERA_SILENCED_CAP_NAMES, i as BaseDevice, on as buildFfmpegArgs, p as DeclaredDevices, pn as DeviceType, sn as BaseAddon, wn as hydrateSchema } from "../../dist-DrLhdcwI.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { FfmpegProcess } from "@camstack/types/node";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -2021,6 +2021,200 @@ async function silenceAnalysisFor(deps, deviceId) {
|
|
|
2021
2021
|
if (failures.length > 0) throw new Error(`camera grid ${deviceId}: could not switch off ${failures.length} analyzer(s) — it will run at full detection cost (${failures.join("; ")})`);
|
|
2022
2022
|
}
|
|
2023
2023
|
//#endregion
|
|
2024
|
+
//#region src/builtins/camera-grid/grid-canvas-seed.ts
|
|
2025
|
+
/**
|
|
2026
|
+
* What the canvas SAYS before a single camera has delivered a frame.
|
|
2027
|
+
*
|
|
2028
|
+
* ## Why black was not good enough
|
|
2029
|
+
*
|
|
2030
|
+
* The compositor emits from its very first tick (`grid-compositor.ts`) — that
|
|
2031
|
+
* is the whole point of a canvas, and it is what removed the ~9 s cold start.
|
|
2032
|
+
* But what it emitted until now was black, and black is the same picture for
|
|
2033
|
+
* four different situations: a grid with no cells, a grid whose sources are all
|
|
2034
|
+
* broken, a grid whose sources are merely slow, and a viewer that connected to
|
|
2035
|
+
* the wrong camera. An operator cannot tell them apart, and the one they
|
|
2036
|
+
* actually hit — sources still opening — is the one that fixes itself.
|
|
2037
|
+
*
|
|
2038
|
+
* The seed paints the LAYOUT instead: one card per cell, at the cell's own
|
|
2039
|
+
* rectangle, carrying the camera's name and the word `Waiting`. Three states
|
|
2040
|
+
* become three pictures — "no cards" is an empty grid, "a card still there" is
|
|
2041
|
+
* a tile that has not arrived, and "a picture" is a tile that has.
|
|
2042
|
+
*
|
|
2043
|
+
* It is honest about what it is. It is not a stand-in picture of the camera,
|
|
2044
|
+
* and it never carries a state it has not been told (a tile whose source is
|
|
2045
|
+
* genuinely broken is a matter for the tile's own decoder and its logs — this
|
|
2046
|
+
* says only "nothing has arrived here yet", which is true of both).
|
|
2047
|
+
*
|
|
2048
|
+
* ## Drawn ONCE, before the tick exists
|
|
2049
|
+
*
|
|
2050
|
+
* The seed is a full-canvas frame blitted in at the compositor's construction,
|
|
2051
|
+
* so the first tick already carries it and the tick never waits on anything.
|
|
2052
|
+
* Rendering it is the only asynchronous part, and it happens BEFORE the
|
|
2053
|
+
* compositor is built — never on the tick, which serves every cell.
|
|
2054
|
+
*
|
|
2055
|
+
* ## Why SVG → sharp, and why the conversion is here
|
|
2056
|
+
*
|
|
2057
|
+
* `snapshot-courtesy.ts` already renders text this way and states the reason:
|
|
2058
|
+
* shelling out to ffmpeg's `drawtext` was 52 ms against sharp's 4.8 ms, and an
|
|
2059
|
+
* SVG names a font FAMILY that fontconfig resolves instead of a font PATH that
|
|
2060
|
+
* has to be probed. This is that pattern, not a second one.
|
|
2061
|
+
*
|
|
2062
|
+
* What is different is the output. The canvas is yuv420p — that is what makes a
|
|
2063
|
+
* blit a strided memcpy — and sharp has no yuv output, so the raster is
|
|
2064
|
+
* converted once, here, by `rgbToYuv420p`. Once per composition start, over a
|
|
2065
|
+
* frame that is already being allocated; never per tick.
|
|
2066
|
+
*
|
|
2067
|
+
* ## Even, like everything else that touches the canvas
|
|
2068
|
+
*
|
|
2069
|
+
* The cards are the TILES' own rectangles, which `buildGridTilePlan` has
|
|
2070
|
+
* already snapped to even pixels — yuv420p subsamples chroma 2×2, and an odd
|
|
2071
|
+
* rectangle tints a tile rather than breaking it. Nothing here re-derives a
|
|
2072
|
+
* rectangle; it copies the one the decoder was sized against, so a card cannot
|
|
2073
|
+
* land anywhere other than exactly where its picture will.
|
|
2074
|
+
*/
|
|
2075
|
+
/**
|
|
2076
|
+
* The word a card carries. Deliberately not a fault: nothing has arrived YET,
|
|
2077
|
+
* which is true of a camera that is slow and of one that is broken, and this
|
|
2078
|
+
* placeholder knows which it is for neither.
|
|
2079
|
+
*/
|
|
2080
|
+
var GRID_SEED_WAITING_LABEL = "Waiting";
|
|
2081
|
+
/**
|
|
2082
|
+
* Family names, not paths — librsvg resolves them through fontconfig, and the
|
|
2083
|
+
* trailing generics keep a host without DejaVu rendering something legible
|
|
2084
|
+
* rather than nothing. Same stack as the courtesy frame, for the same reason.
|
|
2085
|
+
*/
|
|
2086
|
+
var SEED_FONT_STACK = "DejaVu Sans,DejaVu Sans Mono,Helvetica,Arial,sans-serif";
|
|
2087
|
+
/** The card. Dark enough to read as "empty", lit enough to read as "a cell". */
|
|
2088
|
+
var CARD_FILL = "#1b1b21";
|
|
2089
|
+
var CARD_BORDER = "#3f3f4c";
|
|
2090
|
+
var CARD_NAME = "#e8e8ee";
|
|
2091
|
+
var CARD_STATE = "#8f8f9e";
|
|
2092
|
+
/**
|
|
2093
|
+
* The label for one source.
|
|
2094
|
+
*
|
|
2095
|
+
* A name we could not read is not a reason to paint an anonymous rectangle: the
|
|
2096
|
+
* DEVICE ID is what every log line about that tile carries, so it is the one
|
|
2097
|
+
* label an operator can act on. A blank or whitespace name is treated as no
|
|
2098
|
+
* name for the same reason.
|
|
2099
|
+
*/
|
|
2100
|
+
function gridSeedName(names, deviceId) {
|
|
2101
|
+
const name = names.get(deviceId)?.trim();
|
|
2102
|
+
return name === void 0 || name.length === 0 ? `Camera ${String(deviceId)}` : name;
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* The seed a tile plan implies — pure, and the plan is the ONLY input to the
|
|
2106
|
+
* geometry. A card is its tile's rectangle, never a recomputed one.
|
|
2107
|
+
*/
|
|
2108
|
+
function buildGridSeedSpec(plan, names) {
|
|
2109
|
+
return {
|
|
2110
|
+
canvas: plan.canvas,
|
|
2111
|
+
cells: plan.tiles.map((tile) => ({
|
|
2112
|
+
origin: tile.origin,
|
|
2113
|
+
size: tile.size,
|
|
2114
|
+
name: gridSeedName(names, tile.deviceId)
|
|
2115
|
+
}))
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
/** XML escaping for text placed inside an SVG `<text>` node. */
|
|
2119
|
+
function escapeSeedText(value) {
|
|
2120
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
2121
|
+
}
|
|
2122
|
+
/**
|
|
2123
|
+
* The document the seed is rasterized from.
|
|
2124
|
+
*
|
|
2125
|
+
* No full-bleed background: the uncovered part of a canvas is NOT a cell — the
|
|
2126
|
+
* cells need not cover it (`grid-canvas-derivation.ts`) — and painting a card's
|
|
2127
|
+
* treatment over it would say "waiting" about a tile that does not exist. It
|
|
2128
|
+
* stays the canvas' own black.
|
|
2129
|
+
*
|
|
2130
|
+
* Each card clips its own text. A camera named for a whole street would
|
|
2131
|
+
* otherwise spill across the cell next to it, and a label on the wrong tile is
|
|
2132
|
+
* worse than a truncated one.
|
|
2133
|
+
*/
|
|
2134
|
+
function buildGridSeedSvg(spec) {
|
|
2135
|
+
const parts = [`<svg xmlns="http://www.w3.org/2000/svg" width="${String(spec.canvas.width)}" height="${String(spec.canvas.height)}">`];
|
|
2136
|
+
for (const [index, cell] of spec.cells.entries()) {
|
|
2137
|
+
const { width, height } = cell.size;
|
|
2138
|
+
const nameSize = Math.max(9, Math.min(Math.round(width / 12), Math.round(height / 8)));
|
|
2139
|
+
const stateSize = Math.max(8, Math.round(nameSize * .62));
|
|
2140
|
+
const stroke = Math.max(2, Math.round(Math.min(width, height) / 100));
|
|
2141
|
+
const inset = stroke / 2;
|
|
2142
|
+
const centreX = cell.origin.x + width / 2;
|
|
2143
|
+
const centreY = cell.origin.y + height / 2;
|
|
2144
|
+
const clipId = `seed-cell-${String(index)}`;
|
|
2145
|
+
parts.push(`<clipPath id="${clipId}"><rect x="${String(cell.origin.x)}" y="${String(cell.origin.y)}" width="${String(width)}" height="${String(height)}"/></clipPath>`, `<rect x="${String(cell.origin.x + inset)}" y="${String(cell.origin.y + inset)}" width="${String(width - stroke)}" height="${String(height - stroke)}" fill="${CARD_FILL}" stroke="${CARD_BORDER}" stroke-width="${String(stroke)}"/>`, `<g clip-path="url(#${clipId})" font-family="${SEED_FONT_STACK}" text-anchor="middle">`, `<text x="${String(centreX)}" y="${String(Math.round(centreY))}" font-size="${String(nameSize)}" fill="${CARD_NAME}">${escapeSeedText(cell.name)}</text>`, `<text x="${String(centreX)}" y="${String(Math.round(centreY + nameSize * 1.15))}" font-size="${String(stateSize)}" fill="${CARD_STATE}">${GRID_SEED_WAITING_LABEL}</text>`, `</g>`);
|
|
2146
|
+
}
|
|
2147
|
+
parts.push("</svg>");
|
|
2148
|
+
return parts.join("");
|
|
2149
|
+
}
|
|
2150
|
+
/** Limited-range BT.601, which is what a raw yuv420p stream handed to x264 is read as. */
|
|
2151
|
+
var Y_OFFSET = 16;
|
|
2152
|
+
var CHROMA_OFFSET = 128;
|
|
2153
|
+
function clampByte(value) {
|
|
2154
|
+
return Math.min(255, Math.max(0, Math.round(value)));
|
|
2155
|
+
}
|
|
2156
|
+
function luma(r, g, b) {
|
|
2157
|
+
return Y_OFFSET + (65.481 * r + 128.553 * g + 24.966 * b) / 255;
|
|
2158
|
+
}
|
|
2159
|
+
function chromaU(r, g, b) {
|
|
2160
|
+
return CHROMA_OFFSET + (-37.797 * r - 74.203 * g + 112 * b) / 255;
|
|
2161
|
+
}
|
|
2162
|
+
function chromaV(r, g, b) {
|
|
2163
|
+
return CHROMA_OFFSET + (112 * r - 93.786 * g - 18.214 * b) / 255;
|
|
2164
|
+
}
|
|
2165
|
+
/**
|
|
2166
|
+
* Packed RGB → yuv420p, once.
|
|
2167
|
+
*
|
|
2168
|
+
* Chroma is the average of each 2×2 block's colour, converted once per block —
|
|
2169
|
+
* which is what 4:2:0 means, and what keeps a card's border from fringing.
|
|
2170
|
+
* Sizes must be even; the canvas refuses anything else, so a caller that got
|
|
2171
|
+
* here with an odd size has a bug the blit would hide.
|
|
2172
|
+
*/
|
|
2173
|
+
function rgbToYuv420p(rgb, size) {
|
|
2174
|
+
const { width, height } = size;
|
|
2175
|
+
const out = Buffer.alloc(yuv420pSize(width, height));
|
|
2176
|
+
const lumaBytes = width * height;
|
|
2177
|
+
const chromaBytes = width / 2 * (height / 2);
|
|
2178
|
+
for (let y = 0; y < height; y += 1) for (let x = 0; x < width; x += 1) {
|
|
2179
|
+
const at = (y * width + x) * 3;
|
|
2180
|
+
out[y * width + x] = clampByte(luma(rgb[at] ?? 0, rgb[at + 1] ?? 0, rgb[at + 2] ?? 0));
|
|
2181
|
+
}
|
|
2182
|
+
const halfW = width / 2;
|
|
2183
|
+
for (let y = 0; y < height / 2; y += 1) for (let x = 0; x < halfW; x += 1) {
|
|
2184
|
+
let r = 0;
|
|
2185
|
+
let g = 0;
|
|
2186
|
+
let b = 0;
|
|
2187
|
+
for (let dy = 0; dy < 2; dy += 1) for (let dx = 0; dx < 2; dx += 1) {
|
|
2188
|
+
const at = ((y * 2 + dy) * width + (x * 2 + dx)) * 3;
|
|
2189
|
+
r += rgb[at] ?? 0;
|
|
2190
|
+
g += rgb[at + 1] ?? 0;
|
|
2191
|
+
b += rgb[at + 2] ?? 0;
|
|
2192
|
+
}
|
|
2193
|
+
const at = y * halfW + x;
|
|
2194
|
+
out[lumaBytes + at] = clampByte(chromaU(r / 4, g / 4, b / 4));
|
|
2195
|
+
out[lumaBytes + chromaBytes + at] = clampByte(chromaV(r / 4, g / 4, b / 4));
|
|
2196
|
+
}
|
|
2197
|
+
return out;
|
|
2198
|
+
}
|
|
2199
|
+
/**
|
|
2200
|
+
* Render the seed as one yuv420p frame the canvas can take whole.
|
|
2201
|
+
*
|
|
2202
|
+
* Rejects on failure rather than answering black: the caller decides, and it
|
|
2203
|
+
* decides to carry on without a seed — a grid that would not start because its
|
|
2204
|
+
* placeholder would not draw is a worse grid than one that starts black.
|
|
2205
|
+
*/
|
|
2206
|
+
async function renderGridSeed(spec) {
|
|
2207
|
+
const { default: sharp } = await import("sharp");
|
|
2208
|
+
const rgb = await sharp(Buffer.from(buildGridSeedSvg(spec))).flatten({ background: {
|
|
2209
|
+
r: 0,
|
|
2210
|
+
g: 0,
|
|
2211
|
+
b: 0
|
|
2212
|
+
} }).raw().toBuffer();
|
|
2213
|
+
const expected = spec.canvas.width * spec.canvas.height * 3;
|
|
2214
|
+
if (rgb.length !== expected) throw new Error(`camera-grid: the seed rasterized to ${String(rgb.length)} bytes, expected ${String(expected)}`);
|
|
2215
|
+
return rgbToYuv420p(rgb, spec.canvas);
|
|
2216
|
+
}
|
|
2217
|
+
//#endregion
|
|
2024
2218
|
//#region src/builtins/camera-grid/grid-tile-feed.ts
|
|
2025
2219
|
/**
|
|
2026
2220
|
* One source's decoded tiles, of which only the LATEST is ever wanted.
|
|
@@ -2115,11 +2309,16 @@ var GridTileFeed = class {
|
|
|
2115
2309
|
*
|
|
2116
2310
|
* ## It also removes the wait
|
|
2117
2311
|
*
|
|
2118
|
-
* A cell with no frame yet is
|
|
2119
|
-
*
|
|
2312
|
+
* A cell with no frame yet is not a reason to hold the picture back. The output
|
|
2313
|
+
* exists from the first tick and cells fill in as their sources arrive —
|
|
2120
2314
|
* instead of the whole grid waiting for the slowest source's first key frame,
|
|
2121
2315
|
* which is what the ~9 s cold start was.
|
|
2122
2316
|
*
|
|
2317
|
+
* What a cell with no frame yet SHOWS is the seed (`grid-canvas-seed.ts`),
|
|
2318
|
+
* blitted in once at construction: its own rectangle, with its camera's name
|
|
2319
|
+
* and the word `Waiting`. It was black, and black said the same thing about an
|
|
2320
|
+
* empty grid, a broken grid and a grid that is merely still opening.
|
|
2321
|
+
*
|
|
2123
2322
|
* ## What it costs
|
|
2124
2323
|
*
|
|
2125
2324
|
* The same N decodes ffmpeg already paid, in N children instead of one, plus
|
|
@@ -2138,6 +2337,10 @@ var GridCompositor = class {
|
|
|
2138
2337
|
constructor(deps) {
|
|
2139
2338
|
this.deps = deps;
|
|
2140
2339
|
this.canvas = new GridCanvas(deps.plan.canvas.width, deps.plan.canvas.height);
|
|
2340
|
+
if (deps.seed !== null) this.canvas.blit(deps.seed, deps.plan.canvas, {
|
|
2341
|
+
x: 0,
|
|
2342
|
+
y: 0
|
|
2343
|
+
});
|
|
2141
2344
|
for (const tile of deps.plan.tiles) this.placed.set(tile.id, {
|
|
2142
2345
|
tile,
|
|
2143
2346
|
feed: new GridTileFeed(tile.frameBytes)
|
|
@@ -2380,11 +2583,17 @@ function gridCanvasEncoderInvocation(input) {
|
|
|
2380
2583
|
*
|
|
2381
2584
|
* ## The order things start in, and why it is the order
|
|
2382
2585
|
*
|
|
2383
|
-
* The
|
|
2384
|
-
* canvas from its very first beat, so the encoder produces a stream
|
|
2385
|
-
* consumer sees a picture — before any camera has delivered anything.
|
|
2386
|
-
* the cold start the old shape spent waiting for the slowest source's
|
|
2387
|
-
* frame, and it is now spent watching cells fill in.
|
|
2586
|
+
* The seed first, then the encoder, then the tick, then the decoders. The tick
|
|
2587
|
+
* writes the canvas from its very first beat, so the encoder produces a stream
|
|
2588
|
+
* — and the consumer sees a picture — before any camera has delivered anything.
|
|
2589
|
+
* That is the cold start the old shape spent waiting for the slowest source's
|
|
2590
|
+
* first key frame, and it is now spent watching cells fill in.
|
|
2591
|
+
*
|
|
2592
|
+
* What that first picture SAYS is the seed (`grid-canvas-seed.ts`): one card
|
|
2593
|
+
* per cell, at the cell's own rectangle, naming its camera. It is drawn once,
|
|
2594
|
+
* here, before the compositor is built — a tick must never wait on a render —
|
|
2595
|
+
* and it is bounded and fallible: a seed that cannot be drawn leaves the canvas
|
|
2596
|
+
* black, which is what it was before, and never stops the composition.
|
|
2388
2597
|
*
|
|
2389
2598
|
* ## Back-pressure is a SKIPPED tick, never a queue
|
|
2390
2599
|
*
|
|
@@ -2431,6 +2640,77 @@ var DECODER_FIRST_DATA_MS = 15e3;
|
|
|
2431
2640
|
* simply replaced while its cell holds the last frame it produced.
|
|
2432
2641
|
*/
|
|
2433
2642
|
var TILE_MAX_RESTARTS = 5;
|
|
2643
|
+
/**
|
|
2644
|
+
* How long the seed may wait for the source cameras' names.
|
|
2645
|
+
*
|
|
2646
|
+
* The seed is drawn BEFORE the encoder is spawned — that is what puts it on the
|
|
2647
|
+
* first tick — so everything it waits for is added to the cold start the rest
|
|
2648
|
+
* of this file exists to keep short. A name is a nicety; the card exists
|
|
2649
|
+
* either way, labelled by device id. So the read is bounded and the deadline
|
|
2650
|
+
* is not an error.
|
|
2651
|
+
*/
|
|
2652
|
+
var SEED_NAMES_TIMEOUT_MS = 1500;
|
|
2653
|
+
/**
|
|
2654
|
+
* The names for the cards, or an empty map.
|
|
2655
|
+
*
|
|
2656
|
+
* Never throws and never delays past the deadline: an unnamed card is a
|
|
2657
|
+
* complete card (`gridSeedName` falls back to the device id), and a grid that
|
|
2658
|
+
* started late because a name lookup was slow would be a worse grid.
|
|
2659
|
+
*/
|
|
2660
|
+
async function resolveSeedNames(ctx, deviceIds) {
|
|
2661
|
+
const resolve = ctx.resolveSourceNames;
|
|
2662
|
+
if (resolve === void 0) return /* @__PURE__ */ new Map();
|
|
2663
|
+
const empty = /* @__PURE__ */ new Map();
|
|
2664
|
+
return new Promise((settle) => {
|
|
2665
|
+
const timer = setTimeout(() => {
|
|
2666
|
+
ctx.logger.warn("camera grid: the seed could not read the source names in time", {
|
|
2667
|
+
tags: { deviceId: ctx.deviceId },
|
|
2668
|
+
meta: {
|
|
2669
|
+
sources: deviceIds.length,
|
|
2670
|
+
timeoutMs: SEED_NAMES_TIMEOUT_MS
|
|
2671
|
+
}
|
|
2672
|
+
});
|
|
2673
|
+
settle(empty);
|
|
2674
|
+
}, SEED_NAMES_TIMEOUT_MS);
|
|
2675
|
+
timer.unref();
|
|
2676
|
+
resolve(deviceIds).then((names) => {
|
|
2677
|
+
clearTimeout(timer);
|
|
2678
|
+
settle(names);
|
|
2679
|
+
}, (error) => {
|
|
2680
|
+
clearTimeout(timer);
|
|
2681
|
+
ctx.logger.warn("camera grid: the seed could not read the source names", {
|
|
2682
|
+
tags: { deviceId: ctx.deviceId },
|
|
2683
|
+
meta: {
|
|
2684
|
+
sources: deviceIds.length,
|
|
2685
|
+
error: errMsg(error)
|
|
2686
|
+
}
|
|
2687
|
+
});
|
|
2688
|
+
settle(empty);
|
|
2689
|
+
});
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
/**
|
|
2693
|
+
* The picture the canvas starts as: one card per cell, naming its camera.
|
|
2694
|
+
*
|
|
2695
|
+
* `null` — black, which is what this was before — when it cannot be drawn. A
|
|
2696
|
+
* placeholder that refused to render must never be the reason a grid does not
|
|
2697
|
+
* start, so every failure here is a warning and a composition that carries on.
|
|
2698
|
+
*/
|
|
2699
|
+
async function buildCanvasSeed(ctx, tilePlan) {
|
|
2700
|
+
const names = await resolveSeedNames(ctx, [...new Set(tilePlan.tiles.map((tile) => tile.deviceId))]);
|
|
2701
|
+
try {
|
|
2702
|
+
return await renderGridSeed(buildGridSeedSpec(tilePlan, names));
|
|
2703
|
+
} catch (error) {
|
|
2704
|
+
ctx.logger.warn("camera grid: the canvas seed could not be drawn - starting black", {
|
|
2705
|
+
tags: { deviceId: ctx.deviceId },
|
|
2706
|
+
meta: {
|
|
2707
|
+
error: errMsg(error),
|
|
2708
|
+
tiles: tilePlan.tiles.length
|
|
2709
|
+
}
|
|
2710
|
+
});
|
|
2711
|
+
return null;
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2434
2714
|
async function startGridCompositorChild(ctx, plan, sources, onEnded) {
|
|
2435
2715
|
const tilePlan = buildGridTilePlan({
|
|
2436
2716
|
canvas: {
|
|
@@ -2450,12 +2730,14 @@ async function startGridCompositorChild(ctx, plan, sources, onEnded) {
|
|
|
2450
2730
|
ended = true;
|
|
2451
2731
|
onEnded(reason);
|
|
2452
2732
|
};
|
|
2733
|
+
const seed = await buildCanvasSeed(ctx, tilePlan);
|
|
2453
2734
|
let encoderIn = null;
|
|
2454
2735
|
let encoderOut = null;
|
|
2455
2736
|
let draining = false;
|
|
2456
2737
|
let tick = null;
|
|
2457
2738
|
const compositor = new GridCompositor({
|
|
2458
2739
|
plan: tilePlan,
|
|
2740
|
+
seed,
|
|
2459
2741
|
write: (frame) => {
|
|
2460
2742
|
const sink = encoderIn;
|
|
2461
2743
|
if (sink === null || draining || sink.destroyed) return false;
|
|
@@ -2572,7 +2854,8 @@ async function startGridCompositorChild(ctx, plan, sources, onEnded) {
|
|
|
2572
2854
|
meta: {
|
|
2573
2855
|
tiles: tilePlan.tiles.length,
|
|
2574
2856
|
canvas: `${String(tilePlan.canvas.width)}x${String(tilePlan.canvas.height)}`,
|
|
2575
|
-
fps: plan.fps
|
|
2857
|
+
fps: plan.fps,
|
|
2858
|
+
seeded: seed !== null
|
|
2576
2859
|
}
|
|
2577
2860
|
});
|
|
2578
2861
|
return {
|
|
@@ -3001,11 +3284,39 @@ var CameraGridAddon = class extends BaseAddon {
|
|
|
3001
3284
|
this.sessions.set(key, new GridSessionEntry(instanceId, profile, this.makeSession(instanceId, deviceId, profile)));
|
|
3002
3285
|
}
|
|
3003
3286
|
}
|
|
3287
|
+
/**
|
|
3288
|
+
* The source cameras' names, for the cards the canvas is seeded with.
|
|
3289
|
+
*
|
|
3290
|
+
* Asked of the device manager, which OWNS the name, at the moment the seed is
|
|
3291
|
+
* drawn — never kept here. A consumer that mirrored this would be stale on
|
|
3292
|
+
* exactly the write an operator makes (D224), for a label.
|
|
3293
|
+
*
|
|
3294
|
+
* A device that cannot be read is simply absent from the map, and its card
|
|
3295
|
+
* carries the device id instead. One unreadable camera never costs the other
|
|
3296
|
+
* cards their names, which is why these are settled rather than awaited as
|
|
3297
|
+
* one batch.
|
|
3298
|
+
*/
|
|
3299
|
+
async sourceNames(deviceIds) {
|
|
3300
|
+
const names = /* @__PURE__ */ new Map();
|
|
3301
|
+
const results = await Promise.allSettled(deviceIds.map(async (deviceId) => {
|
|
3302
|
+
return {
|
|
3303
|
+
deviceId,
|
|
3304
|
+
name: (await this.ctx.api.deviceManager.getDevice.query({ deviceId }))?.name ?? null
|
|
3305
|
+
};
|
|
3306
|
+
}));
|
|
3307
|
+
for (const result of results) {
|
|
3308
|
+
if (result.status !== "fulfilled") continue;
|
|
3309
|
+
const { deviceId, name } = result.value;
|
|
3310
|
+
if (name !== null) names.set(deviceId, name);
|
|
3311
|
+
}
|
|
3312
|
+
return names;
|
|
3313
|
+
}
|
|
3004
3314
|
makeSession(instanceId, deviceId, profile) {
|
|
3005
3315
|
const childContext = {
|
|
3006
3316
|
binaryPath: this.ffmpegBinaryPath,
|
|
3007
3317
|
logger: this.ctx.logger,
|
|
3008
|
-
deviceId
|
|
3318
|
+
deviceId,
|
|
3319
|
+
resolveSourceNames: (deviceIds) => this.sourceNames(deviceIds)
|
|
3009
3320
|
};
|
|
3010
3321
|
const session = new GridStreamSession({
|
|
3011
3322
|
deviceId,
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D5ONNqL6.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/console-logging/console-destination.ts
|
|
9
9
|
var LEVEL_RANK = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ct as logDestinationCapability, sn as BaseAddon } from "../../dist-
|
|
1
|
+
import { Ct as logDestinationCapability, sn as BaseAddon } from "../../dist-DrLhdcwI.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/console-logging/console-destination.ts
|
|
4
4
|
var LEVEL_RANK = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-D5ONNqL6.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
let node_fs_promises = require("node:fs/promises");
|
|
6
6
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as coreBlockAddonId, K as coreBlockIdFromAddonId, an as errMsg, c as CORE_BLOCKS_ADDON_ID, l as CORE_BLOCK_ADDON_PREFIX, p as DeclaredDevices, q as coreBlocksCapability, sn as BaseAddon, u as CoreBlockSchema } from "../../dist-
|
|
1
|
+
import { G as coreBlockAddonId, K as coreBlockIdFromAddonId, an as errMsg, c as CORE_BLOCKS_ADDON_ID, l as CORE_BLOCK_ADDON_PREFIX, p as DeclaredDevices, q as coreBlocksCapability, sn as BaseAddon, u as CoreBlockSchema } from "../../dist-DrLhdcwI.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
7
|
-
const require_retired_settings_keys = require("../../retired-settings-keys-
|
|
6
|
+
const require_dist = require("../../dist-D5ONNqL6.js");
|
|
7
|
+
const require_retired_settings_keys = require("../../retired-settings-keys-1686ou3s.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let _camstack_types_node = require("@camstack/types/node");
|
|
10
10
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as deviceStateCapability, D as STREAM_PROFILE_META, H as buildStreamParamsConfigSchema, Ht as runtimeStatePolicyFor, Nn as sleep$1, Nt as parseStreamParamsFormPatch, Pn as EventCategory, Q as deviceManagerCapability, St as locationIconKey, Tn as isDeviceConfigCap, an as errMsg, dn as DeviceFeature, et as deviceStatusCapability, fn as DeviceRole, gn as WELL_KNOWN_TAB_MAP, it as enumerateSchemaFields, jt as normalizeUnit, m as DeviceStatusSchema, o as CAMERA_SWITCH_ORDER, pn as DeviceType, qt as siteZoneKey, rt as enumerateItemArrayFields, s as CAP_NAMES_WITH_STATUS, sn as BaseAddon, t as ALL_CAPABILITY_DEFINITIONS, v as LocationIconIdSchema, zt as resolveContainerPrimaryChild } from "../../dist-
|
|
2
|
-
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-
|
|
1
|
+
import { $ as deviceStateCapability, D as STREAM_PROFILE_META, H as buildStreamParamsConfigSchema, Ht as runtimeStatePolicyFor, Nn as sleep$1, Nt as parseStreamParamsFormPatch, Pn as EventCategory, Q as deviceManagerCapability, St as locationIconKey, Tn as isDeviceConfigCap, an as errMsg, dn as DeviceFeature, et as deviceStatusCapability, fn as DeviceRole, gn as WELL_KNOWN_TAB_MAP, it as enumerateSchemaFields, jt as normalizeUnit, m as DeviceStatusSchema, o as CAMERA_SWITCH_ORDER, pn as DeviceType, qt as siteZoneKey, rt as enumerateItemArrayFields, s as CAP_NAMES_WITH_STATUS, sn as BaseAddon, t as ALL_CAPABILITY_DEFINITIONS, v as LocationIconIdSchema, zt as resolveContainerPrimaryChild } from "../../dist-DrLhdcwI.mjs";
|
|
2
|
+
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-Du_0qedo.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { canonicalDeviceFingerprint } from "@camstack/types/node";
|
|
5
5
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D5ONNqL6.js");
|
|
7
7
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
8
8
|
/**
|
|
9
9
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as bareAddonId, Pn as EventCategory, T as SOURCE_DEVICE_TYPES, an as errMsg, at as evaluateSensorEdge, bn as createEvent, gt as isSourceCap, ht as isSameAddonId, pn as DeviceType, sn as BaseAddon, tt as doorbellCapability, w as SOURCE_CAPS } from "../../dist-
|
|
1
|
+
import { B as bareAddonId, Pn as EventCategory, T as SOURCE_DEVICE_TYPES, an as errMsg, at as evaluateSensorEdge, bn as createEvent, gt as isSourceCap, ht as isSameAddonId, pn as DeviceType, sn as BaseAddon, tt as doorbellCapability, w as SOURCE_CAPS } from "../../dist-DrLhdcwI.mjs";
|
|
2
2
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
3
3
|
/**
|
|
4
4
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D5ONNqL6.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
|
|
9
9
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ct as logDestinationCapability, sn as BaseAddon } from "../../dist-
|
|
1
|
+
import { Ct as logDestinationCapability, sn as BaseAddon } from "../../dist-DrLhdcwI.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
|
|
4
4
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-D5ONNqL6.js");
|
|
4
4
|
//#region src/builtins/liveness-monitor/liveness-checks.ts
|
|
5
5
|
var NO_DEVICES = "liveness:no-devices";
|
|
6
6
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as RecordingStatusSchema, Pn as EventCategory, an as errMsg, bn as createEvent, sn as BaseAddon } from "../../dist-
|
|
1
|
+
import { C as RecordingStatusSchema, Pn as EventCategory, an as errMsg, bn as createEvent, sn as BaseAddon } from "../../dist-DrLhdcwI.mjs";
|
|
2
2
|
//#region src/builtins/liveness-monitor/liveness-checks.ts
|
|
3
3
|
var NO_DEVICES = "liveness:no-devices";
|
|
4
4
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D5ONNqL6.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
9
9
|
let crypto$1 = require("crypto");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { N as UserRecordSchema, O as ScopedTokenSchema, R as authProviderCapability, r as ApiKeyRecordSchema, sn as BaseAddon, tn as userManagementCapability } from "../../dist-
|
|
2
|
+
import { N as UserRecordSchema, O as ScopedTokenSchema, R as authProviderCapability, r as ApiKeyRecordSchema, sn as BaseAddon, tn as userManagementCapability } from "../../dist-DrLhdcwI.mjs";
|
|
3
3
|
import * as crypto$2 from "node:crypto";
|
|
4
4
|
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
5
5
|
import nodeCrypto from "crypto";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-D5ONNqL6.js");
|
|
4
4
|
const require_tls = require("../../tls-BFs4PzoW.js");
|
|
5
5
|
let node_fs_promises = require("node:fs/promises");
|
|
6
6
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Pn as EventCategory, sn as BaseAddon, xt as localNetworkCapability } from "../../dist-
|
|
1
|
+
import { Pn as EventCategory, sn as BaseAddon, xt as localNetworkCapability } from "../../dist-DrLhdcwI.mjs";
|
|
2
2
|
import { S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, i as writeExtraSans, l as reissueTlsLeaf, n as readTlsAccessStatus, o as validateUploadedTls, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, y as applyLanHttp } from "../../tls-2xbgg78V.mjs";
|
|
3
3
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D5ONNqL6.js");
|
|
7
7
|
//#region src/builtins/loki-logging/loki-payload.ts
|
|
8
8
|
/**
|
|
9
9
|
* Loki rejects a label name that is not a valid Prometheus label
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ct as logDestinationCapability, sn as BaseAddon } from "../../dist-
|
|
1
|
+
import { Ct as logDestinationCapability, sn as BaseAddon } from "../../dist-DrLhdcwI.mjs";
|
|
2
2
|
//#region src/builtins/loki-logging/loki-payload.ts
|
|
3
3
|
/**
|
|
4
4
|
* Loki rejects a label name that is not a valid Prometheus label
|