@camstack/addon-post-analysis 1.2.117 → 1.2.118
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{dist-Dk-BdVHw.js → dist-CO6pe7bi.js} +6 -1
- package/dist/{dist-PMOtjzh2.mjs → dist-Gn2fFMov.mjs} +6 -1
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BmR_bLi_.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BYzwylnw.mjs} +2 -2
- package/dist/pipeline-analytics/{hostInit-Ddl2vUu6.mjs → hostInit-TlKH3NQ8.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +271 -87
- package/dist/pipeline-analytics/index.mjs +271 -87
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-CO6pe7bi.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -25924,7 +25924,7 @@ var CLASS_RANK_VEHICLE = .8;
|
|
|
25924
25924
|
var CLASS_RANK_ANIMAL = .5;
|
|
25925
25925
|
var CLASS_RANK_DEFAULT = .25;
|
|
25926
25926
|
var PERSON_CLASSES = new Set(["person", "face"]);
|
|
25927
|
-
var VEHICLE_CLASSES = new Set([
|
|
25927
|
+
var VEHICLE_CLASSES$1 = new Set([
|
|
25928
25928
|
"vehicle",
|
|
25929
25929
|
"car",
|
|
25930
25930
|
"truck",
|
|
@@ -25955,7 +25955,7 @@ function clamp01$1(x) {
|
|
|
25955
25955
|
function classRank(className) {
|
|
25956
25956
|
const c = className.toLowerCase();
|
|
25957
25957
|
if (PERSON_CLASSES.has(c)) return 1;
|
|
25958
|
-
if (VEHICLE_CLASSES.has(c)) return CLASS_RANK_VEHICLE;
|
|
25958
|
+
if (VEHICLE_CLASSES$1.has(c)) return CLASS_RANK_VEHICLE;
|
|
25959
25959
|
if (ANIMAL_CLASSES.has(c)) return CLASS_RANK_ANIMAL;
|
|
25960
25960
|
return CLASS_RANK_DEFAULT;
|
|
25961
25961
|
}
|
|
@@ -26558,7 +26558,7 @@ var FaceGalleryProvider = class {
|
|
|
26558
26558
|
const identities = await this.identityStore.listIdentities();
|
|
26559
26559
|
const nameMap = new Map(identities.map((i) => [i.id, i.name]));
|
|
26560
26560
|
const identityName = face.recognizedIdentityId ? nameMap.get(face.recognizedIdentityId) : void 0;
|
|
26561
|
-
const
|
|
26561
|
+
const cropUrl = this.cropUrlFor(face);
|
|
26562
26562
|
return {
|
|
26563
26563
|
faceId: face.id,
|
|
26564
26564
|
deviceId: face.deviceId,
|
|
@@ -26567,7 +26567,7 @@ var FaceGalleryProvider = class {
|
|
|
26567
26567
|
...face.recognizedIdentityId != null ? { recognizedIdentityId: face.recognizedIdentityId } : {},
|
|
26568
26568
|
...identityName !== void 0 ? { identityName } : {},
|
|
26569
26569
|
assigned: face.assigned,
|
|
26570
|
-
...
|
|
26570
|
+
...cropUrl !== void 0 ? { cropUrl } : {},
|
|
26571
26571
|
...face.faceBbox !== void 0 ? { faceBbox: face.faceBbox } : {},
|
|
26572
26572
|
...face.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: face.keyFrameMediaKey } : {},
|
|
26573
26573
|
...face.bestMatchScore != null ? { bestMatchScore: face.bestMatchScore } : {},
|
|
@@ -29687,9 +29687,15 @@ var TrackCloser = class {
|
|
|
29687
29687
|
await this.suppressZeroMedia(t.deviceId, t.trackId);
|
|
29688
29688
|
return;
|
|
29689
29689
|
}
|
|
29690
|
-
|
|
29690
|
+
let thumbnailLanded = ownedMedia.some((m) => m.kind === "thumbnail");
|
|
29691
|
+
if (outcome === "raster-fallback" && closure?.rasterFallback) {
|
|
29692
|
+
await this.commitRasterFallback(t, closure.rasterFallback.jpeg, closure.rasterFallback.timestamp);
|
|
29693
|
+
thumbnailLanded = true;
|
|
29694
|
+
}
|
|
29691
29695
|
await this.maybePromoteLastFrame(t, ownedMedia);
|
|
29692
|
-
|
|
29696
|
+
const hasRasterFallback = closure?.rasterFallback !== void 0;
|
|
29697
|
+
if (!thumbnailLanded) thumbnailLanded = await this.maybeDeriveThumbnailFromKeyFrame(t, ownedMedia, hasRasterFallback);
|
|
29698
|
+
if (!thumbnailLanded && closure?.rasterFallback) await this.commitRasterFallback(t, closure.rasterFallback.jpeg, closure.rasterFallback.timestamp);
|
|
29693
29699
|
this.deps.logger.info("track ended", {
|
|
29694
29700
|
tags: { deviceId: t.deviceId },
|
|
29695
29701
|
meta: {
|
|
@@ -29807,14 +29813,23 @@ var TrackCloser = class {
|
|
|
29807
29813
|
* and the rate could only ever be counted from the database, after the fact.
|
|
29808
29814
|
* A branch that drops work silently reads as "never happened".
|
|
29809
29815
|
*/
|
|
29810
|
-
async maybeDeriveThumbnailFromKeyFrame(t, ownedMedia) {
|
|
29811
|
-
if (ownedMedia.some((m) => m.kind === "thumbnail")) return;
|
|
29816
|
+
async maybeDeriveThumbnailFromKeyFrame(t, ownedMedia, hasRasterFallback) {
|
|
29817
|
+
if (ownedMedia.some((m) => m.kind === "thumbnail")) return true;
|
|
29812
29818
|
const derive = this.deps.deriveThumbnailFromKeyFrame;
|
|
29813
|
-
if (!derive)
|
|
29819
|
+
if (!derive) {
|
|
29820
|
+
if (!hasRasterFallback) this.logNoThumbnailAtClose(t, "derive-not-wired");
|
|
29821
|
+
return false;
|
|
29822
|
+
}
|
|
29814
29823
|
const keyFrame = ownedMedia.find((m) => m.kind === "keyFrame");
|
|
29815
|
-
if (!keyFrame)
|
|
29824
|
+
if (!keyFrame) {
|
|
29825
|
+
if (!hasRasterFallback) this.logNoThumbnailAtClose(t, "no-key-frame");
|
|
29826
|
+
return false;
|
|
29827
|
+
}
|
|
29816
29828
|
const bestSeen = this.deps.residents.bestSeenBbox(t.trackId);
|
|
29817
|
-
if (!bestSeen)
|
|
29829
|
+
if (!bestSeen) {
|
|
29830
|
+
if (!hasRasterFallback) this.logNoThumbnailAtClose(t, "no-best-seen-bbox");
|
|
29831
|
+
return false;
|
|
29832
|
+
}
|
|
29818
29833
|
try {
|
|
29819
29834
|
const derived = await derive({
|
|
29820
29835
|
mediaKey: keyFrame.key,
|
|
@@ -29826,17 +29841,23 @@ var TrackCloser = class {
|
|
|
29826
29841
|
},
|
|
29827
29842
|
frameWidth: bestSeen.frameWidth,
|
|
29828
29843
|
frameHeight: bestSeen.frameHeight,
|
|
29829
|
-
timestamp:
|
|
29844
|
+
timestamp: keyFrame.timestamp
|
|
29830
29845
|
});
|
|
29831
|
-
if (!derived)
|
|
29846
|
+
if (!derived) {
|
|
29847
|
+
if (!hasRasterFallback) this.logNoThumbnailAtClose(t, "derive-returned-null", { keyFrameKey: keyFrame.key });
|
|
29848
|
+
return false;
|
|
29849
|
+
}
|
|
29832
29850
|
const mediaStore = this.deps.mediaStore();
|
|
29833
|
-
if (!mediaStore)
|
|
29851
|
+
if (!mediaStore) {
|
|
29852
|
+
if (!hasRasterFallback) this.logNoThumbnailAtClose(t, "no-media-store");
|
|
29853
|
+
return false;
|
|
29854
|
+
}
|
|
29834
29855
|
await mediaStore.put({
|
|
29835
29856
|
deviceId: t.deviceId,
|
|
29836
29857
|
ownerKind: "track",
|
|
29837
29858
|
ownerId: t.trackId,
|
|
29838
29859
|
kind: "thumbnail",
|
|
29839
|
-
timestamp:
|
|
29860
|
+
timestamp: keyFrame.timestamp,
|
|
29840
29861
|
data: derived.thumbnail
|
|
29841
29862
|
});
|
|
29842
29863
|
await mediaStore.put({
|
|
@@ -29844,7 +29865,7 @@ var TrackCloser = class {
|
|
|
29844
29865
|
ownerKind: "track",
|
|
29845
29866
|
ownerId: t.trackId,
|
|
29846
29867
|
kind: "thumbnailSmall",
|
|
29847
|
-
timestamp:
|
|
29868
|
+
timestamp: keyFrame.timestamp,
|
|
29848
29869
|
data: derived.thumbnailSmall
|
|
29849
29870
|
});
|
|
29850
29871
|
this.deps.logger.info("thumbnail derived from keyFrame at close (no live best-shot landed)", {
|
|
@@ -29855,6 +29876,7 @@ var TrackCloser = class {
|
|
|
29855
29876
|
skewMs: derived.skewMs
|
|
29856
29877
|
}
|
|
29857
29878
|
});
|
|
29879
|
+
return true;
|
|
29858
29880
|
} catch (err) {
|
|
29859
29881
|
this.deps.logger.debug("close-time keyFrame thumbnail derive failed", {
|
|
29860
29882
|
tags: { deviceId: t.deviceId },
|
|
@@ -29863,10 +29885,11 @@ var TrackCloser = class {
|
|
|
29863
29885
|
error: String(err)
|
|
29864
29886
|
}
|
|
29865
29887
|
});
|
|
29866
|
-
this.logNoThumbnailAtClose(t, "derive-threw", {
|
|
29888
|
+
if (!hasRasterFallback) this.logNoThumbnailAtClose(t, "derive-threw", {
|
|
29867
29889
|
keyFrameKey: keyFrame.key,
|
|
29868
29890
|
error: String(err)
|
|
29869
29891
|
});
|
|
29892
|
+
return false;
|
|
29870
29893
|
}
|
|
29871
29894
|
}
|
|
29872
29895
|
/**
|
|
@@ -30736,6 +30759,7 @@ var PlateGalleryProvider = class {
|
|
|
30736
30759
|
logger;
|
|
30737
30760
|
refreshGallery;
|
|
30738
30761
|
emitPlateGalleryChanged;
|
|
30762
|
+
eventMediaBaseUrl;
|
|
30739
30763
|
constructor(deps) {
|
|
30740
30764
|
this.plateStore = deps.plateStore;
|
|
30741
30765
|
this.vehicleStore = deps.vehicleStore;
|
|
@@ -30745,6 +30769,7 @@ var PlateGalleryProvider = class {
|
|
|
30745
30769
|
this.logger = deps.logger;
|
|
30746
30770
|
this.refreshGallery = deps.refreshGallery;
|
|
30747
30771
|
this.emitPlateGalleryChanged = deps.emitPlateGalleryChanged;
|
|
30772
|
+
this.eventMediaBaseUrl = deps.eventMediaBaseUrl;
|
|
30748
30773
|
}
|
|
30749
30774
|
/** Best-effort emit — never allowed to fail the mutation it follows. */
|
|
30750
30775
|
safeEmitPlateGalleryChanged(payload) {
|
|
@@ -30752,6 +30777,12 @@ var PlateGalleryProvider = class {
|
|
|
30752
30777
|
this.emitPlateGalleryChanged(payload);
|
|
30753
30778
|
} catch {}
|
|
30754
30779
|
}
|
|
30780
|
+
cropUrlFor(plate) {
|
|
30781
|
+
if (plate.mediaKey === void 0 || plate.mediaKey === "") return void 0;
|
|
30782
|
+
const base = this.eventMediaBaseUrl();
|
|
30783
|
+
if (base === null) return void 0;
|
|
30784
|
+
return `${base}/${encodeURIComponent(plate.mediaKey)}`;
|
|
30785
|
+
}
|
|
30755
30786
|
async plateCropBase64(plate) {
|
|
30756
30787
|
if (plate.mediaKey) {
|
|
30757
30788
|
const m = await this.mediaStore.getByKey(plate.mediaKey);
|
|
@@ -30760,8 +30791,9 @@ var PlateGalleryProvider = class {
|
|
|
30760
30791
|
const media = await this.mediaStore.listByOwner("plate", plate.id);
|
|
30761
30792
|
return media.length > 0 ? media[0].base64 : void 0;
|
|
30762
30793
|
}
|
|
30763
|
-
async toPlateInfo(plate, nameMap) {
|
|
30764
|
-
const base64 = await this.plateCropBase64(plate);
|
|
30794
|
+
async toPlateInfo(plate, nameMap, opts) {
|
|
30795
|
+
const base64 = opts?.includeCrops ?? true ? await this.plateCropBase64(plate) : void 0;
|
|
30796
|
+
const cropUrl = this.cropUrlFor(plate);
|
|
30765
30797
|
const vehicleName = plate.recognizedVehicleId != null ? nameMap?.get(plate.recognizedVehicleId) : void 0;
|
|
30766
30798
|
return {
|
|
30767
30799
|
plateId: plate.id,
|
|
@@ -30776,7 +30808,8 @@ var PlateGalleryProvider = class {
|
|
|
30776
30808
|
...vehicleName !== void 0 ? { vehicleName } : {},
|
|
30777
30809
|
...plate.plateBbox !== void 0 ? { plateBbox: plate.plateBbox } : {},
|
|
30778
30810
|
...plate.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: plate.keyFrameMediaKey } : {},
|
|
30779
|
-
...base64 !== void 0 ? { base64 } : {}
|
|
30811
|
+
...base64 !== void 0 ? { base64 } : {},
|
|
30812
|
+
...cropUrl !== void 0 ? { cropUrl } : {}
|
|
30780
30813
|
};
|
|
30781
30814
|
}
|
|
30782
30815
|
async vehicleNameMap() {
|
|
@@ -30795,7 +30828,7 @@ var PlateGalleryProvider = class {
|
|
|
30795
30828
|
const plate = await this.plateStore.get(`plate-${input.trackId}`);
|
|
30796
30829
|
if (!plate || plate.deviceId !== input.deviceId) return null;
|
|
30797
30830
|
const nameMap = await this.vehicleNameMap();
|
|
30798
|
-
return this.toPlateInfo(plate, nameMap);
|
|
30831
|
+
return this.toPlateInfo(plate, nameMap, { includeCrops: false });
|
|
30799
30832
|
}
|
|
30800
30833
|
async getPlateMedia(input) {
|
|
30801
30834
|
return (await this.mediaStore.listByOwner("plate", input.plateId)).map((m) => ({
|
|
@@ -35936,6 +35969,19 @@ function createEventMediaHandler(deps) {
|
|
|
35936
35969
|
};
|
|
35937
35970
|
}
|
|
35938
35971
|
//#endregion
|
|
35972
|
+
//#region src/pipeline-analytics/copy-face-crop-preview.ts
|
|
35973
|
+
var TRACK_PREVIEW_KINDS = new Set(["thumbnail", "thumbnailSmall"]);
|
|
35974
|
+
async function copyFaceCropIfTrackHasNoThumbnail(store, input) {
|
|
35975
|
+
if ((await store.listTrackMedia(input.trackId)).some((m) => TRACK_PREVIEW_KINDS.has(m.kind))) return false;
|
|
35976
|
+
await store.putTrackThumbnail({
|
|
35977
|
+
deviceId: input.deviceId,
|
|
35978
|
+
trackId: input.trackId,
|
|
35979
|
+
timestamp: input.timestamp,
|
|
35980
|
+
data: input.crop
|
|
35981
|
+
});
|
|
35982
|
+
return true;
|
|
35983
|
+
}
|
|
35984
|
+
//#endregion
|
|
35939
35985
|
//#region src/pipeline-analytics/event-thumbnail-resolver.ts
|
|
35940
35986
|
/** Pick an event-OWNED media blob: the tight `crop`, else the native-res boxed
|
|
35941
35987
|
* `fullFrameBoxed`, else any available file. Both preferred kinds are RETIRED
|
|
@@ -35944,29 +35990,47 @@ function createEventMediaHandler(deps) {
|
|
|
35944
35990
|
function pickEventOwnedMedia(files) {
|
|
35945
35991
|
return files.find((f) => f.kind === "crop") ?? files.find((f) => f.kind === "fullFrameBoxed") ?? files[0];
|
|
35946
35992
|
}
|
|
35947
|
-
/** Pick a track's fallback media
|
|
35948
|
-
*
|
|
35949
|
-
*
|
|
35950
|
-
* Undefined when the
|
|
35993
|
+
/** Pick a track's fallback media when no subject crop exists: `thumbnailSmall`
|
|
35994
|
+
* → `thumbnail` → native `keyFrameSmall`/`keyFrame`/`fullFrame` → rolling
|
|
35995
|
+
* `lastFrame` → `firstFrame` → newest `snapshot` → any blob. A full frame
|
|
35996
|
+
* beats a class icon. Undefined when the owner has no media. */
|
|
35951
35997
|
function pickTrackFallbackMedia(files) {
|
|
35952
|
-
return files.find((f) => f.kind === "thumbnailSmall") ?? files.find((f) => f.kind === "thumbnail") ?? files.find((f) => f.kind === "lastFrame") ?? files.find((f) => f.kind === "firstFrame") ?? [...files].reverse().find((f) => f.kind === "snapshot") ?? files[files.length - 1];
|
|
35998
|
+
return files.find((f) => f.kind === "thumbnailSmall") ?? files.find((f) => f.kind === "thumbnail") ?? files.find((f) => f.kind === "keyFrameSmall") ?? files.find((f) => f.kind === "keyFrame") ?? files.find((f) => f.kind === "fullFrame") ?? files.find((f) => f.kind === "lastFrame") ?? files.find((f) => f.kind === "firstFrame") ?? [...files].reverse().find((f) => f.kind === "snapshot") ?? files[files.length - 1];
|
|
35953
35999
|
}
|
|
35954
36000
|
/**
|
|
35955
|
-
* Crop-forced resolution (the `?kind=crop`
|
|
35956
|
-
*
|
|
35957
|
-
*
|
|
35958
|
-
*
|
|
35959
|
-
*
|
|
35960
|
-
* returning undefined. A boxed frame (with the target's box drawn) is a real
|
|
35961
|
-
* preview; an empty tile is not. Clean is always tried FIRST so the common case
|
|
35962
|
-
* still serves an unboxed tile. Undefined only when the owner has NO media.
|
|
36001
|
+
* Crop-forced resolution (the `?kind=crop` / `?kind=thumbnailSmall` reel path).
|
|
36002
|
+
*
|
|
36003
|
+
* Ladder: body crop (track/event thumbnail) → tight face/plate crop → any
|
|
36004
|
+
* remaining image (keyFrame / lastFrame / …). The class icon is only honest
|
|
36005
|
+
* when the owner has no stored bytes at all.
|
|
35963
36006
|
*
|
|
35964
36007
|
* `pickClean` is injected because the clean-kind picker (`pickCleanMedia`) lives
|
|
35965
36008
|
* in the addon `index.ts` over the concrete `MediaFileKind` set; this keeps the
|
|
35966
36009
|
* composition pure + unit-testable here.
|
|
35967
36010
|
*/
|
|
35968
|
-
function resolveCropForcedMedia(files, pickClean) {
|
|
35969
|
-
return pickClean(files) ?? pickTrackFallbackMedia(files);
|
|
36011
|
+
function resolveCropForcedMedia(files, pickClean, detailFiles = []) {
|
|
36012
|
+
return pickClean(files) ?? pickClean(detailFiles) ?? pickTrackFallbackMedia([...files, ...detailFiles]);
|
|
36013
|
+
}
|
|
36014
|
+
/**
|
|
36015
|
+
* Event + track media (the body/scene) and face/plate media (the detail
|
|
36016
|
+
* crops). Face rows live under `face-${trackId}`; a track can close with
|
|
36017
|
+
* `hasEmbeddedFace` and ZERO track files (0e343f86).
|
|
36018
|
+
*/
|
|
36019
|
+
async function collectCropForcedMedia(source, id) {
|
|
36020
|
+
const eventFiles = await source.listByOwner("event", id);
|
|
36021
|
+
const trackFiles = await source.listByOwner("track", id);
|
|
36022
|
+
const ownerTrackId = trackFiles.length === 0 ? await source.getTrackIdForEvent(id) : null;
|
|
36023
|
+
const owningTrackFiles = ownerTrackId === null ? [] : await source.listByOwner("track", ownerTrackId);
|
|
36024
|
+
const trackId = ownerTrackId ?? id;
|
|
36025
|
+
const [faceFiles, plateFiles] = await Promise.all([source.listByOwner("face", `face-${trackId}`), source.listByOwner("plate", `plate-${trackId}`)]);
|
|
36026
|
+
return {
|
|
36027
|
+
bodyFiles: [
|
|
36028
|
+
...eventFiles,
|
|
36029
|
+
...trackFiles,
|
|
36030
|
+
...owningTrackFiles
|
|
36031
|
+
],
|
|
36032
|
+
detailFiles: [...faceFiles, ...plateFiles]
|
|
36033
|
+
};
|
|
35970
36034
|
}
|
|
35971
36035
|
/**
|
|
35972
36036
|
* Resolve the default-path media for an event id: the event's own media when it
|
|
@@ -35980,6 +36044,14 @@ async function resolveDefaultEventMedia(deps) {
|
|
|
35980
36044
|
return pickTrackFallbackMedia(await deps.listTrackMedia(trackId)) ?? null;
|
|
35981
36045
|
}
|
|
35982
36046
|
//#endregion
|
|
36047
|
+
//#region src/pipeline-analytics/ensure-track-hierarchy.ts
|
|
36048
|
+
async function ensureTrackHierarchyMedia(deps, input) {
|
|
36049
|
+
const tasks = [];
|
|
36050
|
+
if (!deps.isThumbnailLanded(input.trackId)) tasks.push(deps.captureBodyThumbnail(input));
|
|
36051
|
+
if (!deps.isKeyFrameLanded(input.trackId)) tasks.push(deps.captureKeyFrame(input));
|
|
36052
|
+
await Promise.all(tasks);
|
|
36053
|
+
}
|
|
36054
|
+
//#endregion
|
|
35983
36055
|
//#region src/pipeline-analytics/face-settings.ts
|
|
35984
36056
|
/**
|
|
35985
36057
|
* Per-device face-recognition settings. Cascade: a per-device override on top
|
|
@@ -36498,6 +36570,21 @@ var FaceRecognizer = class {
|
|
|
36498
36570
|
...held,
|
|
36499
36571
|
crop
|
|
36500
36572
|
});
|
|
36573
|
+
if (crop !== void 0 && input.frameHandle !== void 0) this.deps.onLiveHierarchy?.({
|
|
36574
|
+
deviceId: input.deviceId,
|
|
36575
|
+
trackId: c.trackId,
|
|
36576
|
+
timestamp: input.timestamp,
|
|
36577
|
+
bodyBbox: {
|
|
36578
|
+
x: c.bbox.x,
|
|
36579
|
+
y: c.bbox.y,
|
|
36580
|
+
w: c.bbox.w,
|
|
36581
|
+
h: c.bbox.h
|
|
36582
|
+
},
|
|
36583
|
+
frameWidth: input.frameWidth,
|
|
36584
|
+
frameHeight: input.frameHeight,
|
|
36585
|
+
cropPadding: input.cropPadding,
|
|
36586
|
+
frameHandle: input.frameHandle
|
|
36587
|
+
});
|
|
36501
36588
|
this.deps.logger.debug("face: best-face held", {
|
|
36502
36589
|
tags: {
|
|
36503
36590
|
deviceId: input.deviceId,
|
|
@@ -36647,6 +36734,22 @@ var FaceRecognizer = class {
|
|
|
36647
36734
|
});
|
|
36648
36735
|
return;
|
|
36649
36736
|
}
|
|
36737
|
+
try {
|
|
36738
|
+
await this.deps.copyCropToTrackPreview?.({
|
|
36739
|
+
deviceId,
|
|
36740
|
+
trackId,
|
|
36741
|
+
crop: held.crop,
|
|
36742
|
+
timestamp: held.timestamp
|
|
36743
|
+
});
|
|
36744
|
+
} catch (err) {
|
|
36745
|
+
this.deps.logger.debug("face: track preview copy failed", {
|
|
36746
|
+
tags: { deviceId },
|
|
36747
|
+
meta: {
|
|
36748
|
+
trackId,
|
|
36749
|
+
error: String(err)
|
|
36750
|
+
}
|
|
36751
|
+
});
|
|
36752
|
+
}
|
|
36650
36753
|
const keyFrameMediaKey = this.deps.getKeyFrameMediaKey?.(trackId);
|
|
36651
36754
|
try {
|
|
36652
36755
|
await this.deps.faceStore.insert({
|
|
@@ -44227,6 +44330,12 @@ var PlateRecognizer = class {
|
|
|
44227
44330
|
text: c.plateText,
|
|
44228
44331
|
score: c.plateScore,
|
|
44229
44332
|
bbox: c.plateBbox,
|
|
44333
|
+
bodyBbox: {
|
|
44334
|
+
x: c.bbox.x,
|
|
44335
|
+
y: c.bbox.y,
|
|
44336
|
+
w: c.bbox.w,
|
|
44337
|
+
h: c.bbox.h
|
|
44338
|
+
},
|
|
44230
44339
|
timestamp: input.timestamp,
|
|
44231
44340
|
frameWidth: input.frameWidth,
|
|
44232
44341
|
frameHeight: input.frameHeight,
|
|
@@ -44300,6 +44409,16 @@ var PlateRecognizer = class {
|
|
|
44300
44409
|
}
|
|
44301
44410
|
});
|
|
44302
44411
|
}
|
|
44412
|
+
if (crop !== void 0 && input.frameHandle !== void 0 && input.bodyBbox !== void 0) this.deps.onLiveHierarchy?.({
|
|
44413
|
+
deviceId: input.deviceId,
|
|
44414
|
+
trackId: input.trackId,
|
|
44415
|
+
timestamp: input.timestamp,
|
|
44416
|
+
bodyBbox: input.bodyBbox,
|
|
44417
|
+
frameWidth: input.frameWidth,
|
|
44418
|
+
frameHeight: input.frameHeight,
|
|
44419
|
+
cropPadding: input.cropPadding,
|
|
44420
|
+
frameHandle: input.frameHandle
|
|
44421
|
+
});
|
|
44303
44422
|
this.bestPlate.set(input.trackId, {
|
|
44304
44423
|
text: input.text,
|
|
44305
44424
|
score: input.score,
|
|
@@ -48334,14 +48453,23 @@ function isAfterCursor(row, cursor) {
|
|
|
48334
48453
|
if (row.lastSeen < cursor.lastSeen) return true;
|
|
48335
48454
|
return row.lastSeen === cursor.lastSeen && row.id < cursor.trackId;
|
|
48336
48455
|
}
|
|
48456
|
+
var VEHICLE_CLASSES = new Set([
|
|
48457
|
+
"vehicle",
|
|
48458
|
+
"car",
|
|
48459
|
+
"truck",
|
|
48460
|
+
"bus"
|
|
48461
|
+
]);
|
|
48462
|
+
function motionlessMaxPxForClass(className) {
|
|
48463
|
+
return VEHICLE_CLASSES.has(className.toLowerCase()) ? 8 : 40;
|
|
48464
|
+
}
|
|
48337
48465
|
function netDisplacementPx(positions) {
|
|
48338
48466
|
const first = positions[0];
|
|
48339
48467
|
const last = positions[positions.length - 1];
|
|
48340
48468
|
if (!first || !last) return 0;
|
|
48341
48469
|
return Math.hypot(last.x - first.x, last.y - first.y);
|
|
48342
48470
|
}
|
|
48343
|
-
function isShortMotionlessTrack(lifeMs, movedPx) {
|
|
48344
|
-
return lifeMs <= 25e3 && movedPx <=
|
|
48471
|
+
function isShortMotionlessTrack(lifeMs, movedPx, className) {
|
|
48472
|
+
return lifeMs <= 25e3 && movedPx <= motionlessMaxPxForClass(className);
|
|
48345
48473
|
}
|
|
48346
48474
|
//#endregion
|
|
48347
48475
|
//#region src/pipeline-analytics/store/track-store.ts
|
|
@@ -48925,7 +49053,7 @@ var TrackStore = class {
|
|
|
48925
49053
|
for (const [trackId, t] of this.active) {
|
|
48926
49054
|
if (nowMs - t.lastSeen < this.config.ttlMs) continue;
|
|
48927
49055
|
t.active = false;
|
|
48928
|
-
if (t.positions.length > 0 && isShortMotionlessTrack(t.lastSeen - t.firstSeen, netDisplacementPx(t.positions))) t.closedReason = "stationary-promotion";
|
|
49056
|
+
if (t.positions.length > 0 && isShortMotionlessTrack(t.lastSeen - t.firstSeen, netDisplacementPx(t.positions), t.className)) t.closedReason = "stationary-promotion";
|
|
48929
49057
|
const record = cloneTrack(t);
|
|
48930
49058
|
try {
|
|
48931
49059
|
await this.persistCompleted(record);
|
|
@@ -56931,10 +57059,12 @@ var CONFIRMATION_CROP_MAX_WIDTH = 320;
|
|
|
56931
57059
|
* a late confirmation is still worth running; see `DEFAULT_CAPTURE_PRIORITY`). */
|
|
56932
57060
|
var ENRICHMENT_CAPTURE_STALE_MS = 3e3;
|
|
56933
57061
|
/** Reconcile cadence for the per-device `zones` / `zoneRules` slice handles.
|
|
56934
|
-
* The `device.state-changed` push
|
|
56935
|
-
*
|
|
56936
|
-
*
|
|
56937
|
-
|
|
57062
|
+
* The `device.state-changed` push reaches a forked child through
|
|
57063
|
+
* `createEventBusSliceSource` (the DeviceProxy no longer waits on
|
|
57064
|
+
* `live.onEvent`). Events are still lossy telemetry (D8); this timer
|
|
57065
|
+
* is the RPC reconcile, not the mechanism. Five minutes matches the
|
|
57066
|
+
* viewer's `RECONCILE_MS`. */
|
|
57067
|
+
var ZONE_SLICE_RECONCILE_MS = 3e5;
|
|
56938
57068
|
/** How often the evaluating node re-reads the central scene set. */
|
|
56939
57069
|
var SCENE_RELOAD_INTERVAL_MS = 3e4;
|
|
56940
57070
|
/** How long the install coordinates are trusted between settings reads. */
|
|
@@ -56998,38 +57128,31 @@ var MOTION_EVENT_HEARTBEAT_MS = 5e3;
|
|
|
56998
57128
|
*
|
|
56999
57129
|
* `thumbnail` joined the clean set on 2026-07-17: the track best-thumbnail is
|
|
57000
57130
|
* now a subject-centered crop (event-media-dispatcher), and for DEGENERATE
|
|
57001
|
-
* tracks (no object event → no `crop`) it is the ONLY subject-centered media
|
|
57002
|
-
*
|
|
57003
|
-
*
|
|
57004
|
-
*
|
|
57005
|
-
*
|
|
57006
|
-
*
|
|
57007
|
-
*
|
|
57008
|
-
*
|
|
57009
|
-
*
|
|
57010
|
-
* over 404). The retired-but-still-stored kinds (`crop`) stay listed so old rows
|
|
57011
|
-
* keep resolving.
|
|
57131
|
+
* tracks (no object event → no `crop`) it is the ONLY subject-centered media.
|
|
57132
|
+
*
|
|
57133
|
+
* Full-scene kinds (`fullFrame`, `keyFrame`, `keyFrameSmall`) stay out of
|
|
57134
|
+
* this list so a crop is preferred when one exists. `resolveCropForcedMedia`
|
|
57135
|
+
* then falls through to those full-scene kinds — a keyFrame beats an icon.
|
|
57136
|
+
* An *explicit* `?kind=keyFrame` / `?kind=snapshot` still matches exactly, so
|
|
57137
|
+
* videoclips and other kind-specific fetches keep working.
|
|
57138
|
+
*
|
|
57139
|
+
* The retired-but-still-stored kind (`crop`) stays listed so old rows resolve.
|
|
57012
57140
|
*/
|
|
57013
57141
|
var CLEAN_MEDIA_KINDS = [
|
|
57014
57142
|
"crop",
|
|
57015
57143
|
"thumbnail",
|
|
57016
|
-
"thumbnailSmall"
|
|
57017
|
-
"fullFrame",
|
|
57018
|
-
"keyFrame",
|
|
57019
|
-
"keyFrameSmall"
|
|
57144
|
+
"thumbnailSmall"
|
|
57020
57145
|
];
|
|
57021
57146
|
/**
|
|
57022
|
-
* Pick
|
|
57023
|
-
* `
|
|
57024
|
-
* {@link CLEAN_MEDIA_KINDS}
|
|
57025
|
-
*
|
|
57147
|
+
* Pick media for a crop-forced request: the exact `preferKind` when present
|
|
57148
|
+
* (so `?kind=snapshot` still serves a snapshot), else the first available
|
|
57149
|
+
* subject crop in {@link CLEAN_MEDIA_KINDS}. Returns undefined when no crop
|
|
57150
|
+
* is in the list — the caller then falls through to any remaining image
|
|
57151
|
+
* (`resolveCropForcedMedia`); the class icon is only for a total miss.
|
|
57026
57152
|
*/
|
|
57027
57153
|
function pickCleanMedia(files, preferKind) {
|
|
57028
|
-
const
|
|
57029
|
-
if (
|
|
57030
|
-
const exact = files.find((f) => f.kind === preferKind);
|
|
57031
|
-
if (exact) return exact;
|
|
57032
|
-
}
|
|
57154
|
+
const exact = files.find((f) => f.kind === preferKind);
|
|
57155
|
+
if (exact) return exact;
|
|
57033
57156
|
for (const kind of CLEAN_MEDIA_KINDS) {
|
|
57034
57157
|
const found = files.find((f) => f.kind === kind);
|
|
57035
57158
|
if (found) return found;
|
|
@@ -58493,6 +58616,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
58493
58616
|
}, trackId);
|
|
58494
58617
|
},
|
|
58495
58618
|
markEmbeddedFace: (trackId) => stores.trackStore.markEmbeddedFace(trackId),
|
|
58619
|
+
copyCropToTrackPreview: async (input) => {
|
|
58620
|
+
await copyFaceCropIfTrackHasNoThumbnail({
|
|
58621
|
+
listTrackMedia: (trackId) => stores.mediaStore.listByOwner("track", trackId),
|
|
58622
|
+
putTrackThumbnail: (put) => stores.mediaStore.put({
|
|
58623
|
+
deviceId: put.deviceId,
|
|
58624
|
+
ownerKind: "track",
|
|
58625
|
+
ownerId: put.trackId,
|
|
58626
|
+
kind: "thumbnail",
|
|
58627
|
+
timestamp: put.timestamp,
|
|
58628
|
+
data: put.data
|
|
58629
|
+
})
|
|
58630
|
+
}, input);
|
|
58631
|
+
},
|
|
58496
58632
|
getKeyFrameMediaKey: (trackId) => this.residents.keyFrameKey(trackId),
|
|
58497
58633
|
emitFaceGalleryChanged: (payload) => this.emitFaceGalleryChanged(payload),
|
|
58498
58634
|
onSemanticMatch: (match) => this.promoteSemanticRecognition({
|
|
@@ -58507,6 +58643,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
58507
58643
|
confidence: match.confidence,
|
|
58508
58644
|
...match.frameHandle !== void 0 ? { frameHandle: match.frameHandle } : {}
|
|
58509
58645
|
}),
|
|
58646
|
+
onLiveHierarchy: (input) => this.requestTrackHierarchy(input),
|
|
58510
58647
|
logger: logger.child("FaceRecognizer")
|
|
58511
58648
|
});
|
|
58512
58649
|
this.faceRecognizer.refreshGallery();
|
|
@@ -58539,6 +58676,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
58539
58676
|
deriveCropFromKeyFrame,
|
|
58540
58677
|
getKeyFrameMediaKey: (trackId) => this.residents.keyFrameKey(trackId),
|
|
58541
58678
|
emitPlateGalleryChanged: (payload) => this.emitPlateGalleryChanged(payload),
|
|
58679
|
+
onLiveHierarchy: (input) => this.requestTrackHierarchy(input),
|
|
58542
58680
|
logger: logger.child("PlateRecognizer")
|
|
58543
58681
|
});
|
|
58544
58682
|
this.plateRecognizer.refreshGallery();
|
|
@@ -59908,6 +60046,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
59908
60046
|
refreshGallery: () => {
|
|
59909
60047
|
this.plateRecognizer?.refreshGallery();
|
|
59910
60048
|
},
|
|
60049
|
+
eventMediaBaseUrl: () => this.eventMediaBaseUrl,
|
|
59911
60050
|
emitPlateGalleryChanged: (payload) => this.emitPlateGalleryChanged(payload)
|
|
59912
60051
|
});
|
|
59913
60052
|
return [
|
|
@@ -60964,7 +61103,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
60964
61103
|
if (!first) return;
|
|
60965
61104
|
const lifeMs = track.lastSeen - track.firstSeen;
|
|
60966
61105
|
const moved = netDisplacementPx(track.positions ?? []);
|
|
60967
|
-
if (!isShortMotionlessTrack(lifeMs, moved)) {
|
|
61106
|
+
if (!isShortMotionlessTrack(lifeMs, moved, track.className)) {
|
|
60968
61107
|
this.phantomCells?.noteMovingClose({
|
|
60969
61108
|
deviceId: track.deviceId,
|
|
60970
61109
|
className: track.className,
|
|
@@ -61082,6 +61221,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
61082
61221
|
}, d.score, d.label, frame.timestamp);
|
|
61083
61222
|
if (this.plateRecognizer) {
|
|
61084
61223
|
const mediaSettings = await this.resolveDeviceMediaSettings(deviceId);
|
|
61224
|
+
const body = this.residents.bestSeenBbox(trackId);
|
|
61085
61225
|
await this.plateRecognizer.observePlateRead({
|
|
61086
61226
|
deviceId,
|
|
61087
61227
|
trackId,
|
|
@@ -61097,7 +61237,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
61097
61237
|
frameWidth: frame.frameWidth,
|
|
61098
61238
|
frameHeight: frame.frameHeight,
|
|
61099
61239
|
cropPadding: mediaSettings.cropPadding,
|
|
61100
|
-
...frame.frameHandle !== void 0 ? { frameHandle: frame.frameHandle } : {}
|
|
61240
|
+
...frame.frameHandle !== void 0 ? { frameHandle: frame.frameHandle } : {},
|
|
61241
|
+
...body !== void 0 ? { bodyBbox: {
|
|
61242
|
+
x: body.x,
|
|
61243
|
+
y: body.y,
|
|
61244
|
+
w: body.w,
|
|
61245
|
+
h: body.h
|
|
61246
|
+
} } : {}
|
|
61101
61247
|
});
|
|
61102
61248
|
}
|
|
61103
61249
|
}
|
|
@@ -61398,6 +61544,51 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
61398
61544
|
}
|
|
61399
61545
|
}).catch(() => {})));
|
|
61400
61546
|
}
|
|
61547
|
+
/**
|
|
61548
|
+
* A face or plate crop just landed from a still-live handle. Fill the
|
|
61549
|
+
* missing parent rungs — person/vehicle thumbnail (display crop, same
|
|
61550
|
+
* surface that produced the detail) and native keyFrame — so Events never
|
|
61551
|
+
* has a child crop without a body and a scene.
|
|
61552
|
+
*/
|
|
61553
|
+
requestTrackHierarchy(input) {
|
|
61554
|
+
ensureTrackHierarchyMedia({
|
|
61555
|
+
isThumbnailLanded: (trackId) => this.residents.isThumbnailLanded(trackId),
|
|
61556
|
+
isKeyFrameLanded: (trackId) => this.residents.keyFrameCapture(trackId).landed,
|
|
61557
|
+
captureBodyThumbnail: (req) => this.captureHierarchyBodyThumbnail(req),
|
|
61558
|
+
captureKeyFrame: async (req) => {
|
|
61559
|
+
await this.persistKeyFrames(req.deviceId, req.timestamp, [{
|
|
61560
|
+
trackId: req.trackId,
|
|
61561
|
+
retry: false
|
|
61562
|
+
}], req.frameHandle);
|
|
61563
|
+
}
|
|
61564
|
+
}, input);
|
|
61565
|
+
}
|
|
61566
|
+
async captureHierarchyBodyThumbnail(input) {
|
|
61567
|
+
const displayCrop = this.captureDisplayCropFn;
|
|
61568
|
+
const mediaStore = this.mediaStore;
|
|
61569
|
+
if (!displayCrop || !mediaStore) return;
|
|
61570
|
+
await this.captureScheduler.request({
|
|
61571
|
+
deviceId: input.deviceId,
|
|
61572
|
+
trackId: input.trackId,
|
|
61573
|
+
kind: "thumbnail",
|
|
61574
|
+
coalesceKey: captureCoalesceKey(input.deviceId, "thumbnail", `${input.trackId}:hierarchy`),
|
|
61575
|
+
staleAfterMs: ENRICHMENT_CAPTURE_STALE_MS,
|
|
61576
|
+
exec: async () => {
|
|
61577
|
+
if (this.residents.isThumbnailLanded(input.trackId)) return;
|
|
61578
|
+
const jpeg = await displayCrop(input.frameHandle, input.bodyBbox, input.frameWidth, input.frameHeight, input.cropPadding, input.trackId);
|
|
61579
|
+
if (!jpeg) return;
|
|
61580
|
+
await mediaStore.put({
|
|
61581
|
+
deviceId: input.deviceId,
|
|
61582
|
+
ownerKind: "track",
|
|
61583
|
+
ownerId: input.trackId,
|
|
61584
|
+
kind: "thumbnail",
|
|
61585
|
+
timestamp: input.timestamp,
|
|
61586
|
+
data: jpeg
|
|
61587
|
+
});
|
|
61588
|
+
this.residents.markThumbnailLanded(input.deviceId, input.trackId);
|
|
61589
|
+
}
|
|
61590
|
+
}).catch(() => {});
|
|
61591
|
+
}
|
|
61401
61592
|
/** Attribution-time SEMANTIC best-shot promotion (D-semantic-bestshot,
|
|
61402
61593
|
* 2026-08-21; see `pipeline/semantic-promotion.ts`): offer a frame from
|
|
61403
61594
|
* which a recognition SUCCEEDED to the best-frame ranking with the
|
|
@@ -62903,15 +63094,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
62903
63094
|
* applies to the very next frame even when frames stop briefly
|
|
62904
63095
|
* (e.g. during binding flips).
|
|
62905
63096
|
*
|
|
62906
|
-
* RECONCILE: the push channel behind `subscribe`
|
|
62907
|
-
* via `
|
|
62908
|
-
*
|
|
62909
|
-
*
|
|
62910
|
-
*
|
|
62911
|
-
* telemetry (D8); the durable channel is RPC + reconcile — so each
|
|
62912
|
-
* proxy also refreshes its two slices on a slow timer. `refresh()`
|
|
62913
|
-
* round-trips `deviceState.getCapSlice` and fans out through the SAME
|
|
62914
|
-
* subscribe callbacks above, so a zone edit lands within one interval.
|
|
63097
|
+
* RECONCILE: the push channel behind `subscribe` is `device.state-changed`
|
|
63098
|
+
* via the addon's `ctx.eventBus` (`createEventBusSliceSource`). That
|
|
63099
|
+
* declares UDS interest so the parent actually fans the slice here. Events
|
|
63100
|
+
* remain lossy (D8); `refresh()` round-trips `deviceState.getCapSlice` on
|
|
63101
|
+
* this timer as the backstop.
|
|
62915
63102
|
*/
|
|
62916
63103
|
async ensureProxy(deviceId) {
|
|
62917
63104
|
const cached = this.proxies.get(deviceId);
|
|
@@ -64842,14 +65029,11 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
64842
65029
|
async readEventThumbnail(id, preferKind) {
|
|
64843
65030
|
const eventFiles = await (this.mediaStore?.listByOwner("event", id) ?? Promise.resolve([]));
|
|
64844
65031
|
if (preferKind !== void 0 && preferKind.length > 0) {
|
|
64845
|
-
const
|
|
64846
|
-
|
|
64847
|
-
|
|
64848
|
-
|
|
64849
|
-
|
|
64850
|
-
...trackFiles,
|
|
64851
|
-
...owningTrackFiles
|
|
64852
|
-
], (files) => pickCleanMedia(files, preferKind));
|
|
65032
|
+
const layers = await collectCropForcedMedia({
|
|
65033
|
+
listByOwner: (ownerKind, ownerId) => this.mediaStore?.listByOwner(ownerKind, ownerId) ?? Promise.resolve([]),
|
|
65034
|
+
getTrackIdForEvent: (eventId) => this.eventStore?.getTrackIdForEvent(eventId) ?? Promise.resolve(null)
|
|
65035
|
+
}, id);
|
|
65036
|
+
const chosen = resolveCropForcedMedia(layers.bodyFiles, (files) => pickCleanMedia(files, preferKind), layers.detailFiles);
|
|
64853
65037
|
if (!chosen) return null;
|
|
64854
65038
|
return {
|
|
64855
65039
|
bytes: Buffer.from(chosen.base64, "base64"),
|