@camstack/addon-pipeline 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-analyzer/index.js +1 -1
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +2 -2
- package/dist/detection-pipeline/index.mjs +2 -2
- package/dist/{dist-DWy-H6bo.js → dist-CVjqvU8G.js} +95 -13
- package/dist/{dist-DLWXeXC1.mjs → dist-ClyAFBZk.mjs} +95 -13
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +228 -24
- package/dist/pipeline-runner/index.mjs +228 -24
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/session-decode/decode-worker-child.js +55 -7
- package/dist/session-decode/decode-worker-child.mjs +55 -7
- package/dist/{step-definitions-BQG_6dmZ.js → step-definitions-D76MMwbo.js} +1 -1
- package/dist/{step-definitions-CVk19Y0u.mjs → step-definitions-DMOw4fzn.mjs} +1 -1
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CpB1jG0N.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-7jRy3Uzr.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CQC_f4Yv.mjs +26 -0
- package/dist/stream-broker/{hostInit--Zair8Av.mjs → hostInit-4pxims1d.mjs} +3 -3
- package/dist/stream-broker/index.js +1 -1
- package/dist/stream-broker/index.mjs +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-CyVJTZEO.mjs → worker-protocol-D7RzZIla.mjs} +16 -2
- package/dist/{worker-protocol-PP4jKHHJ.js → worker-protocol-DextwlTX.js} +21 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-sOOxiGvP.js → MaskShapeCanvas-DI4BY7W2-BPRZW9TX.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-DdTQuT23.js → MotionZonesSettings-NcxxQN8r-Bw6Nlg-Q.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BrR0L3ql.js → PrivacyMaskSettings-APgPLF7p-BLF9QKG8.js} +1 -1
- package/embed-dist/assets/{index-Dk21l1_3.js → index-DiUhKfSk.js} +13 -13
- package/embed-dist/assets/index-Dv5H-32C.css +2 -0
- package/embed-dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BflyNuZv.mjs +0 -26
- package/embed-dist/assets/index-D4MGuECB.css +0 -2
|
@@ -3,11 +3,11 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-D6vf50IK.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
6
|
+
const require_dist = require("../dist-CVjqvU8G.js");
|
|
7
7
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
8
8
|
const require_sheet_geometry = require("../sheet-geometry-BR-ip0CP.js");
|
|
9
|
-
const require_worker_protocol = require("../worker-protocol-
|
|
10
|
-
const require_step_definitions = require("../step-definitions-
|
|
9
|
+
const require_worker_protocol = require("../worker-protocol-DextwlTX.js");
|
|
10
|
+
const require_step_definitions = require("../step-definitions-D76MMwbo.js");
|
|
11
11
|
let node_child_process = require("node:child_process");
|
|
12
12
|
let node_url = require("node:url");
|
|
13
13
|
let sharp = require("sharp");
|
|
@@ -1152,6 +1152,17 @@ function summarizeDeviceLoad(attachedDeviceKeys) {
|
|
|
1152
1152
|
})).toSorted((a, b) => a.deviceKey.localeCompare(b.deviceKey));
|
|
1153
1153
|
}
|
|
1154
1154
|
//#endregion
|
|
1155
|
+
//#region src/pipeline-runner/native-crop-tier.ts
|
|
1156
|
+
/**
|
|
1157
|
+
* Pure tier decision for `getNativeCrop`. A native HIT always resolves to the
|
|
1158
|
+
* native surface. On a native MISS, only a full-frame request falls back to the
|
|
1159
|
+
* ≤640 RAM store; an ROI miss is reported as `miss` (no quality masquerade).
|
|
1160
|
+
*/
|
|
1161
|
+
function resolveNativeCropSource(isFullFrame, nativeHit) {
|
|
1162
|
+
if (nativeHit) return "native";
|
|
1163
|
+
return isFullFrame ? "ram-fullframe" : "miss";
|
|
1164
|
+
}
|
|
1165
|
+
//#endregion
|
|
1155
1166
|
//#region src/session-decode/fork-decode-worker.ts
|
|
1156
1167
|
/**
|
|
1157
1168
|
* Real `fork` implementation for the session-decode coordinator — Epic C P1,
|
|
@@ -1669,11 +1680,18 @@ var SessionDecodeSession = class {
|
|
|
1669
1680
|
});
|
|
1670
1681
|
return;
|
|
1671
1682
|
case "nativeCropMiss":
|
|
1683
|
+
if (raw.reason !== void 0) this.logger.debug("session-decode: native crop miss", { meta: {
|
|
1684
|
+
requestId: raw.requestId,
|
|
1685
|
+
reason: raw.reason
|
|
1686
|
+
} });
|
|
1672
1687
|
this.settleNativeCrop(raw.requestId, null);
|
|
1673
1688
|
return;
|
|
1674
1689
|
case "error":
|
|
1675
1690
|
this.settleError(raw.message, raw.frameId);
|
|
1676
1691
|
return;
|
|
1692
|
+
case "log":
|
|
1693
|
+
this.logger[raw.level](raw.message);
|
|
1694
|
+
return;
|
|
1677
1695
|
}
|
|
1678
1696
|
}
|
|
1679
1697
|
handleExit(info) {
|
|
@@ -2242,13 +2260,93 @@ async function runDetailSubtree(deps, input) {
|
|
|
2242
2260
|
if (!crop) return null;
|
|
2243
2261
|
const details = [];
|
|
2244
2262
|
for (const step of steps) {
|
|
2245
|
-
const
|
|
2263
|
+
const deviceKey = deps.resolveStepDevice?.(input.deviceId, step);
|
|
2264
|
+
const result = await deps.runPipeline([step], crop.jpeg, input.deviceId, deviceKey);
|
|
2246
2265
|
if (!result) continue;
|
|
2247
2266
|
for (const detection of result.detections) details.push(toDetailResult(detection, step.addonId, crop));
|
|
2248
2267
|
}
|
|
2249
2268
|
return { details };
|
|
2250
2269
|
}
|
|
2251
2270
|
//#endregion
|
|
2271
|
+
//#region src/pipeline-runner/device-jump.ts
|
|
2272
|
+
/**
|
|
2273
|
+
* Step-tree device jump (same-node, phase 1) — the PURE resolution core.
|
|
2274
|
+
*
|
|
2275
|
+
* Given the camera's elected ("effective") inference device and the node's
|
|
2276
|
+
* enabled-device roster, decide which device ONE enrichment step should run on:
|
|
2277
|
+
*
|
|
2278
|
+
* 1. A manual `jumpDeviceKey` override wins when it names a candidate that can
|
|
2279
|
+
* run the step (bypasses the auto choice; operator intent).
|
|
2280
|
+
* 2. Otherwise, if the effective device's format can run the step's model →
|
|
2281
|
+
* NO jump (the step inherits the effective device — the common case).
|
|
2282
|
+
* 3. Otherwise AUTO-jump: among the node's OTHER enabled devices whose format
|
|
2283
|
+
* can run the step (and that are under their `maxSessions` cap), pick the
|
|
2284
|
+
* weighted-least-loaded one — mirroring the balancer's per-device core.
|
|
2285
|
+
* 4. If nothing on the node can run it → `no-candidate` (the caller emits the
|
|
2286
|
+
* loud once-per-(camera,step) warn that replaces today's silent degrade).
|
|
2287
|
+
*
|
|
2288
|
+
* Framework-free + side-effect-free so the whole decision table is unit-testable
|
|
2289
|
+
* without a live runner/catalog. The `canRun(format)` predicate is injected by
|
|
2290
|
+
* the runner (which owns the model catalog); this module never imports it.
|
|
2291
|
+
*/
|
|
2292
|
+
/** The `ModelFormat` of a `<backend>:<device>` key, or `undefined` for an unknown backend. */
|
|
2293
|
+
function formatOf(deviceKey) {
|
|
2294
|
+
const colon = deviceKey.indexOf(":");
|
|
2295
|
+
return require_dist.DEVICE_BACKEND_TO_FORMAT[colon >= 0 ? deviceKey.slice(0, colon) : deviceKey];
|
|
2296
|
+
}
|
|
2297
|
+
/** True when the step's model has a build for `deviceKey`'s format. */
|
|
2298
|
+
function canRunOn(deviceKey, canRun) {
|
|
2299
|
+
const format = formatOf(deviceKey);
|
|
2300
|
+
return format !== void 0 && canRun(format);
|
|
2301
|
+
}
|
|
2302
|
+
/** Effective weighted load of a device (session count ÷ weight); lower = preferred. */
|
|
2303
|
+
function weightedLoad(d) {
|
|
2304
|
+
return d.attachedCameras / (d.weight > 0 ? d.weight : 1);
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Weighted-least-loaded pick with a deterministic `deviceKey` tie-break —
|
|
2308
|
+
* mirrors the node/device balancer's decision core (least `load/weight`, ties
|
|
2309
|
+
* broken stably). `devices` is assumed non-empty.
|
|
2310
|
+
*/
|
|
2311
|
+
function pickWeightedLeastLoaded(devices) {
|
|
2312
|
+
return devices.reduce((best, d) => {
|
|
2313
|
+
const dScore = weightedLoad(d);
|
|
2314
|
+
const bScore = weightedLoad(best);
|
|
2315
|
+
if (dScore < bScore) return d;
|
|
2316
|
+
if (dScore > bScore) return best;
|
|
2317
|
+
return d.deviceKey < best.deviceKey ? d : best;
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Resolve the device one enrichment step runs on. See the module header for the
|
|
2322
|
+
* decision table. Pure — never throws, no I/O.
|
|
2323
|
+
*/
|
|
2324
|
+
function resolveStepDevice(input) {
|
|
2325
|
+
const { effectiveDeviceKey, manualJumpDeviceKey, canRun } = input;
|
|
2326
|
+
if (manualJumpDeviceKey !== void 0 && manualJumpDeviceKey !== effectiveDeviceKey) {
|
|
2327
|
+
const target = input.devices.find((d) => d.deviceKey === manualJumpDeviceKey);
|
|
2328
|
+
if (target && canRunOn(target.deviceKey, canRun)) return {
|
|
2329
|
+
kind: "jumped",
|
|
2330
|
+
deviceKey: target.deviceKey,
|
|
2331
|
+
reason: "manual"
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
if (canRunOn(effectiveDeviceKey, canRun)) return {
|
|
2335
|
+
kind: "no-jump",
|
|
2336
|
+
deviceKey: effectiveDeviceKey
|
|
2337
|
+
};
|
|
2338
|
+
const runnable = input.devices.filter((d) => d.deviceKey !== effectiveDeviceKey && (d.maxSessions === null || d.attachedCameras < d.maxSessions) && canRunOn(d.deviceKey, canRun));
|
|
2339
|
+
if (runnable.length === 0) return {
|
|
2340
|
+
kind: "no-candidate",
|
|
2341
|
+
deviceKey: effectiveDeviceKey
|
|
2342
|
+
};
|
|
2343
|
+
return {
|
|
2344
|
+
kind: "jumped",
|
|
2345
|
+
deviceKey: pickWeightedLeastLoaded(runnable).deviceKey,
|
|
2346
|
+
reason: "auto"
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
//#endregion
|
|
2252
2350
|
//#region src/pipeline-runner/index.ts
|
|
2253
2351
|
var DEFAULT_CONFIG = {
|
|
2254
2352
|
maxQueueDepth: 30,
|
|
@@ -3184,31 +3282,77 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3184
3282
|
return { success: true };
|
|
3185
3283
|
}
|
|
3186
3284
|
/**
|
|
3187
|
-
*
|
|
3188
|
-
*
|
|
3189
|
-
*
|
|
3190
|
-
*
|
|
3191
|
-
*
|
|
3285
|
+
* Native-resolution crop for a retention-ring frame (native-crop feature).
|
|
3286
|
+
* Looks the handle up in {@link nativeCropRegistry}; the worker downloads ONLY
|
|
3287
|
+
* the ROI — a full native frame never travels the wire.
|
|
3288
|
+
*
|
|
3289
|
+
* Tier decision ({@link resolveNativeCropSource}): a native-lease HIT always
|
|
3290
|
+
* wins. On a MISS (never registered, or the worker's tiny native ring already
|
|
3291
|
+
* evicted the frame), an ROI (subject-crop) request returns `null` so the
|
|
3292
|
+
* caller reports the miss and RETRIES — it is NEVER handed the ≤640 detection
|
|
3293
|
+
* raster masquerading as a native crop (Task S1). A full-frame request keeps
|
|
3294
|
+
* the ≤640 RAM `RetainedFrameStore` fallback (blank-frame guard / 640-snapshot
|
|
3295
|
+
* resolve legitimately want the lower-RES honest frame).
|
|
3192
3296
|
*/
|
|
3193
3297
|
async getNativeCrop(input) {
|
|
3194
3298
|
const b = input.bbox;
|
|
3195
|
-
|
|
3299
|
+
const isFullFrame = b.x <= .001 && b.y <= .001 && b.w >= .999 && b.h >= .999;
|
|
3300
|
+
const isUnboundedFullFrame = isFullFrame && input.maxWidth === void 0;
|
|
3301
|
+
let nativeResult = null;
|
|
3302
|
+
if (!isUnboundedFullFrame) {
|
|
3196
3303
|
const requester = this.nativeCropRegistry.get(input.handle);
|
|
3197
3304
|
if (requester) {
|
|
3198
3305
|
const result = await requester(input.bbox, input.maxWidth);
|
|
3199
|
-
if (result)
|
|
3306
|
+
if (result) nativeResult = {
|
|
3200
3307
|
bytes: result.bytes,
|
|
3201
3308
|
width: result.width,
|
|
3202
3309
|
height: result.height
|
|
3203
3310
|
};
|
|
3204
3311
|
}
|
|
3205
3312
|
}
|
|
3313
|
+
const tier = resolveNativeCropSource(isFullFrame, nativeResult !== null);
|
|
3314
|
+
if (tier === "native" && nativeResult) return this.encodeNativeCropResult(nativeResult, tier, input.encodeJpeg);
|
|
3315
|
+
if (tier !== "ram-fullframe") return null;
|
|
3206
3316
|
const crop = this.retainedFrames?.crop(input.handle, input.bbox, input.maxWidth);
|
|
3207
|
-
|
|
3317
|
+
if (!crop) return null;
|
|
3318
|
+
return this.encodeNativeCropResult({
|
|
3208
3319
|
bytes: crop.bytes,
|
|
3209
3320
|
width: crop.width,
|
|
3210
3321
|
height: crop.height
|
|
3211
|
-
}
|
|
3322
|
+
}, tier, input.encodeJpeg);
|
|
3323
|
+
}
|
|
3324
|
+
/**
|
|
3325
|
+
* Shape a resolved RGB crop into a `getNativeCrop` result. Raw path (default):
|
|
3326
|
+
* return the zero-copy `bytes` — same-node callers encode locally. Compressed
|
|
3327
|
+
* path (`encodeJpeg`, the CROSS-NODE case): encode to JPEG in-process, on the
|
|
3328
|
+
* OWNING node, and return `jpeg` (base64) with NO `bytes` — the payload then
|
|
3329
|
+
* fits Moleculer's `maxPacketSize` and no raw pixels cross the process
|
|
3330
|
+
* boundary (CLAUDE.md invariant #21). On an encode failure the crop is a miss
|
|
3331
|
+
* (`null`) — the caller retries — never a silent raw-RGB cross-node fallback.
|
|
3332
|
+
*/
|
|
3333
|
+
async encodeNativeCropResult(crop, tier, encodeJpeg) {
|
|
3334
|
+
if (encodeJpeg !== true) return {
|
|
3335
|
+
bytes: crop.bytes,
|
|
3336
|
+
width: crop.width,
|
|
3337
|
+
height: crop.height,
|
|
3338
|
+
tier
|
|
3339
|
+
};
|
|
3340
|
+
try {
|
|
3341
|
+
const sharp$3 = (await import("sharp")).default;
|
|
3342
|
+
return {
|
|
3343
|
+
jpeg: (await sharp$3(Buffer.from(crop.bytes), { raw: {
|
|
3344
|
+
width: crop.width,
|
|
3345
|
+
height: crop.height,
|
|
3346
|
+
channels: 3
|
|
3347
|
+
} }).jpeg({ quality: 90 }).toBuffer()).toString("base64"),
|
|
3348
|
+
width: crop.width,
|
|
3349
|
+
height: crop.height,
|
|
3350
|
+
tier
|
|
3351
|
+
};
|
|
3352
|
+
} catch (err) {
|
|
3353
|
+
this.ctx.logger.warn("getNativeCrop: cross-node JPEG encode failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
3354
|
+
return null;
|
|
3355
|
+
}
|
|
3212
3356
|
}
|
|
3213
3357
|
/**
|
|
3214
3358
|
* Two-plane design: run the DETAIL subtree (crop children) for a single
|
|
@@ -3223,10 +3367,72 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3223
3367
|
return runDetailSubtree({
|
|
3224
3368
|
resolveChildSteps: (deviceId, parentClassName, filter) => this.resolveDetailChildSteps(deviceId, parentClassName, filter),
|
|
3225
3369
|
getNativeCrop: (deviceId, handle, paddedBbox) => this.getNativeCropForDetail(deviceId, handle, paddedBbox),
|
|
3226
|
-
runPipeline: (steps, imageJpeg, deviceId) => this.runDetailPipeline(steps, imageJpeg, deviceId)
|
|
3370
|
+
runPipeline: (steps, imageJpeg, deviceId, deviceKey) => this.runDetailPipeline(steps, imageJpeg, deviceId, deviceKey),
|
|
3371
|
+
resolveStepDevice: (deviceId, step) => this.resolveDetailStepDevice(deviceId, step)
|
|
3227
3372
|
}, input);
|
|
3228
3373
|
}
|
|
3229
3374
|
/**
|
|
3375
|
+
* Step-tree device jump (phase 1) — pick which same-node inference device an
|
|
3376
|
+
* enrichment `step` runs on. AUTO-jumps only when the camera's elected device
|
|
3377
|
+
* cannot run the step's model (its format has no build); an operator
|
|
3378
|
+
* `step.jumpDeviceKey` overrides. Returns the resolved `<backend>:<device>`
|
|
3379
|
+
* key, or `undefined` (⇒ the runner's per-camera default pool) when there is
|
|
3380
|
+
* nothing to route: no elected device, or no jump roster on the attach.
|
|
3381
|
+
*
|
|
3382
|
+
* When NO enabled device on the node can run the step, logs a LOUD warn once
|
|
3383
|
+
* per (camera, step) — killing the historical SILENT per-child degrade (an
|
|
3384
|
+
* `edgetpu` root would otherwise drop faces/plates/clip with no signal).
|
|
3385
|
+
*/
|
|
3386
|
+
resolveDetailStepDevice(deviceId, step) {
|
|
3387
|
+
const config = this.attached.get(deviceId)?.config;
|
|
3388
|
+
const effectiveDeviceKey = config?.deviceKey;
|
|
3389
|
+
if (!effectiveDeviceKey) return void 0;
|
|
3390
|
+
const roster = config?.inferenceDevices;
|
|
3391
|
+
if (!roster || roster.length < 2) return effectiveDeviceKey;
|
|
3392
|
+
const attachedByKey = this.perDeviceAttachedCounts();
|
|
3393
|
+
const devices = roster.map((d) => ({
|
|
3394
|
+
deviceKey: d.deviceKey,
|
|
3395
|
+
weight: d.weight,
|
|
3396
|
+
maxSessions: d.maxSessions,
|
|
3397
|
+
attachedCameras: attachedByKey.get(d.deviceKey) ?? 0
|
|
3398
|
+
}));
|
|
3399
|
+
const def = tryGetStepDefinition(step.addonId);
|
|
3400
|
+
const canRun = (format) => def === null || def.models.some((m) => m.formats[format]);
|
|
3401
|
+
const result = resolveStepDevice({
|
|
3402
|
+
effectiveDeviceKey,
|
|
3403
|
+
devices,
|
|
3404
|
+
...step.jumpDeviceKey !== void 0 ? { manualJumpDeviceKey: step.jumpDeviceKey } : {},
|
|
3405
|
+
canRun
|
|
3406
|
+
});
|
|
3407
|
+
if (result.kind === "no-candidate") this.warnStepHasNoRunnableDevice(deviceId, step.addonId, effectiveDeviceKey);
|
|
3408
|
+
return result.deviceKey;
|
|
3409
|
+
}
|
|
3410
|
+
/** Live attached-camera count per inference `deviceKey` on this runner. */
|
|
3411
|
+
perDeviceAttachedCounts() {
|
|
3412
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3413
|
+
for (const cam of this.attached.values()) {
|
|
3414
|
+
const key = cam.config.deviceKey;
|
|
3415
|
+
if (key === void 0) continue;
|
|
3416
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
3417
|
+
}
|
|
3418
|
+
return counts;
|
|
3419
|
+
}
|
|
3420
|
+
/** `${deviceId}:${addonId}` pairs already warned about a no-runnable-device step. */
|
|
3421
|
+
warnedNoJumpDevice = /* @__PURE__ */ new Set();
|
|
3422
|
+
warnStepHasNoRunnableDevice(deviceId, addonId, effectiveDeviceKey) {
|
|
3423
|
+
const key = `${deviceId}:${addonId}`;
|
|
3424
|
+
if (this.warnedNoJumpDevice.has(key)) return;
|
|
3425
|
+
this.warnedNoJumpDevice.add(key);
|
|
3426
|
+
this.ctx?.logger.warn("enrichment step has no runnable inference device on this node — producing NO results for it", {
|
|
3427
|
+
tags: { deviceId },
|
|
3428
|
+
meta: {
|
|
3429
|
+
step: addonId,
|
|
3430
|
+
electedDeviceKey: effectiveDeviceKey,
|
|
3431
|
+
hint: "the elected device format has no build of this step, and no enabled same-node device can run it — enable a compatible accelerator or add a jumpDeviceKey"
|
|
3432
|
+
}
|
|
3433
|
+
});
|
|
3434
|
+
}
|
|
3435
|
+
/**
|
|
3230
3436
|
* Real `resolveChildSteps` dep: `null` when the camera isn't attached
|
|
3231
3437
|
* (matches the cap doc — "camera not attached" is a distinct case from
|
|
3232
3438
|
* "attached but no matching child steps", which returns an empty array
|
|
@@ -3248,16 +3454,12 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3248
3454
|
async getNativeCropForDetail(_deviceId, handle, paddedBbox) {
|
|
3249
3455
|
const native = await this.getNativeCrop({
|
|
3250
3456
|
handle,
|
|
3251
|
-
bbox: paddedBbox
|
|
3457
|
+
bbox: paddedBbox,
|
|
3458
|
+
encodeJpeg: true
|
|
3252
3459
|
});
|
|
3253
|
-
if (!native) return null;
|
|
3254
|
-
const sharp$3 = (await import("sharp")).default;
|
|
3460
|
+
if (!native?.jpeg) return null;
|
|
3255
3461
|
return {
|
|
3256
|
-
jpeg:
|
|
3257
|
-
width: native.width,
|
|
3258
|
-
height: native.height,
|
|
3259
|
-
channels: 3
|
|
3260
|
-
} }).jpeg({ quality: 90 }).toBuffer(),
|
|
3462
|
+
jpeg: Buffer.from(native.jpeg, "base64"),
|
|
3261
3463
|
width: native.width,
|
|
3262
3464
|
height: native.height,
|
|
3263
3465
|
frameSpace: denormalizeRect(paddedBbox, handle.width, handle.height)
|
|
@@ -3317,10 +3519,10 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3317
3519
|
})();
|
|
3318
3520
|
}
|
|
3319
3521
|
/** Real `runPipeline` dep: node-local `pipelineExecutor.runPipeline` on the `'full'` plane. */
|
|
3320
|
-
async runDetailPipeline(steps, imageJpeg, deviceId) {
|
|
3522
|
+
async runDetailPipeline(steps, imageJpeg, deviceId, stepDeviceKey) {
|
|
3321
3523
|
const api = this.ctxIfReady?.api;
|
|
3322
3524
|
if (!api) return null;
|
|
3323
|
-
const detailDeviceKey = this.attached.get(deviceId)?.config?.deviceKey;
|
|
3525
|
+
const detailDeviceKey = stepDeviceKey ?? this.attached.get(deviceId)?.config?.deviceKey;
|
|
3324
3526
|
try {
|
|
3325
3527
|
return await api.pipelineExecutor.runPipeline.mutate({
|
|
3326
3528
|
steps: [...steps],
|
|
@@ -3397,6 +3599,8 @@ var PipelineRunnerAddon = class extends require_dist.BaseAddon {
|
|
|
3397
3599
|
this.lastDecodeUnexpectedEndAt.delete(deviceId);
|
|
3398
3600
|
this.thumbSampler?.forget(deviceId);
|
|
3399
3601
|
this.packageGovernor.forget(deviceId);
|
|
3602
|
+
const warnPrefix = `${deviceId}:`;
|
|
3603
|
+
for (const key of this.warnedNoJumpDevice) if (key.startsWith(warnPrefix)) this.warnedNoJumpDevice.delete(key);
|
|
3400
3604
|
this.attached.delete(deviceId);
|
|
3401
3605
|
this.lastMotionAt.delete(deviceId);
|
|
3402
3606
|
this.lastEmittedCameraMetrics.delete(deviceId);
|