@camstack/addon-post-analysis 1.2.133 → 1.2.135
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-DruBvw_u.mjs → dist-BAN19J7s.mjs} +255 -25
- package/dist/{dist-Dqa0bdV6.js → dist-kn_4KEmG.js} +255 -25
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CJn5xy9l.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CnLNNc0Y.mjs} +3 -3
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-D_xOplZC.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-y4mecoXI.mjs → hostInit-7L4CyDzy.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +269 -26
- package/dist/pipeline-analytics/index.mjs +269 -26
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +3 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CZJ4DupR.mjs +0 -26
|
@@ -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-kn_4KEmG.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");
|
|
@@ -29021,9 +29021,18 @@ function pairRiders(detections) {
|
|
|
29021
29021
|
}
|
|
29022
29022
|
//#endregion
|
|
29023
29023
|
//#region src/pipeline-analytics/pipeline/tracker/rescue-growth.ts
|
|
29024
|
-
/**
|
|
29025
|
-
*
|
|
29026
|
-
|
|
29024
|
+
/**
|
|
29025
|
+
* How far the grown box's centroid may sit, as a multiple of the **TRACK's own**
|
|
29026
|
+
* diagonal. It was a multiple of the LARGER of the two diagonals until
|
|
29027
|
+
* 2026-08-25, which meant the bigger the INTRUDING detection, the further it
|
|
29028
|
+
* could reach to steal an identity. Live track `59dfb972` lost its car that
|
|
29029
|
+
* way: a 386×752 box 574 px away out-reached a 364×385 track purely because it
|
|
29030
|
+
* was 2.07× the area. The reach is now a property of the track alone.
|
|
29031
|
+
*
|
|
29032
|
+
* 2.3 covers the live `4285605a` bird→dog jump (74.7 px over a 35.8 px crop
|
|
29033
|
+
* diagonal = 2.085) with the same ~10% headroom the old 1.35 gave it.
|
|
29034
|
+
*/
|
|
29035
|
+
var GROWTH_CENTROID_FACTOR = 2.3;
|
|
29027
29036
|
function intersectionArea(a, b) {
|
|
29028
29037
|
const x1 = Math.max(a.x, b.x);
|
|
29029
29038
|
const y1 = Math.max(a.y, b.y);
|
|
@@ -29032,11 +29041,30 @@ function intersectionArea(a, b) {
|
|
|
29032
29041
|
if (x2 <= x1 || y2 <= y1) return 0;
|
|
29033
29042
|
return (x2 - x1) * (y2 - y1);
|
|
29034
29043
|
}
|
|
29035
|
-
/**
|
|
29036
|
-
*
|
|
29037
|
-
*
|
|
29038
|
-
*
|
|
29039
|
-
|
|
29044
|
+
/**
|
|
29045
|
+
* Same-class partial crop growing into (or collapsing from) a full-body box
|
|
29046
|
+
* with IoU 0. Used by rescue and spawn-dedup so we do not mint a second id.
|
|
29047
|
+
* A SMALL box nested inside a LARGE one is occlusion, not growth — those
|
|
29048
|
+
* stay two tracks (sort-tracker occlusion suite).
|
|
29049
|
+
*
|
|
29050
|
+
* **Geometry alone cannot make this gate safe, and that is measured, not
|
|
29051
|
+
* assumed.** The two live cases:
|
|
29052
|
+
*
|
|
29053
|
+
* | case | dist | trackDiag | dist/trackDiag | dist/maxDiag | dt | trackDiag/s |
|
|
29054
|
+
* | --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
29055
|
+
* | `4285605a` bird→dog (KEEP) | 74.7 | 35.8 | 2.085 | 1.238 | 170 ms | 12.26 |
|
|
29056
|
+
* | `59dfb972` car A→car B (REJECT) | 573.8 | 529.8 | 1.083 | 0.679 | 106 ms | 10.22 |
|
|
29057
|
+
*
|
|
29058
|
+
* The theft is a *milder* jump than the case the gate exists for, in EVERY
|
|
29059
|
+
* scale-relative measure — and still milder once divided by elapsed time. A
|
|
29060
|
+
* scale-invariant geometric predicate that accepts the bird therefore MUST
|
|
29061
|
+
* accept the car; so must a speed cap expressed in track-diagonals per second.
|
|
29062
|
+
* The separator has to be something other than the shape of the two boxes, and
|
|
29063
|
+
* the one that holds is track MATURITY ({@link GROWTH_MAX_TRACK_AGE_MS}).
|
|
29064
|
+
*/
|
|
29065
|
+
function growthAdjacentMatch(track, det, ctx) {
|
|
29066
|
+
if (ctx.trackAgeMs > 1500) return false;
|
|
29067
|
+
if (ctx.gapMs > 1e3) return false;
|
|
29040
29068
|
const trackArea = track.w * track.h;
|
|
29041
29069
|
const detArea = det.w * det.h;
|
|
29042
29070
|
if (trackArea <= 0 || detArea <= 0) return false;
|
|
@@ -29046,8 +29074,24 @@ function growthAdjacentMatch(track, det) {
|
|
|
29046
29074
|
if (intersectionArea(track, det) / smaller >= .5) return false;
|
|
29047
29075
|
const tc = bboxCentroid(track);
|
|
29048
29076
|
const dc = bboxCentroid(det);
|
|
29049
|
-
return Math.hypot(tc.x - dc.x, tc.y - dc.y) <= GROWTH_CENTROID_FACTOR * Math.
|
|
29077
|
+
return Math.hypot(tc.x - dc.x, tc.y - dc.y) <= GROWTH_CENTROID_FACTOR * Math.hypot(track.w, track.h);
|
|
29050
29078
|
}
|
|
29079
|
+
/** Per-(stage, gate) counter + sampling decision. One instance per tracker, so
|
|
29080
|
+
* the counts are per camera and per detection source. */
|
|
29081
|
+
var LooseBindSampler = class {
|
|
29082
|
+
counts = /* @__PURE__ */ new Map();
|
|
29083
|
+
/** Increment the (stage, gate) counter and answer with the running total when
|
|
29084
|
+
* this decision should be reported, or `undefined` when it is sampled out. */
|
|
29085
|
+
next(stage, gate) {
|
|
29086
|
+
const key = `${stage}|${gate}`;
|
|
29087
|
+
const count = (this.counts.get(key) ?? 0) + 1;
|
|
29088
|
+
this.counts.set(key, count);
|
|
29089
|
+
if (count === 1 || count % 50 === 0) return count;
|
|
29090
|
+
}
|
|
29091
|
+
reset() {
|
|
29092
|
+
this.counts.clear();
|
|
29093
|
+
}
|
|
29094
|
+
};
|
|
29051
29095
|
//#endregion
|
|
29052
29096
|
//#region src/pipeline-analytics/pipeline/tracker/sort-tracker.ts
|
|
29053
29097
|
var DEFAULT_TRACKER_CONFIG = {
|
|
@@ -29125,12 +29169,62 @@ var SortTracker = class SortTracker {
|
|
|
29125
29169
|
* moment the pair stops overlapping and are pruned when either track dies.
|
|
29126
29170
|
*/
|
|
29127
29171
|
dupStreaks = /* @__PURE__ */ new Map();
|
|
29172
|
+
/** Per-(stage, gate) counters behind the sampled salvage-bind diagnostics. */
|
|
29173
|
+
bindSampler = new LooseBindSampler();
|
|
29174
|
+
/** Sink for salvage-bind diagnostics. Null until an owner that knows the
|
|
29175
|
+
* `deviceId` wires one — see {@link setBindObserver}. */
|
|
29176
|
+
bindObserver = null;
|
|
29128
29177
|
constructor(config = {}) {
|
|
29129
29178
|
this.config = {
|
|
29130
29179
|
...DEFAULT_TRACKER_CONFIG,
|
|
29131
29180
|
...config
|
|
29132
29181
|
};
|
|
29133
29182
|
}
|
|
29183
|
+
/**
|
|
29184
|
+
* Wire the diagnostics sink for the salvage branches (rescue, resurrection,
|
|
29185
|
+
* growth spawn-dedup). The tracker has no `deviceId`; the caller that does
|
|
29186
|
+
* stamps it on the log line. Stage 1 is never reported — it runs per track
|
|
29187
|
+
* per frame at frame-rate.
|
|
29188
|
+
*/
|
|
29189
|
+
setBindObserver(observer) {
|
|
29190
|
+
this.bindObserver = observer;
|
|
29191
|
+
}
|
|
29192
|
+
/** Emit one salvage decision, subject to {@link LOOSE_BIND_LOG_SAMPLE_EVERY}.
|
|
29193
|
+
* Nothing is computed when no observer is wired. */
|
|
29194
|
+
reportBind(stage, gate, track, det, timestamp) {
|
|
29195
|
+
const observer = this.bindObserver;
|
|
29196
|
+
if (!observer) return;
|
|
29197
|
+
const bindCount = this.bindSampler.next(stage, gate);
|
|
29198
|
+
if (bindCount === void 0) return;
|
|
29199
|
+
const tc = bboxCentroid(track.bbox);
|
|
29200
|
+
const dc = bboxCentroid(det.bbox);
|
|
29201
|
+
const trackArea = track.bbox.w * track.bbox.h;
|
|
29202
|
+
const detArea = det.bbox.w * det.bbox.h;
|
|
29203
|
+
const smaller = Math.min(trackArea, detArea);
|
|
29204
|
+
observer({
|
|
29205
|
+
stage,
|
|
29206
|
+
gate,
|
|
29207
|
+
trackId: track.id,
|
|
29208
|
+
trackClass: track.class,
|
|
29209
|
+
detClass: det.class,
|
|
29210
|
+
detScore: det.score,
|
|
29211
|
+
iou: iou$2(track.bbox, det.bbox),
|
|
29212
|
+
centroidDistPx: Math.hypot(tc.x - dc.x, tc.y - dc.y),
|
|
29213
|
+
trackDiagPx: Math.hypot(track.bbox.w, track.bbox.h),
|
|
29214
|
+
areaRatio: smaller > 0 ? Math.max(trackArea, detArea) / smaller : 0,
|
|
29215
|
+
dtMs: timestamp - track.lastSeen,
|
|
29216
|
+
trackAgeMs: timestamp - track.firstSeen,
|
|
29217
|
+
bindCount,
|
|
29218
|
+
sampledEvery: 50
|
|
29219
|
+
});
|
|
29220
|
+
}
|
|
29221
|
+
/** When the pair is being judged, relative to `track`. */
|
|
29222
|
+
static growthContext(track, timestamp) {
|
|
29223
|
+
return {
|
|
29224
|
+
trackAgeMs: timestamp - track.firstSeen,
|
|
29225
|
+
gapMs: timestamp - track.lastSeen
|
|
29226
|
+
};
|
|
29227
|
+
}
|
|
29134
29228
|
/** Whether two macro-classes may be collapsed as the SAME subject. Same class
|
|
29135
29229
|
* always; the {person,animal} pair only when `personAnimalDedup` is on. */
|
|
29136
29230
|
dedupCompatible(a, b) {
|
|
@@ -29175,8 +29269,15 @@ var SortTracker = class SortTracker {
|
|
|
29175
29269
|
* set of tracks already surviving this frame (matched, coasting, resurrected,
|
|
29176
29270
|
* and earlier same-frame spawns).
|
|
29177
29271
|
*/
|
|
29178
|
-
isDuplicateSpawn(det, live) {
|
|
29179
|
-
for (const t of live)
|
|
29272
|
+
isDuplicateSpawn(det, live, timestamp) {
|
|
29273
|
+
for (const t of live) {
|
|
29274
|
+
if (!this.dedupCompatible(t.class, det.class)) continue;
|
|
29275
|
+
if (iou$2(det.bbox, t.bbox) >= this.config.dedupSpawnIou) return true;
|
|
29276
|
+
if (growthAdjacentMatch(t.bbox, det.bbox, SortTracker.growthContext(t, timestamp))) {
|
|
29277
|
+
this.reportBind("spawn-dedup", "growth", t, det, timestamp);
|
|
29278
|
+
return true;
|
|
29279
|
+
}
|
|
29280
|
+
}
|
|
29180
29281
|
if (this.config.personAnimalDedup && det.class === ANIMAL_CLASS && det.score < this.config.animalOverPersonMaxScore) {
|
|
29181
29282
|
for (const t of live) if (t.class === PERSON_CLASS && iou$2(det.bbox, t.bbox) >= this.config.animalOverPersonIou) return true;
|
|
29182
29283
|
}
|
|
@@ -29263,15 +29364,36 @@ var SortTracker = class SortTracker {
|
|
|
29263
29364
|
* crouch→animal flip when `classGroupAssoc` is on, but never by an unrelated
|
|
29264
29365
|
* class.
|
|
29265
29366
|
*/
|
|
29266
|
-
looseMatch(track, det) {
|
|
29267
|
-
if (this.assocGroup(track.class) !== this.assocGroup(det.class)) return
|
|
29268
|
-
if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return
|
|
29367
|
+
looseMatch(track, det, timestamp) {
|
|
29368
|
+
if (this.assocGroup(track.class) !== this.assocGroup(det.class)) return null;
|
|
29369
|
+
if (iou$2(track.bbox, det.bbox) >= this.config.rescueIouThreshold) return "iou";
|
|
29269
29370
|
const tc = bboxCentroid(track.bbox);
|
|
29270
29371
|
const dc = bboxCentroid(det.bbox);
|
|
29271
29372
|
const dist = Math.hypot(tc.x - dc.x, tc.y - dc.y);
|
|
29272
29373
|
const diag = Math.hypot(track.bbox.w, track.bbox.h);
|
|
29273
|
-
if (dist <= this.config.rescueCentroidFactor * diag) return
|
|
29274
|
-
|
|
29374
|
+
if (dist <= this.config.rescueCentroidFactor * diag) return "centroid";
|
|
29375
|
+
if (growthAdjacentMatch(track.bbox, det.bbox, SortTracker.growthContext(track, timestamp))) return "growth";
|
|
29376
|
+
return null;
|
|
29377
|
+
}
|
|
29378
|
+
/**
|
|
29379
|
+
* Whether `det` would mint a track of its own if no salvage branch claimed it:
|
|
29380
|
+
* it clears BOTH spawn floors (per-class score, per-class bbox-area fraction).
|
|
29381
|
+
*
|
|
29382
|
+
* A rescue that rests on PROXIMITY ALONE — the `centroid` clause, reached only
|
|
29383
|
+
* when the overlap is already below `rescueIouThreshold` — must lose to such a
|
|
29384
|
+
* detection. Live track `27855a56` bound a second car 452 px away at IoU 0.058
|
|
29385
|
+
* purely because 452 ≤ 0.75 × the 589×445 track diagonal; the box it stole was
|
|
29386
|
+
* a full-size, floor-clearing vehicle that would have spawned perfectly well
|
|
29387
|
+
* on its own. **A spare track costs media; a stolen track corrupts the plate,
|
|
29388
|
+
* the travelled distance and the last frame of the track it was taken from.**
|
|
29389
|
+
*
|
|
29390
|
+
* The `iou` clause is untouched (real overlap IS evidence), and so is `growth`
|
|
29391
|
+
* — that one is bounded by track maturity inside `growthAdjacentMatch`, and a
|
|
29392
|
+
* partial crop is by construction the same subject, so refusing it here would
|
|
29393
|
+
* re-fragment `4285605a`.
|
|
29394
|
+
*/
|
|
29395
|
+
isPlausibleBirth(det, frameArea) {
|
|
29396
|
+
return this.meetsClassScoreFloor(det) && this.meetsSpawnAreaFloor(det, frameArea);
|
|
29275
29397
|
}
|
|
29276
29398
|
/** Fold a matched detection's class + score into a track's lifetime vote
|
|
29277
29399
|
* tally. A non-positive score still counts as an infinitesimal vote so a
|
|
@@ -29385,20 +29507,25 @@ var SortTracker = class SortTracker {
|
|
|
29385
29507
|
for (let di = 0; di < detections.length; di++) {
|
|
29386
29508
|
if (used.has(di) || lowSet.has(di)) continue;
|
|
29387
29509
|
const det = detections[di];
|
|
29388
|
-
|
|
29510
|
+
const gate = this.looseMatch(track, det, timestamp);
|
|
29511
|
+
if (gate === null) continue;
|
|
29512
|
+
if (gate === "centroid" && this.isPlausibleBirth(det, frameArea)) continue;
|
|
29389
29513
|
rescuePairs.push({
|
|
29390
29514
|
track,
|
|
29391
29515
|
detIdx: di,
|
|
29392
|
-
score: iou$2(track.bbox, det.bbox)
|
|
29516
|
+
score: iou$2(track.bbox, det.bbox),
|
|
29517
|
+
gate
|
|
29393
29518
|
});
|
|
29394
29519
|
}
|
|
29395
29520
|
}
|
|
29396
29521
|
rescuePairs.sort((a, b) => b.score - a.score);
|
|
29397
29522
|
for (const pair of rescuePairs) {
|
|
29398
29523
|
if (matchedTracks.has(pair.track) || used.has(pair.detIdx)) continue;
|
|
29399
|
-
|
|
29524
|
+
const det = detections[pair.detIdx];
|
|
29525
|
+
matched.set(pair.track, det);
|
|
29400
29526
|
matchedTracks.add(pair.track);
|
|
29401
29527
|
used.add(pair.detIdx);
|
|
29528
|
+
this.reportBind("rescue", pair.gate, pair.track, det, timestamp);
|
|
29402
29529
|
}
|
|
29403
29530
|
if (this.config.byteTrackEnabled && lowIdxs.length > 0) this.greedyAssociate(detections, lowIdxs, this.config.byteTrackLowIouThreshold, used, matchedTracks, matched);
|
|
29404
29531
|
for (const [track, det] of matched) {
|
|
@@ -29452,17 +29579,22 @@ var SortTracker = class SortTracker {
|
|
|
29452
29579
|
const det = detections[di];
|
|
29453
29580
|
let best;
|
|
29454
29581
|
let bestScore = -1;
|
|
29582
|
+
let bestGate = "iou";
|
|
29455
29583
|
for (const lost of this.lostTracks) {
|
|
29456
29584
|
if (!lost.resurrectable) continue;
|
|
29457
29585
|
if (timestamp - lost.lostAt > this.config.resurrectionWindowMs) continue;
|
|
29458
|
-
|
|
29586
|
+
const gate = this.looseMatch(lost, det, timestamp);
|
|
29587
|
+
if (gate === null) continue;
|
|
29588
|
+
if (gate === "centroid" && this.isPlausibleBirth(det, frameArea)) continue;
|
|
29459
29589
|
const score = iou$2(lost.bbox, det.bbox);
|
|
29460
29590
|
if (score > bestScore) {
|
|
29461
29591
|
best = lost;
|
|
29462
29592
|
bestScore = score;
|
|
29593
|
+
bestGate = gate;
|
|
29463
29594
|
}
|
|
29464
29595
|
}
|
|
29465
29596
|
if (!best) continue;
|
|
29597
|
+
this.reportBind("resurrection", bestGate, best, det, timestamp);
|
|
29466
29598
|
this.lostTracks = this.lostTracks.filter((t) => t !== best);
|
|
29467
29599
|
best.bbox = det.bbox;
|
|
29468
29600
|
best.class = det.class;
|
|
@@ -29502,7 +29634,7 @@ var SortTracker = class SortTracker {
|
|
|
29502
29634
|
continue;
|
|
29503
29635
|
}
|
|
29504
29636
|
if (this.config.occlusionEnabled && occluderBoxes.some((ob) => containment(det.bbox, ob) >= this.config.occlusionContainment)) continue;
|
|
29505
|
-
if (this.config.dedupEnabled && this.isDuplicateSpawn(det, surviving)) {
|
|
29637
|
+
if (this.config.dedupEnabled && this.isDuplicateSpawn(det, surviving, timestamp)) {
|
|
29506
29638
|
used.add(di);
|
|
29507
29639
|
continue;
|
|
29508
29640
|
}
|
|
@@ -29561,6 +29693,7 @@ var SortTracker = class SortTracker {
|
|
|
29561
29693
|
this.tracks = [];
|
|
29562
29694
|
this.lostTracks = [];
|
|
29563
29695
|
this.dupStreaks.clear();
|
|
29696
|
+
this.bindSampler.reset();
|
|
29564
29697
|
}
|
|
29565
29698
|
};
|
|
29566
29699
|
//#endregion
|
|
@@ -29977,6 +30110,16 @@ var FrameProcessor = class {
|
|
|
29977
30110
|
setStationaryGate(gate) {
|
|
29978
30111
|
this.stationaryGate = gate;
|
|
29979
30112
|
}
|
|
30113
|
+
/**
|
|
30114
|
+
* Wire (or clear) the tracker's salvage-bind diagnostics sink. The tracker
|
|
30115
|
+
* itself knows no `deviceId`; the addon that owns this processor stamps
|
|
30116
|
+
* `tags: { deviceId }` on the line — see the wiring in `pipeline-analytics`.
|
|
30117
|
+
* Only the rescue / resurrection / growth branches report, and they report
|
|
30118
|
+
* sampled: stage 1 runs per track per frame at frame-rate.
|
|
30119
|
+
*/
|
|
30120
|
+
setBindObserver(observer) {
|
|
30121
|
+
this.tracker.setBindObserver(observer);
|
|
30122
|
+
}
|
|
29980
30123
|
/** Forget a track in the underlying tracker (used when the addon promotes it
|
|
29981
30124
|
* to a stationary-object registry entry). */
|
|
29982
30125
|
dropTrack(trackId) {
|
|
@@ -34936,6 +35079,70 @@ async function makeSquareThumb(bytes, size) {
|
|
|
34936
35079
|
}).jpeg({ quality: THUMB_QUALITY }).toBuffer();
|
|
34937
35080
|
}
|
|
34938
35081
|
//#endregion
|
|
35082
|
+
//#region src/pipeline-analytics/track-media-scope.ts
|
|
35083
|
+
/** The stored kind a detail owner's crop is written with. */
|
|
35084
|
+
var STORED_DETAIL_CROP_KIND = "crop";
|
|
35085
|
+
/** The kinds only a detail owner can produce. */
|
|
35086
|
+
var DETAIL_CROP_KINDS = ["faceCrop", "plateCrop"];
|
|
35087
|
+
/**
|
|
35088
|
+
* Every owner that holds media for `trackId`, in publication order (the track's
|
|
35089
|
+
* own rows first, then face, then plate).
|
|
35090
|
+
*/
|
|
35091
|
+
function trackMediaOwnerScopes(trackId) {
|
|
35092
|
+
return [
|
|
35093
|
+
{
|
|
35094
|
+
ownerKind: "track",
|
|
35095
|
+
ownerId: trackId,
|
|
35096
|
+
publishCropAs: null
|
|
35097
|
+
},
|
|
35098
|
+
{
|
|
35099
|
+
ownerKind: "face",
|
|
35100
|
+
ownerId: `${FACE_MEDIA_OWNER_PREFIX}${trackId}`,
|
|
35101
|
+
publishCropAs: "faceCrop"
|
|
35102
|
+
},
|
|
35103
|
+
{
|
|
35104
|
+
ownerKind: "plate",
|
|
35105
|
+
ownerId: `${PLATE_MEDIA_OWNER_PREFIX}${trackId}`,
|
|
35106
|
+
publishCropAs: "plateCrop"
|
|
35107
|
+
}
|
|
35108
|
+
];
|
|
35109
|
+
}
|
|
35110
|
+
/**
|
|
35111
|
+
* The scopes worth READING for a `kinds`-filtered request.
|
|
35112
|
+
*
|
|
35113
|
+
* A detail owner can only ever publish `faceCrop` / `plateCrop`, so a request
|
|
35114
|
+
* that names neither is answered without opening those two owners — which is
|
|
35115
|
+
* what keeps the common per-entry fetch (`kinds: ['keyFrameSmall']`) at exactly
|
|
35116
|
+
* its previous cost. The track scope is never skipped: it is the only owner
|
|
35117
|
+
* whose kinds are open-ended, and dropping it on a guess is how a listing
|
|
35118
|
+
* starts lying about what a track has.
|
|
35119
|
+
*/
|
|
35120
|
+
function trackMediaScopesForKinds(trackId, kinds) {
|
|
35121
|
+
const scopes = trackMediaOwnerScopes(trackId);
|
|
35122
|
+
if (kinds === void 0 || kinds.length === 0) return scopes;
|
|
35123
|
+
return kinds.some((kind) => DETAIL_CROP_KINDS.includes(kind)) ? scopes : scopes.filter((scope) => scope.publishCropAs === null);
|
|
35124
|
+
}
|
|
35125
|
+
/**
|
|
35126
|
+
* Fold every scope's rows into ONE track media listing: detail crops
|
|
35127
|
+
* relabelled, everything filtered to `kinds`, ordered oldest first.
|
|
35128
|
+
*
|
|
35129
|
+
* `relabel` is injected rather than spread inline so the caller keeps its own
|
|
35130
|
+
* concrete row type (`MediaFile` with bytes, `MediaFileInfo` without) — there
|
|
35131
|
+
* is no cast and no third shape in between.
|
|
35132
|
+
*/
|
|
35133
|
+
function collectTrackMedia(scoped, relabel, kinds) {
|
|
35134
|
+
const out = [];
|
|
35135
|
+
for (const { scope, rows } of scoped) for (const row of rows) {
|
|
35136
|
+
if (scope.publishCropAs === null) {
|
|
35137
|
+
out.push(row);
|
|
35138
|
+
continue;
|
|
35139
|
+
}
|
|
35140
|
+
if (row.kind !== STORED_DETAIL_CROP_KIND) continue;
|
|
35141
|
+
out.push(relabel(row, scope.publishCropAs));
|
|
35142
|
+
}
|
|
35143
|
+
return (kinds === void 0 || kinds.length === 0 ? out : out.filter((row) => kinds.includes(row.kind))).toSorted((a, b) => a.timestamp - b.timestamp);
|
|
35144
|
+
}
|
|
35145
|
+
//#endregion
|
|
34939
35146
|
//#region src/pipeline-analytics/zone-name-stamp.ts
|
|
34940
35147
|
/**
|
|
34941
35148
|
* Copy each track with `zoneNames` stamped on.
|
|
@@ -35055,10 +35262,25 @@ var AnalyticsQueryFacade = class {
|
|
|
35055
35262
|
const all = await (this.deps.mediaStore()?.listByOwner("event", input.eventId, input.deviceId) ?? Promise.resolve([]));
|
|
35056
35263
|
return input.kind ? all.filter((m) => m.kind === input.kind) : all;
|
|
35057
35264
|
}
|
|
35265
|
+
/**
|
|
35266
|
+
* A track's media WITH the bytes, restricted to `kinds` when given.
|
|
35267
|
+
*
|
|
35268
|
+
* Reads every owner a track's media lives under — its own rows plus the face
|
|
35269
|
+
* and plate detail crops, whose stored `kind: 'crop'` is published as
|
|
35270
|
+
* `faceCrop` / `plateCrop`. See `track-media-scope.ts` for why the rename
|
|
35271
|
+
* happens on the way out and not in the caller's question.
|
|
35272
|
+
*/
|
|
35058
35273
|
async getTrackMedia(input) {
|
|
35059
|
-
const
|
|
35060
|
-
|
|
35061
|
-
|
|
35274
|
+
const store = this.deps.mediaStore();
|
|
35275
|
+
if (store === null) return [];
|
|
35276
|
+
const scopes = trackMediaScopesForKinds(input.trackId, input.kinds);
|
|
35277
|
+
return collectTrackMedia(await Promise.all(scopes.map(async (scope) => ({
|
|
35278
|
+
scope,
|
|
35279
|
+
rows: await store.listByOwner(scope.ownerKind, scope.ownerId, input.deviceId)
|
|
35280
|
+
}))), (row, kind) => ({
|
|
35281
|
+
...row,
|
|
35282
|
+
kind
|
|
35283
|
+
}), input.kinds);
|
|
35062
35284
|
}
|
|
35063
35285
|
/**
|
|
35064
35286
|
* A track's media WITHOUT the bytes.
|
|
@@ -35068,9 +35290,21 @@ var AnalyticsQueryFacade = class {
|
|
|
35068
35290
|
* that path reads every blob off disk and base64-encodes it first, so the
|
|
35069
35291
|
* response would shrink while the work did not. Measured: 7.67 MB read and
|
|
35070
35292
|
* ~10 MB of string allocated, to answer a question the index already holds.
|
|
35293
|
+
*
|
|
35294
|
+
* Covers the same owners as {@link getTrackMedia} — a manifest that listed
|
|
35295
|
+
* fewer keys than the byte read can serve (or more) is the disagreement the
|
|
35296
|
+
* viewer's per-entry loader turns into a red triangle.
|
|
35071
35297
|
*/
|
|
35072
35298
|
async listTrackMedia(input) {
|
|
35073
|
-
|
|
35299
|
+
const store = this.deps.mediaStore();
|
|
35300
|
+
if (store === null) return [];
|
|
35301
|
+
return collectTrackMedia(await Promise.all(trackMediaOwnerScopes(input.trackId).map(async (scope) => ({
|
|
35302
|
+
scope,
|
|
35303
|
+
rows: await store.listInfoByOwner(scope.ownerKind, scope.ownerId, input.deviceId)
|
|
35304
|
+
}))), (row, kind) => ({
|
|
35305
|
+
...row,
|
|
35306
|
+
kind
|
|
35307
|
+
}), void 0);
|
|
35074
35308
|
}
|
|
35075
35309
|
async pruneEventsBefore(input) {
|
|
35076
35310
|
const eventStore = this.deps.eventStore();
|
|
@@ -64782,6 +65016,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
64782
65016
|
cooldownSec,
|
|
64783
65017
|
minTrackAgeMs: trk.minTrackAgeMs
|
|
64784
65018
|
}, source);
|
|
65019
|
+
p.setBindObserver((record) => {
|
|
65020
|
+
this.ctx.logger.debug("tracker: salvage bind", {
|
|
65021
|
+
tags: { deviceId },
|
|
65022
|
+
meta: {
|
|
65023
|
+
source,
|
|
65024
|
+
...record
|
|
65025
|
+
}
|
|
65026
|
+
});
|
|
65027
|
+
});
|
|
64785
65028
|
p.setTrackLabelState((trackId) => {
|
|
64786
65029
|
const active = this.trackStore?.getActiveByTrack(trackId);
|
|
64787
65030
|
return active !== null && active !== void 0 ? {
|