@camstack/addon-post-analysis 1.2.2 → 1.2.4
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-DSP-cVM0.js → dist-Ck79MtSp.js} +2 -1
- package/dist/{dist-Bu9eXObI.mjs → dist-gQ5DHTYd.mjs} +2 -1
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/{node-DtSEvIXU.js → node-C4bKtLou.js} +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-2w2j7Rm4.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-ChHurWQz.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-CTQ8-sg-.mjs → hostInit-DQbbiJYs.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +390 -46
- package/dist/pipeline-analytics/index.mjs +389 -45
- 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-Ck79MtSp.js");
|
|
6
6
|
let node_crypto = require("node:crypto");
|
|
7
7
|
let sharp = require("sharp");
|
|
8
8
|
sharp = require_dist.__toESM(sharp);
|
|
@@ -3360,6 +3360,24 @@ async function cascadeDeleteTracks(registry, trackStore, trackIds) {
|
|
|
3360
3360
|
for (const trackId of trackIds) await trackStore.deletePersisted(trackId);
|
|
3361
3361
|
}
|
|
3362
3362
|
/**
|
|
3363
|
+
* Full whole-track cascade for ONE track: events → their media + track media →
|
|
3364
|
+
* track row. Delegates to the generalized `cascadeDeleteTracks` so the shipped
|
|
3365
|
+
* `deleteTracks` provider path flows through the same engine while preserving
|
|
3366
|
+
* the exact legacy call order (event ids stay local to the delete). Adapts the
|
|
3367
|
+
* narrow legacy store shapes onto `TrackScopedStore` — a superset store (faces /
|
|
3368
|
+
* embeddings) is opted in by the CALLER's registry, not by this legacy shim.
|
|
3369
|
+
*/
|
|
3370
|
+
async function cascadeDeleteTrack(stores, trackId) {
|
|
3371
|
+
await cascadeDeleteTracks([{ deleteByTracks: async (ids) => {
|
|
3372
|
+
for (const id of ids) {
|
|
3373
|
+
const eventIds = await stores.eventStore.deleteByTrack(id);
|
|
3374
|
+
if (eventIds.length > 0) await stores.mediaStore.deleteForEvents([...eventIds]);
|
|
3375
|
+
}
|
|
3376
|
+
} }, { deleteByTracks: async (ids) => {
|
|
3377
|
+
await stores.mediaStore.deleteForTracks([...ids]);
|
|
3378
|
+
} }], stores.trackStore, [trackId]);
|
|
3379
|
+
}
|
|
3380
|
+
/**
|
|
3363
3381
|
* Widened batch whole-track deletion — the ONE engine behind the three §5 entry
|
|
3364
3382
|
* points. Runs the FULL registry cascade per track (via `cascadeDeleteTracks`
|
|
3365
3383
|
* with a single-id list so the track root is deleted last), fires cleanup on
|
|
@@ -4497,6 +4515,7 @@ var SINGLE_INSTANCE_KINDS = new Set([
|
|
|
4497
4515
|
"keyFrame",
|
|
4498
4516
|
"keyFrameSmall",
|
|
4499
4517
|
"thumbnail",
|
|
4518
|
+
"thumbnailSmall",
|
|
4500
4519
|
"firstFrame",
|
|
4501
4520
|
"lastFrame"
|
|
4502
4521
|
]);
|
|
@@ -6477,6 +6496,106 @@ function squareSafeCropRegionNormalized(bbox, frame, padding) {
|
|
|
6477
6496
|
};
|
|
6478
6497
|
}
|
|
6479
6498
|
//#endregion
|
|
6499
|
+
//#region src/shared/frame/crop-extractor.ts
|
|
6500
|
+
/**
|
|
6501
|
+
* Extracts a JPEG-encoded crop from a raw frame buffer using a normalized bounding box.
|
|
6502
|
+
* Coordinates are clamped to frame bounds to avoid out-of-range errors.
|
|
6503
|
+
*/
|
|
6504
|
+
async function extractCrop(frameData, frameWidth, frameHeight, bbox) {
|
|
6505
|
+
const rawLeft = Math.round(bbox.x * frameWidth);
|
|
6506
|
+
const rawTop = Math.round(bbox.y * frameHeight);
|
|
6507
|
+
const rawWidth = Math.round(bbox.w * frameWidth);
|
|
6508
|
+
const rawHeight = Math.round(bbox.h * frameHeight);
|
|
6509
|
+
const left = Math.max(0, Math.min(rawLeft, frameWidth - 1));
|
|
6510
|
+
const top = Math.max(0, Math.min(rawTop, frameHeight - 1));
|
|
6511
|
+
const width = Math.max(1, Math.min(rawWidth, frameWidth - left));
|
|
6512
|
+
const height = Math.max(1, Math.min(rawHeight, frameHeight - top));
|
|
6513
|
+
return {
|
|
6514
|
+
crop: await (0, sharp.default)(frameData, { raw: {
|
|
6515
|
+
width: frameWidth,
|
|
6516
|
+
height: frameHeight,
|
|
6517
|
+
channels: 3
|
|
6518
|
+
} }).extract({
|
|
6519
|
+
left,
|
|
6520
|
+
top,
|
|
6521
|
+
width,
|
|
6522
|
+
height
|
|
6523
|
+
}).jpeg({ quality: 90 }).toBuffer(),
|
|
6524
|
+
width,
|
|
6525
|
+
height
|
|
6526
|
+
};
|
|
6527
|
+
}
|
|
6528
|
+
//#endregion
|
|
6529
|
+
//#region src/shared/frame/square-subject-crop.ts
|
|
6530
|
+
/** Expansion factor applied to the bbox long side to frame the subject with a
|
|
6531
|
+
* little breathing room (operator choice: ×1.2). */
|
|
6532
|
+
var SQUARE_SUBJECT_CROP_EXPANSION = 1.2;
|
|
6533
|
+
/**
|
|
6534
|
+
* Compute a SQUARE crop region in PIXEL space around the subject bbox.
|
|
6535
|
+
*
|
|
6536
|
+
* Algorithm:
|
|
6537
|
+
* 1. center: cx = x + w/2, cy = y + h/2
|
|
6538
|
+
* 2. side = max(w, h) × SQUARE_SUBJECT_CROP_EXPANSION
|
|
6539
|
+
* 3. clamp side to fit the frame: side = min(side, W, H) — a square can never
|
|
6540
|
+
* exceed the frame's SHORT edge (this is the "subject bigger than the
|
|
6541
|
+
* frame's short side" case)
|
|
6542
|
+
* 4. center-and-clamp the origin so the square stays fully inside the frame
|
|
6543
|
+
* 5. round to integer pixels
|
|
6544
|
+
*
|
|
6545
|
+
* The returned region fully contains the bbox whenever the bbox itself fits in a
|
|
6546
|
+
* square of the frame's short side (always true for real detections).
|
|
6547
|
+
*/
|
|
6548
|
+
function squareSubjectCropRegion(bbox, frame) {
|
|
6549
|
+
const { W, H } = frame;
|
|
6550
|
+
const cx = bbox.x + bbox.w / 2;
|
|
6551
|
+
const cy = bbox.y + bbox.h / 2;
|
|
6552
|
+
const longerSide = Math.max(bbox.w, bbox.h);
|
|
6553
|
+
const side = Math.min(longerSide * SQUARE_SUBJECT_CROP_EXPANSION, W, H);
|
|
6554
|
+
const rawX0 = cx - side / 2;
|
|
6555
|
+
const rawY0 = cy - side / 2;
|
|
6556
|
+
const x0 = Math.max(0, Math.min(rawX0, W - side));
|
|
6557
|
+
const y0 = Math.max(0, Math.min(rawY0, H - side));
|
|
6558
|
+
return {
|
|
6559
|
+
x: Math.round(x0),
|
|
6560
|
+
y: Math.round(y0),
|
|
6561
|
+
w: Math.round(side),
|
|
6562
|
+
h: Math.round(side)
|
|
6563
|
+
};
|
|
6564
|
+
}
|
|
6565
|
+
/**
|
|
6566
|
+
* The same SQUARE region as {@link squareSubjectCropRegion}, expressed in
|
|
6567
|
+
* NORMALIZED [0,1]×[0,1] coordinates. A normalized box maps DIRECTLY onto a
|
|
6568
|
+
* native-resolution surface of the SAME aspect ratio, so the region computed
|
|
6569
|
+
* from the detection-frame dimensions addresses the exact same ROI on the
|
|
6570
|
+
* runner's retained native frame. Reuses the pixel geometry verbatim (single
|
|
6571
|
+
* source of truth) and divides by the frame dimensions.
|
|
6572
|
+
*/
|
|
6573
|
+
function squareSubjectCropRegionNormalized(bbox, frame) {
|
|
6574
|
+
const region = squareSubjectCropRegion(bbox, frame);
|
|
6575
|
+
return {
|
|
6576
|
+
x: region.x / frame.W,
|
|
6577
|
+
y: region.y / frame.H,
|
|
6578
|
+
w: region.w / frame.W,
|
|
6579
|
+
h: region.h / frame.H
|
|
6580
|
+
};
|
|
6581
|
+
}
|
|
6582
|
+
/**
|
|
6583
|
+
* Derive the `thumbnailSmall` JPEG from an already-encoded native subject-crop
|
|
6584
|
+
* JPEG. Downscales to at most {@link THUMBNAIL_SMALL_MAX_WIDTH} on the long side
|
|
6585
|
+
* (aspect preserved). If the native crop's long side is already ≤ the cap, the
|
|
6586
|
+
* ORIGINAL buffer is returned unchanged (store as-is, never upscale, no wasted
|
|
6587
|
+
* re-encode).
|
|
6588
|
+
*/
|
|
6589
|
+
async function deriveThumbnailSmall(nativeJpeg) {
|
|
6590
|
+
const meta = await (0, sharp.default)(nativeJpeg).metadata();
|
|
6591
|
+
const longSide = Math.max(meta.width ?? 0, meta.height ?? 0);
|
|
6592
|
+
if (longSide > 0 && longSide <= 480) return nativeJpeg;
|
|
6593
|
+
return (0, sharp.default)(nativeJpeg).resize(480, 480, {
|
|
6594
|
+
fit: "inside",
|
|
6595
|
+
withoutEnlargement: true
|
|
6596
|
+
}).jpeg({ quality: 88 }).toBuffer();
|
|
6597
|
+
}
|
|
6598
|
+
//#endregion
|
|
6480
6599
|
//#region src/shared/frame/box-drawer.ts
|
|
6481
6600
|
var DEFAULT_COLOR = require_dist.DEFAULT_EVENT_COLOR;
|
|
6482
6601
|
var DEFAULT_QUALITY = 80;
|
|
@@ -6645,7 +6764,8 @@ var EventMediaDispatcher = class {
|
|
|
6645
6764
|
const snapshots = input.snapshots ?? [];
|
|
6646
6765
|
const empty = {
|
|
6647
6766
|
storedSnapshots: [],
|
|
6648
|
-
thumbnailTrackIds: []
|
|
6767
|
+
thumbnailTrackIds: [],
|
|
6768
|
+
rasterFallbacks: []
|
|
6649
6769
|
};
|
|
6650
6770
|
if (events.length === 0 && trackFrames.length === 0 && snapshots.length === 0) return empty;
|
|
6651
6771
|
let decoded;
|
|
@@ -6697,21 +6817,67 @@ var EventMediaDispatcher = class {
|
|
|
6697
6817
|
});
|
|
6698
6818
|
return empty;
|
|
6699
6819
|
}
|
|
6820
|
+
const rasterFallbacks = await this.collectRasterFallbacks(frameData, fw, fh, trackFrames, snapshots, input.cropPadding, input.rasterFallbackWantedTrackIds);
|
|
6700
6821
|
for (const ev of events) await this.writeEventMedia(deviceId, frameHandle, fw, fh, ev, input.cropPadding);
|
|
6701
6822
|
for (const tf of trackFrames) await this.writeTrackFrame(deviceId, frameHandle, frameData, fw, fh, tf);
|
|
6702
6823
|
const storedSnapshots = [];
|
|
6703
6824
|
const thumbnailTrackIds = [];
|
|
6704
6825
|
for (const sn of snapshots) {
|
|
6705
|
-
const res = await this.writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn
|
|
6826
|
+
const res = await this.writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn);
|
|
6706
6827
|
if (res.storedSnapshot) storedSnapshots.push(res.storedSnapshot);
|
|
6707
6828
|
if (res.thumbnailWritten) thumbnailTrackIds.push(sn.trackId);
|
|
6708
6829
|
}
|
|
6709
6830
|
return {
|
|
6710
6831
|
storedSnapshots,
|
|
6711
|
-
thumbnailTrackIds
|
|
6832
|
+
thumbnailTrackIds,
|
|
6833
|
+
rasterFallbacks
|
|
6712
6834
|
};
|
|
6713
6835
|
}
|
|
6714
6836
|
/**
|
|
6837
|
+
* Cut ONE clean detection-raster subject crop per WANTED track that has a
|
|
6838
|
+
* target (firstFrame or snapshot) in this frame — the "first available
|
|
6839
|
+
* detection-raster frame" of the zero-media fallback. Cropped from the
|
|
6840
|
+
* already-resolved `frameData` at its REAL resolution via {@link extractCrop}
|
|
6841
|
+
* (extract-only — NEVER upscaled). Deduped per trackId (first target wins).
|
|
6842
|
+
* A per-track encode failure is skipped (logged) — a missing fallback simply
|
|
6843
|
+
* leaves the track with no last-resort preview, never an error.
|
|
6844
|
+
*/
|
|
6845
|
+
async collectRasterFallbacks(frameData, fw, fh, trackFrames, snapshots, cropPadding, wanted) {
|
|
6846
|
+
if (!wanted || wanted.size === 0) return [];
|
|
6847
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6848
|
+
const out = [];
|
|
6849
|
+
const candidates = [...trackFrames.map((t) => ({
|
|
6850
|
+
trackId: t.trackId,
|
|
6851
|
+
timestamp: t.timestamp,
|
|
6852
|
+
bbox: t.bbox
|
|
6853
|
+
})), ...snapshots.map((s) => ({
|
|
6854
|
+
trackId: s.trackId,
|
|
6855
|
+
timestamp: s.timestamp,
|
|
6856
|
+
bbox: s.bbox
|
|
6857
|
+
}))];
|
|
6858
|
+
for (const c of candidates) {
|
|
6859
|
+
if (!wanted.has(c.trackId) || seen.has(c.trackId)) continue;
|
|
6860
|
+
seen.add(c.trackId);
|
|
6861
|
+
try {
|
|
6862
|
+
const { crop } = await extractCrop(frameData, fw, fh, squareSafeCropRegionNormalized(c.bbox, {
|
|
6863
|
+
W: fw,
|
|
6864
|
+
H: fh
|
|
6865
|
+
}, cropPadding));
|
|
6866
|
+
out.push({
|
|
6867
|
+
trackId: c.trackId,
|
|
6868
|
+
timestamp: c.timestamp,
|
|
6869
|
+
jpeg: crop
|
|
6870
|
+
});
|
|
6871
|
+
} catch (err) {
|
|
6872
|
+
this.deps.logger.debug("event media: raster fallback crop failed", { meta: {
|
|
6873
|
+
trackId: c.trackId,
|
|
6874
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6875
|
+
} });
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
return out;
|
|
6879
|
+
}
|
|
6880
|
+
/**
|
|
6715
6881
|
* Periodic per-track media (§5). The boxed FULL frame is encoded once and
|
|
6716
6882
|
* shared by the appended `snapshot` (timeline filmstrip) and the rolling
|
|
6717
6883
|
* `lastFrame` (overwrite). The best `thumbnail` is DIFFERENT: a clean
|
|
@@ -6723,7 +6889,7 @@ var EventMediaDispatcher = class {
|
|
|
6723
6889
|
* failed). `thumbnailWritten` reports whether a best `thumbnail` actually
|
|
6724
6890
|
* landed this frame (#27-A) so the caller can stop forcing retries.
|
|
6725
6891
|
*/
|
|
6726
|
-
async writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn
|
|
6892
|
+
async writeTrackSnapshot(deviceId, frameHandle, frameData, fw, fh, sn) {
|
|
6727
6893
|
if (!sn.appendSnapshot && !sn.rollingLastFrame && !sn.bestThumbnail) return {
|
|
6728
6894
|
storedSnapshot: null,
|
|
6729
6895
|
thumbnailWritten: false
|
|
@@ -6750,8 +6916,11 @@ var EventMediaDispatcher = class {
|
|
|
6750
6916
|
if (sn.rollingLastFrame && boxed) await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
|
|
6751
6917
|
let thumbnailWritten = false;
|
|
6752
6918
|
if (sn.bestThumbnail) {
|
|
6753
|
-
const
|
|
6754
|
-
if (
|
|
6919
|
+
const variants = await this.cropSubjectVariants(frameHandle, fw, fh, sn.bbox);
|
|
6920
|
+
if (variants) {
|
|
6921
|
+
thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail", sn.timestamp, variants.thumbnail);
|
|
6922
|
+
await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall", sn.timestamp, variants.thumbnailSmall);
|
|
6923
|
+
}
|
|
6755
6924
|
}
|
|
6756
6925
|
return {
|
|
6757
6926
|
storedSnapshot: stored,
|
|
@@ -6801,6 +6970,54 @@ var EventMediaDispatcher = class {
|
|
|
6801
6970
|
}
|
|
6802
6971
|
}
|
|
6803
6972
|
/**
|
|
6973
|
+
* The best-shot subject crop as its TWO persisted variants (best-crop
|
|
6974
|
+
* fast-load, 2026-07-21). ONE native fetch: the SQUARE-framed subject ROI
|
|
6975
|
+
* (side = max(w,h)×1.2, clamped to the frame — {@link squareSubjectCropRegionNormalized})
|
|
6976
|
+
* is requested from the runner's retained native surface with NO `maxWidth`
|
|
6977
|
+
* (uncapped TRUE native, decision #3) → the `thumbnail`. The `thumbnailSmall`
|
|
6978
|
+
* is DERIVED by downscaling that SAME JPEG to the 480 long-side cap — never a
|
|
6979
|
+
* second `getNativeCropJpeg`, never upscaled ({@link deriveThumbnailSmall}
|
|
6980
|
+
* returns the native buffer as-is when it is already ≤ 480).
|
|
6981
|
+
*
|
|
6982
|
+
* NATIVE-OR-NOTHING: any miss/error (or a runner without the method) returns
|
|
6983
|
+
* `null` after a loud `logger.warn`; the caller SKIPS the write and the
|
|
6984
|
+
* per-frame retry lands a real native crop later. Never a local resize
|
|
6985
|
+
* upscale of a ≤640 tile (a blurred lie).
|
|
6986
|
+
*/
|
|
6987
|
+
async cropSubjectVariants(frameHandle, fw, fh, bbox) {
|
|
6988
|
+
if (!this.deps.getNativeCropJpeg) {
|
|
6989
|
+
this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
|
|
6990
|
+
shmId: frameHandle.shmId,
|
|
6991
|
+
reason: "no-native-cap"
|
|
6992
|
+
} });
|
|
6993
|
+
return null;
|
|
6994
|
+
}
|
|
6995
|
+
try {
|
|
6996
|
+
const norm = squareSubjectCropRegionNormalized(bbox, {
|
|
6997
|
+
W: fw,
|
|
6998
|
+
H: fh
|
|
6999
|
+
});
|
|
7000
|
+
const native = await this.deps.getNativeCropJpeg(frameHandle, norm);
|
|
7001
|
+
if (!native) {
|
|
7002
|
+
this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
|
|
7003
|
+
shmId: frameHandle.shmId,
|
|
7004
|
+
reason: "native-miss"
|
|
7005
|
+
} });
|
|
7006
|
+
return null;
|
|
7007
|
+
}
|
|
7008
|
+
return {
|
|
7009
|
+
thumbnail: native,
|
|
7010
|
+
thumbnailSmall: await deriveThumbnailSmall(native)
|
|
7011
|
+
};
|
|
7012
|
+
} catch (err) {
|
|
7013
|
+
this.deps.logger.warn("native subject crop miss — will retry, no upscale", { meta: {
|
|
7014
|
+
shmId: frameHandle.shmId,
|
|
7015
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7016
|
+
} });
|
|
7017
|
+
return null;
|
|
7018
|
+
}
|
|
7019
|
+
}
|
|
7020
|
+
/**
|
|
6804
7021
|
* The boxed timeline tile (`firstFrame`/`snapshot`/`lastFrame`): the track's
|
|
6805
7022
|
* OWN box burned onto the NATIVE full frame downscaled to
|
|
6806
7023
|
* {@link SNAPSHOT_MAX_WIDTH}. On a native miss it falls back to the ≤640
|
|
@@ -6927,36 +7144,6 @@ var EventMediaDispatcher = class {
|
|
|
6927
7144
|
}
|
|
6928
7145
|
};
|
|
6929
7146
|
//#endregion
|
|
6930
|
-
//#region src/shared/frame/crop-extractor.ts
|
|
6931
|
-
/**
|
|
6932
|
-
* Extracts a JPEG-encoded crop from a raw frame buffer using a normalized bounding box.
|
|
6933
|
-
* Coordinates are clamped to frame bounds to avoid out-of-range errors.
|
|
6934
|
-
*/
|
|
6935
|
-
async function extractCrop(frameData, frameWidth, frameHeight, bbox) {
|
|
6936
|
-
const rawLeft = Math.round(bbox.x * frameWidth);
|
|
6937
|
-
const rawTop = Math.round(bbox.y * frameHeight);
|
|
6938
|
-
const rawWidth = Math.round(bbox.w * frameWidth);
|
|
6939
|
-
const rawHeight = Math.round(bbox.h * frameHeight);
|
|
6940
|
-
const left = Math.max(0, Math.min(rawLeft, frameWidth - 1));
|
|
6941
|
-
const top = Math.max(0, Math.min(rawTop, frameHeight - 1));
|
|
6942
|
-
const width = Math.max(1, Math.min(rawWidth, frameWidth - left));
|
|
6943
|
-
const height = Math.max(1, Math.min(rawHeight, frameHeight - top));
|
|
6944
|
-
return {
|
|
6945
|
-
crop: await (0, sharp.default)(frameData, { raw: {
|
|
6946
|
-
width: frameWidth,
|
|
6947
|
-
height: frameHeight,
|
|
6948
|
-
channels: 3
|
|
6949
|
-
} }).extract({
|
|
6950
|
-
left,
|
|
6951
|
-
top,
|
|
6952
|
-
width,
|
|
6953
|
-
height
|
|
6954
|
-
}).jpeg({ quality: 90 }).toBuffer(),
|
|
6955
|
-
width,
|
|
6956
|
-
height
|
|
6957
|
-
};
|
|
6958
|
-
}
|
|
6959
|
-
//#endregion
|
|
6960
7147
|
//#region src/pipeline-analytics/embedding/embedding-dispatcher.ts
|
|
6961
7148
|
function selectCropBbox(detection) {
|
|
6962
7149
|
return detection.refinedBbox ?? detection.bbox;
|
|
@@ -8745,6 +8932,16 @@ function planPeriodicMedia(input) {
|
|
|
8745
8932
|
bestThumbnail: input.isNewBest || !thumbnailLanded
|
|
8746
8933
|
};
|
|
8747
8934
|
}
|
|
8935
|
+
var DEFAULT_ZERO_MEDIA_POLICY_CONFIG = { suppressMaxDurationMs: 1e3 };
|
|
8936
|
+
/**
|
|
8937
|
+
* Classify a closing track's persistence outcome. Pure — see the module header
|
|
8938
|
+
* for the full contract.
|
|
8939
|
+
*/
|
|
8940
|
+
function decideZeroMediaPolicy(input, config = DEFAULT_ZERO_MEDIA_POLICY_CONFIG) {
|
|
8941
|
+
if (input.hasMedia) return "persist";
|
|
8942
|
+
if (input.durationMs < config.suppressMaxDurationMs && !input.confirmed) return "suppress";
|
|
8943
|
+
return input.hasRasterFallback ? "raster-fallback" : "persist";
|
|
8944
|
+
}
|
|
8748
8945
|
//#endregion
|
|
8749
8946
|
//#region src/pipeline-analytics/best-thumbnail-guard.ts
|
|
8750
8947
|
/**
|
|
@@ -11997,10 +12194,28 @@ function pickEventOwnedMedia(files) {
|
|
|
11997
12194
|
return files.find((f) => f.kind === "crop") ?? files.find((f) => f.kind === "fullFrameBoxed") ?? files[0];
|
|
11998
12195
|
}
|
|
11999
12196
|
/** Pick a track's fallback media in the shared cadence-preference order the
|
|
12000
|
-
* KeyEvent path uses:
|
|
12001
|
-
*
|
|
12197
|
+
* KeyEvent path uses: `thumbnailSmall` (480 fast-load) → best `thumbnail` →
|
|
12198
|
+
* rolling `lastFrame` → `firstFrame` → newest `snapshot` → any track blob.
|
|
12199
|
+
* Undefined when the track owns no media. */
|
|
12002
12200
|
function pickTrackFallbackMedia(files) {
|
|
12003
|
-
return 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];
|
|
12201
|
+
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];
|
|
12202
|
+
}
|
|
12203
|
+
/**
|
|
12204
|
+
* Crop-forced resolution (the `?kind=crop` reel/timeline path). Prefer a CLEAN
|
|
12205
|
+
* (never-boxed) frame via the injected `pickClean`; when the track owns NO clean
|
|
12206
|
+
* frame at all — the regression class created by retiring `crop`/`fullFrame`
|
|
12207
|
+
* production (2026-07-21), leaving degenerate tracks with only boxed
|
|
12208
|
+
* `lastFrame`/`snapshot`/`firstFrame` — degrade to ANY track media rather than
|
|
12209
|
+
* returning undefined. A boxed frame (with the target's box drawn) is a real
|
|
12210
|
+
* preview; an empty tile is not. Clean is always tried FIRST so the common case
|
|
12211
|
+
* still serves an unboxed tile. Undefined only when the owner has NO media.
|
|
12212
|
+
*
|
|
12213
|
+
* `pickClean` is injected because the clean-kind picker (`pickCleanMedia`) lives
|
|
12214
|
+
* in the addon `index.ts` over the concrete `MediaFileKind` set; this keeps the
|
|
12215
|
+
* composition pure + unit-testable here.
|
|
12216
|
+
*/
|
|
12217
|
+
function resolveCropForcedMedia(files, pickClean) {
|
|
12218
|
+
return pickClean(files) ?? pickTrackFallbackMedia(files);
|
|
12004
12219
|
}
|
|
12005
12220
|
/**
|
|
12006
12221
|
* Resolve the default-path media for an event id: the event's own media when it
|
|
@@ -12358,6 +12573,14 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12358
12573
|
* area) + emit time, so a material improvement is measured against the
|
|
12359
12574
|
* last emit and debounced. Seeded at `start`, dropped at `end`. */
|
|
12360
12575
|
trackLifecycleUpdateMem = /* @__PURE__ */ new Map();
|
|
12576
|
+
/** Per-active-track close-policy state (spec §"Zero-media track policy"):
|
|
12577
|
+
* whether the track ever reached the tracker's hit-confirmation
|
|
12578
|
+
* (`trackAge >= minHits`) and the FIRST retained detection-raster subject
|
|
12579
|
+
* crop. Both are read ONLY at close (`sweepExpiredTracks`) to decide
|
|
12580
|
+
* suppress / raster-fallback / persist for a track that ends with no media.
|
|
12581
|
+
* `deviceId` lets `clearDevice` prune without an active-track lookup. Dropped
|
|
12582
|
+
* on every teardown path. */
|
|
12583
|
+
trackClosureState = /* @__PURE__ */ new Map();
|
|
12361
12584
|
/** The shared crop extractor (native-res first, detection-frame fallback),
|
|
12362
12585
|
* captured in the constructor so `processFrame` can crop object thumbnails in
|
|
12363
12586
|
* the same live-frame window as the face/plate/event-media captures. The
|
|
@@ -12416,7 +12639,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12416
12639
|
let storage = this.ctx.kernel.storage;
|
|
12417
12640
|
const mediaRoot = process.env.CAMSTACK_MEDIA_ROOT?.trim();
|
|
12418
12641
|
if (mediaRoot) {
|
|
12419
|
-
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-
|
|
12642
|
+
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-C4bKtLou.js"));
|
|
12420
12643
|
storage = new FilesystemStorageProvider(mediaRoot);
|
|
12421
12644
|
logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
|
|
12422
12645
|
}
|
|
@@ -12849,6 +13072,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12849
13072
|
this.bindingCache?.onBindingsChanged(data);
|
|
12850
13073
|
if (data.capName === "pipeline-analytics" && data.reason === "wrapper-deactivated") {
|
|
12851
13074
|
this.trackStore?.clearDevice(data.deviceId);
|
|
13075
|
+
this.clearClosureStateForDevice(data.deviceId);
|
|
12852
13076
|
this.stationaryRegistry?.forgetDevice(data.deviceId);
|
|
12853
13077
|
this.overlayState.clearDevice(data.deviceId);
|
|
12854
13078
|
this.overlaySynthesisWarnAt.delete(data.deviceId);
|
|
@@ -12867,6 +13091,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
12867
13091
|
this.unsubDeviceUnreg = this.ctx.eventBus.subscribe({ category: require_dist.EventCategory.DeviceUnregistered }, (ev) => {
|
|
12868
13092
|
const { deviceId } = ev.data;
|
|
12869
13093
|
this.trackStore?.clearDevice(deviceId);
|
|
13094
|
+
this.clearClosureStateForDevice(deviceId);
|
|
12870
13095
|
this.stationaryRegistry?.forgetDevice(deviceId);
|
|
12871
13096
|
this.overlayState.clearDevice(deviceId);
|
|
12872
13097
|
this.overlaySynthesisWarnAt.delete(deviceId);
|
|
@@ -13222,6 +13447,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
13222
13447
|
this.thumbnailInFlight.clear();
|
|
13223
13448
|
this.keyFrameInFlight.clear();
|
|
13224
13449
|
this.trackLifecycleUpdateMem.clear();
|
|
13450
|
+
this.trackClosureState.clear();
|
|
13225
13451
|
this.objectEmbeddingBestSelector.clear();
|
|
13226
13452
|
this.levelStateByDevice.clear();
|
|
13227
13453
|
this.settingsCacheByDevice.clear();
|
|
@@ -13340,6 +13566,10 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
13340
13566
|
});
|
|
13341
13567
|
positionsCountById.set(t.trackId, upserted.positions.length);
|
|
13342
13568
|
}
|
|
13569
|
+
if (result.rawTrackedDetections.length > 0) {
|
|
13570
|
+
const { minHits } = await this.resolveDeviceDetectionSensitivitySettings(deviceId);
|
|
13571
|
+
for (const rt of result.rawTrackedDetections) if (rt.trackAge >= minHits) this.markTrackConfirmed(deviceId, rt.trackId);
|
|
13572
|
+
}
|
|
13343
13573
|
const log = this.ctx.logger.withTags({ deviceId });
|
|
13344
13574
|
const prevIds = this.lastActiveTrackIds.get(key) ?? /* @__PURE__ */ new Set();
|
|
13345
13575
|
const firstFrameTargets = [];
|
|
@@ -13392,6 +13622,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
13392
13622
|
...t.label ? { label: t.label } : {}
|
|
13393
13623
|
});
|
|
13394
13624
|
this.trackStore.seedSnapshotClock(id, result.timestamp, t.bbox);
|
|
13625
|
+
this.lastFrameAtByTrack.set(id, result.timestamp);
|
|
13395
13626
|
}
|
|
13396
13627
|
this.ctx.eventBus.emit({
|
|
13397
13628
|
id: `pa-${(0, node_crypto.randomUUID)()}`,
|
|
@@ -13563,14 +13794,25 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
13563
13794
|
} });
|
|
13564
13795
|
const thumbInFlightTrackIds = snapshotTargets.filter((t) => t.bestThumbnail).map((t) => t.trackId);
|
|
13565
13796
|
for (const trackId of thumbInFlightTrackIds) this.thumbnailInFlight.add(trackId);
|
|
13797
|
+
const rasterFallbackWantedTrackIds = /* @__PURE__ */ new Set();
|
|
13798
|
+
for (const t of firstFrameTargets) if (!this.trackClosureState.get(t.trackId)?.rasterFallback) rasterFallbackWantedTrackIds.add(t.trackId);
|
|
13799
|
+
for (const s of snapshotTargets) if (!this.trackClosureState.get(s.trackId)?.rasterFallback) rasterFallbackWantedTrackIds.add(s.trackId);
|
|
13566
13800
|
this.eventMediaDispatcher.captureForFrame({
|
|
13567
13801
|
deviceId,
|
|
13568
13802
|
frameHandle,
|
|
13569
13803
|
events: eventTargets,
|
|
13570
13804
|
trackFrames: firstFrameTargets,
|
|
13571
13805
|
snapshots: snapshotTargets,
|
|
13572
|
-
cropPadding: mediaSettings.cropPadding
|
|
13806
|
+
cropPadding: mediaSettings.cropPadding,
|
|
13807
|
+
rasterFallbackWantedTrackIds
|
|
13573
13808
|
}).then((res) => {
|
|
13809
|
+
for (const rf of res.rasterFallbacks) {
|
|
13810
|
+
const st = this.ensureTrackClosureState(deviceId, rf.trackId);
|
|
13811
|
+
if (!st.rasterFallback) st.rasterFallback = {
|
|
13812
|
+
jpeg: rf.jpeg,
|
|
13813
|
+
timestamp: rf.timestamp
|
|
13814
|
+
};
|
|
13815
|
+
}
|
|
13574
13816
|
for (const s of res.storedSnapshots) this.trackStore?.addSnapshot(s.trackId, {
|
|
13575
13817
|
timestamp: s.timestamp,
|
|
13576
13818
|
position: {
|
|
@@ -14581,6 +14823,44 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
14581
14823
|
const expired = await this.trackStore.expireStale(Date.now());
|
|
14582
14824
|
for (const t of expired) {
|
|
14583
14825
|
const duration = t.lastSeen - t.firstSeen;
|
|
14826
|
+
const closure = this.trackClosureState.get(t.trackId);
|
|
14827
|
+
const outcome = decideZeroMediaPolicy({
|
|
14828
|
+
durationMs: duration,
|
|
14829
|
+
hasMedia: (await this.mediaStore?.listByOwner("track", t.trackId) ?? []).length > 0,
|
|
14830
|
+
confirmed: closure?.confirmed ?? false,
|
|
14831
|
+
hasRasterFallback: closure?.rasterFallback !== void 0
|
|
14832
|
+
});
|
|
14833
|
+
if (outcome === "suppress") {
|
|
14834
|
+
this.ctx.logger.info("track suppressed (zero-media false birth)", {
|
|
14835
|
+
tags: { deviceId: t.deviceId },
|
|
14836
|
+
meta: {
|
|
14837
|
+
trackId: t.trackId,
|
|
14838
|
+
className: t.className,
|
|
14839
|
+
durationMs: duration,
|
|
14840
|
+
positions: t.positions.length
|
|
14841
|
+
}
|
|
14842
|
+
});
|
|
14843
|
+
await this.suppressZeroMediaTrack(t.deviceId, t.trackId);
|
|
14844
|
+
continue;
|
|
14845
|
+
}
|
|
14846
|
+
if (outcome === "raster-fallback" && closure?.rasterFallback) try {
|
|
14847
|
+
await this.mediaStore?.put({
|
|
14848
|
+
deviceId: t.deviceId,
|
|
14849
|
+
ownerKind: "track",
|
|
14850
|
+
ownerId: t.trackId,
|
|
14851
|
+
kind: "thumbnail",
|
|
14852
|
+
timestamp: closure.rasterFallback.timestamp,
|
|
14853
|
+
data: closure.rasterFallback.jpeg
|
|
14854
|
+
});
|
|
14855
|
+
} catch (err) {
|
|
14856
|
+
this.ctx.logger.debug("zero-media raster fallback put failed", {
|
|
14857
|
+
tags: { deviceId: t.deviceId },
|
|
14858
|
+
meta: {
|
|
14859
|
+
trackId: t.trackId,
|
|
14860
|
+
error: String(err)
|
|
14861
|
+
}
|
|
14862
|
+
});
|
|
14863
|
+
}
|
|
14584
14864
|
this.ctx.logger.info("track ended", {
|
|
14585
14865
|
tags: { deviceId: t.deviceId },
|
|
14586
14866
|
meta: {
|
|
@@ -14691,12 +14971,53 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
14691
14971
|
});
|
|
14692
14972
|
this.emitTrackLifecycle(endPayload, t.lastSeen);
|
|
14693
14973
|
this.trackLifecycleUpdateMem.delete(t.trackId);
|
|
14974
|
+
this.trackClosureState.delete(t.trackId);
|
|
14694
14975
|
}
|
|
14695
14976
|
} catch (err) {
|
|
14696
14977
|
if (this.shuttingDown) return;
|
|
14697
14978
|
this.ctx.logger.debug("sweepExpiredTracks failed", { meta: { error: String(err) } });
|
|
14698
14979
|
}
|
|
14699
14980
|
}
|
|
14981
|
+
/**
|
|
14982
|
+
* Undo a zero-media false-birth track (spec §"Zero-media track policy"): tear
|
|
14983
|
+
* down its live per-track state (mirroring the natural end MINUS importance
|
|
14984
|
+
* scoring + the lifecycle `end`/`TrackEnded` events) and cascade-delete the
|
|
14985
|
+
* just-persisted row + any events/media so nothing dangles. Best-effort.
|
|
14986
|
+
*/
|
|
14987
|
+
async suppressZeroMediaTrack(deviceId, trackId) {
|
|
14988
|
+
const faceEnd = this.faceRecognizer?.onTrackEnd(deviceId, trackId);
|
|
14989
|
+
const dropKeyFrame = () => {
|
|
14990
|
+
this.keyFrameKeyByTrackId.delete(trackId);
|
|
14991
|
+
};
|
|
14992
|
+
if (faceEnd) faceEnd.finally(dropKeyFrame);
|
|
14993
|
+
else dropKeyFrame();
|
|
14994
|
+
this.plateRecognizer?.onTrackEnd(deviceId, trackId);
|
|
14995
|
+
this.bestFrameTracker.delete(trackId);
|
|
14996
|
+
this.objectEmbeddingBestSelector.delete(trackId);
|
|
14997
|
+
this.lastFrameAtByTrack.delete(trackId);
|
|
14998
|
+
this.thumbnailLandedTracks.delete(trackId);
|
|
14999
|
+
this.thumbnailInFlight.delete(trackId);
|
|
15000
|
+
this.keyFrameInFlight.delete(trackId);
|
|
15001
|
+
this.detailDispatcher?.onTrackEnded(deviceId, trackId);
|
|
15002
|
+
this.overlayState.onTrackEnded(deviceId, trackId);
|
|
15003
|
+
this.trackLifecycleUpdateMem.delete(trackId);
|
|
15004
|
+
this.trackClosureState.delete(trackId);
|
|
15005
|
+
if (this.eventStore && this.mediaStore && this.trackStore) try {
|
|
15006
|
+
await cascadeDeleteTrack({
|
|
15007
|
+
eventStore: this.eventStore,
|
|
15008
|
+
mediaStore: this.mediaStore,
|
|
15009
|
+
trackStore: this.trackStore
|
|
15010
|
+
}, trackId);
|
|
15011
|
+
} catch (err) {
|
|
15012
|
+
this.ctx.logger.debug("zero-media suppression cascade failed", {
|
|
15013
|
+
tags: { deviceId },
|
|
15014
|
+
meta: {
|
|
15015
|
+
trackId,
|
|
15016
|
+
error: String(err)
|
|
15017
|
+
}
|
|
15018
|
+
});
|
|
15019
|
+
}
|
|
15020
|
+
}
|
|
14700
15021
|
async sweepRetention() {
|
|
14701
15022
|
if (this.shuttingDown || !this.eventStore || !this.mediaStore) return;
|
|
14702
15023
|
const now = Date.now();
|
|
@@ -14906,6 +15227,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
14906
15227
|
this.thumbnailInFlight.delete(track.trackId);
|
|
14907
15228
|
this.keyFrameInFlight.delete(track.trackId);
|
|
14908
15229
|
this.trackLifecycleUpdateMem.delete(track.trackId);
|
|
15230
|
+
this.trackClosureState.delete(track.trackId);
|
|
14909
15231
|
this.detailDispatcher?.onTrackEnded(deviceId, track.trackId);
|
|
14910
15232
|
this.overlayState.onTrackEnded(deviceId, track.trackId);
|
|
14911
15233
|
this.lastActiveTrackIds.get(key)?.delete(track.trackId);
|
|
@@ -15204,6 +15526,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
15204
15526
|
}
|
|
15205
15527
|
async clearTracks(input) {
|
|
15206
15528
|
this.trackStore?.clearDevice(input.deviceId);
|
|
15529
|
+
this.clearClosureStateForDevice(input.deviceId);
|
|
15207
15530
|
this.stationaryRegistry?.clearDevice(input.deviceId);
|
|
15208
15531
|
this.overlayState.clearDevice(input.deviceId);
|
|
15209
15532
|
this.overlaySynthesisWarnAt.delete(input.deviceId);
|
|
@@ -15416,6 +15739,27 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
15416
15739
|
clearLiveTrackState(deviceId, trackId) {
|
|
15417
15740
|
this.detailDispatcher?.onTrackEnded(deviceId, trackId);
|
|
15418
15741
|
this.overlayState.onTrackEnded(deviceId, trackId);
|
|
15742
|
+
this.trackClosureState.delete(trackId);
|
|
15743
|
+
}
|
|
15744
|
+
/** Lazily create + return a track's close-policy state (zero-media policy). */
|
|
15745
|
+
ensureTrackClosureState(deviceId, trackId) {
|
|
15746
|
+
let st = this.trackClosureState.get(trackId);
|
|
15747
|
+
if (!st) {
|
|
15748
|
+
st = {
|
|
15749
|
+
deviceId,
|
|
15750
|
+
confirmed: false
|
|
15751
|
+
};
|
|
15752
|
+
this.trackClosureState.set(trackId, st);
|
|
15753
|
+
}
|
|
15754
|
+
return st;
|
|
15755
|
+
}
|
|
15756
|
+
/** Sticky-mark a track as confirmed once it reaches the tracker's hit gate. */
|
|
15757
|
+
markTrackConfirmed(deviceId, trackId) {
|
|
15758
|
+
this.ensureTrackClosureState(deviceId, trackId).confirmed = true;
|
|
15759
|
+
}
|
|
15760
|
+
/** Drop every close-policy entry for a device (mirrors `trackStore.clearDevice`). */
|
|
15761
|
+
clearClosureStateForDevice(deviceId) {
|
|
15762
|
+
for (const [trackId, st] of this.trackClosureState) if (st.deviceId === deviceId) this.trackClosureState.delete(trackId);
|
|
15419
15763
|
}
|
|
15420
15764
|
async deleteTracks(input) {
|
|
15421
15765
|
const cascade = this.buildTrackCascadeRegistry();
|
|
@@ -15710,11 +16054,11 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
15710
16054
|
const eventFiles = await (this.mediaStore?.listByOwner("event", id) ?? Promise.resolve([]));
|
|
15711
16055
|
if (preferKind !== void 0 && preferKind.length > 0) {
|
|
15712
16056
|
const trackFiles = await (this.mediaStore?.listByOwner("track", id) ?? Promise.resolve([]));
|
|
15713
|
-
const
|
|
15714
|
-
if (!
|
|
16057
|
+
const chosen = resolveCropForcedMedia([...eventFiles, ...trackFiles], (files) => pickCleanMedia(files, preferKind));
|
|
16058
|
+
if (!chosen) return null;
|
|
15715
16059
|
return {
|
|
15716
|
-
bytes: Buffer.from(
|
|
15717
|
-
key:
|
|
16060
|
+
bytes: Buffer.from(chosen.base64, "base64"),
|
|
16061
|
+
key: chosen.key
|
|
15718
16062
|
};
|
|
15719
16063
|
}
|
|
15720
16064
|
const chosen = await resolveDefaultEventMedia({
|