@camstack/addon-post-analysis 1.2.62 → 1.2.64
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-D4fGaA71.js → dist-BQ-M7jn9.js} +301 -36
- package/dist/{dist-CdqESw05.mjs → dist-CSTtfEOC.mjs} +301 -36
- 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-DrgptocJ.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-lSMqbbY-.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-DJwl-swi.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-EHTrRb5u.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-C8v4WVLy.mjs → hostInit-D5rpu5pY.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +281 -55
- package/dist/pipeline-analytics/index.mjs +281 -55
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as array, B as subKindsOf, C as addonWidgetsSourceCapability, D as cosineSimilarity, E as buildEventKindDescriptor, F as kebabToCamel, G as BaseAddon, H as videoclipsCapability, I as notificationRulesCapability, J as hydrateSchema, K as DeviceType, L as pipelineAnalyticsCapability, M as faceGalleryCapability, O as customAction, P as isScheduleActive, Q as _enum, R as plateGalleryCapability, S as TrackSourceSchema, T as audioMetricsCapability, U as zoneAnalyticsCapability, V as vectorDimFromBase64, W as errMsg, X as nodePin, Y as isDeviceScopedCap, Z as sleep, _ as NcTaxonomySchema, a as EVENT_PAD_MS, at as string, b as TimelapseRuleInputSchema, c as NC_CONDITION_CATALOG, d as NcRuleInputSchema, et as boolean, f as NcRulePatchSchema, g as NcSnoozeSuppressedSchema, h as NcSnoozeSchema, i as EVENT_KIND_BY_CAP, it as record, j as encodeVectorBase64, k as defineCustomActions, l as NC_TAXONOMY, m as NcSnoozeInputSchema, n as DEFAULT_EVENT_COLOR, nt as number, o as LabelAttributionSchema, ot as unknown, p as NcRuleSchema, q as createEvent, r as DeclaredDevices, rt as object, s as MACRO_LABELS, st as EventCategory, t as BaseDevice, tt as literal, u as NcConditionDescriptorSchema, v as OpsLogEntrySchema, w as alarmPanelCapability, x as TimelapseRuleSchema, y as RetrainStatusSchema, z as readDeviceStateFrom } from "../dist-
|
|
1
|
+
import { $ as array, B as subKindsOf, C as addonWidgetsSourceCapability, D as cosineSimilarity, E as buildEventKindDescriptor, F as kebabToCamel, G as BaseAddon, H as videoclipsCapability, I as notificationRulesCapability, J as hydrateSchema, K as DeviceType, L as pipelineAnalyticsCapability, M as faceGalleryCapability, O as customAction, P as isScheduleActive, Q as _enum, R as plateGalleryCapability, S as TrackSourceSchema, T as audioMetricsCapability, U as zoneAnalyticsCapability, V as vectorDimFromBase64, W as errMsg, X as nodePin, Y as isDeviceScopedCap, Z as sleep, _ as NcTaxonomySchema, a as EVENT_PAD_MS, at as string, b as TimelapseRuleInputSchema, c as NC_CONDITION_CATALOG, d as NcRuleInputSchema, et as boolean, f as NcRulePatchSchema, g as NcSnoozeSuppressedSchema, h as NcSnoozeSchema, i as EVENT_KIND_BY_CAP, it as record, j as encodeVectorBase64, k as defineCustomActions, l as NC_TAXONOMY, m as NcSnoozeInputSchema, n as DEFAULT_EVENT_COLOR, nt as number, o as LabelAttributionSchema, ot as unknown, p as NcRuleSchema, q as createEvent, r as DeclaredDevices, rt as object, s as MACRO_LABELS, st as EventCategory, t as BaseDevice, tt as literal, u as NcConditionDescriptorSchema, v as OpsLogEntrySchema, w as alarmPanelCapability, x as TimelapseRuleSchema, y as RetrainStatusSchema, z as readDeviceStateFrom } from "../dist-CSTtfEOC.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { promises } from "node:fs";
|
|
@@ -5589,6 +5589,12 @@ function frozenOccupancy(rule, occ) {
|
|
|
5589
5589
|
occupied: occ.occupied
|
|
5590
5590
|
};
|
|
5591
5591
|
}
|
|
5592
|
+
function notificationZones(rule, subject) {
|
|
5593
|
+
const configured = rule.conditions.zones?.ids;
|
|
5594
|
+
if (configured === void 0 || configured.length === 0) return subject.zones;
|
|
5595
|
+
const allowed = new Set(configured);
|
|
5596
|
+
return subject.zones.filter((zoneId) => allowed.has(zoneId));
|
|
5597
|
+
}
|
|
5592
5598
|
/** Classify an object-event row as a package delivery / pick-up, or `null` when
|
|
5593
5599
|
* it is an ordinary detection. */
|
|
5594
5600
|
function packagePhaseOf(ev) {
|
|
@@ -6561,6 +6567,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
6561
6567
|
const isTrackScoped = kind === "object-event" || kind === "track-end";
|
|
6562
6568
|
const isSystemEvent = kind === "system-event";
|
|
6563
6569
|
const direct = new Set(rule.targets.map((t) => t.targetId));
|
|
6570
|
+
const renderedZones = notificationZones(rule, subject);
|
|
6564
6571
|
return [...rule.targets, ...userTargets.filter((t) => !direct.has(t.targetId))].filter((target) => !rule.disabledTargetIds.includes(target.targetId)).map((target) => {
|
|
6565
6572
|
const payload = {
|
|
6566
6573
|
ruleName: rule.name,
|
|
@@ -6586,7 +6593,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
6586
6593
|
className: subject.classNames[0] ?? subject.sensorKind ?? "event",
|
|
6587
6594
|
...subject.label !== void 0 ? { label: subject.label } : {},
|
|
6588
6595
|
...subject.confidence !== void 0 ? { confidence: subject.confidence } : {},
|
|
6589
|
-
zones:
|
|
6596
|
+
zones: renderedZones,
|
|
6590
6597
|
timestamp: subject.timestamp,
|
|
6591
6598
|
...subject.occupancy !== void 0 ? { occupancy: frozenOccupancy(rule, subject.occupancy) } : {},
|
|
6592
6599
|
...subject.systemEvent !== void 0 ? { systemEvent: subject.systemEvent } : {}
|
|
@@ -15764,6 +15771,7 @@ function resolveFaceSettings(raw) {
|
|
|
15764
15771
|
*/
|
|
15765
15772
|
function createLocationAwareMediaStorage(deps) {
|
|
15766
15773
|
const roots = /* @__PURE__ */ new Map();
|
|
15774
|
+
let defaultBypassInvalidated = false;
|
|
15767
15775
|
const rootOf = async (locationId) => {
|
|
15768
15776
|
const cached = roots.get(locationId);
|
|
15769
15777
|
if (cached !== void 0) return cached;
|
|
@@ -15774,21 +15782,55 @@ function createLocationAwareMediaStorage(deps) {
|
|
|
15774
15782
|
const absOf = async (locationId, relativePath) => path.join(await rootOf(locationId), relativePath);
|
|
15775
15783
|
return {
|
|
15776
15784
|
write: async (input) => {
|
|
15777
|
-
if (input.location === deps.defaultLocation) return deps.base.write(input);
|
|
15785
|
+
if (input.location === deps.defaultLocation && !defaultBypassInvalidated) return deps.base.write(input);
|
|
15778
15786
|
const abs = await absOf(input.location, input.relativePath);
|
|
15779
15787
|
await promises.mkdir(path.dirname(abs), { recursive: true });
|
|
15780
15788
|
await promises.writeFile(abs, input.data);
|
|
15781
15789
|
},
|
|
15782
15790
|
read: async (input) => {
|
|
15783
|
-
if (input.location === deps.defaultLocation) return deps.base.read(input);
|
|
15791
|
+
if (input.location === deps.defaultLocation && !defaultBypassInvalidated) return deps.base.read(input);
|
|
15784
15792
|
return promises.readFile(await absOf(input.location, input.relativePath));
|
|
15785
15793
|
},
|
|
15786
15794
|
delete: async (input) => {
|
|
15787
|
-
if (input.location === deps.defaultLocation) return deps.base.delete(input);
|
|
15795
|
+
if (input.location === deps.defaultLocation && !defaultBypassInvalidated) return deps.base.delete(input);
|
|
15788
15796
|
await promises.rm(await absOf(input.location, input.relativePath), { force: true });
|
|
15797
|
+
},
|
|
15798
|
+
invalidateRoots: () => {
|
|
15799
|
+
roots.clear();
|
|
15800
|
+
defaultBypassInvalidated = true;
|
|
15789
15801
|
}
|
|
15790
15802
|
};
|
|
15791
15803
|
}
|
|
15804
|
+
//#endregion
|
|
15805
|
+
//#region src/pipeline-analytics/media-write-gate.ts
|
|
15806
|
+
/**
|
|
15807
|
+
* Reversible writer gate for storage maintenance. `pause()` closes admission
|
|
15808
|
+
* first, then waits for writes already admitted to settle; callers never
|
|
15809
|
+
* mutate device bindings or analytics configuration to quiesce media.
|
|
15810
|
+
*/
|
|
15811
|
+
var MediaWriteGate = class {
|
|
15812
|
+
paused = false;
|
|
15813
|
+
active = 0;
|
|
15814
|
+
drainedWaiters = [];
|
|
15815
|
+
async run(write) {
|
|
15816
|
+
if (this.paused) throw new Error("event-media writes are paused for storage migration");
|
|
15817
|
+
this.active++;
|
|
15818
|
+
try {
|
|
15819
|
+
return await write();
|
|
15820
|
+
} finally {
|
|
15821
|
+
this.active--;
|
|
15822
|
+
if (this.active === 0) for (const resolve of this.drainedWaiters.splice(0)) resolve();
|
|
15823
|
+
}
|
|
15824
|
+
}
|
|
15825
|
+
pause() {
|
|
15826
|
+
this.paused = true;
|
|
15827
|
+
if (this.active === 0) return Promise.resolve();
|
|
15828
|
+
return new Promise((resolve) => this.drainedWaiters.push(resolve));
|
|
15829
|
+
}
|
|
15830
|
+
resume() {
|
|
15831
|
+
this.paused = false;
|
|
15832
|
+
}
|
|
15833
|
+
};
|
|
15792
15834
|
var MACRO_SET = new Set([
|
|
15793
15835
|
"person",
|
|
15794
15836
|
"vehicle",
|
|
@@ -16659,7 +16701,14 @@ var RETRAIN_FRAME_COLUMNS = [
|
|
|
16659
16701
|
name: "copiedAt",
|
|
16660
16702
|
type: "INTEGER",
|
|
16661
16703
|
notNull: true
|
|
16662
|
-
}
|
|
16704
|
+
},
|
|
16705
|
+
(
|
|
16706
|
+
/** NULL = current eventMedia default; stamped when migration moves a copy
|
|
16707
|
+
* before the logical default is repointed. */
|
|
16708
|
+
{
|
|
16709
|
+
name: "locationId",
|
|
16710
|
+
type: "TEXT"
|
|
16711
|
+
})
|
|
16663
16712
|
];
|
|
16664
16713
|
var RETRAIN_FRAME_INDEXES = [{
|
|
16665
16714
|
name: "idx_retrain_frames_track",
|
|
@@ -16688,12 +16737,14 @@ var RetrainFrameStore = class {
|
|
|
16688
16737
|
logger;
|
|
16689
16738
|
readSource;
|
|
16690
16739
|
now;
|
|
16740
|
+
writeGate;
|
|
16691
16741
|
constructor(deps) {
|
|
16692
16742
|
this.store = deps.store;
|
|
16693
16743
|
this.storage = deps.storage;
|
|
16694
16744
|
this.logger = deps.logger;
|
|
16695
16745
|
this.readSource = deps.readSource;
|
|
16696
16746
|
this.now = deps.now;
|
|
16747
|
+
this.writeGate = deps.writeGate;
|
|
16697
16748
|
}
|
|
16698
16749
|
static async declare(store) {
|
|
16699
16750
|
await store.declareCollection.mutate({
|
|
@@ -16712,7 +16763,8 @@ var RetrainFrameStore = class {
|
|
|
16712
16763
|
sizeBytes: Number(data["sizeBytes"] ?? 0),
|
|
16713
16764
|
width: Number(data["width"] ?? 0),
|
|
16714
16765
|
height: Number(data["height"] ?? 0),
|
|
16715
|
-
copiedAt: Number(data["copiedAt"] ?? 0)
|
|
16766
|
+
copiedAt: Number(data["copiedAt"] ?? 0),
|
|
16767
|
+
locationId: typeof data["locationId"] === "string" && data["locationId"].length > 0 ? data["locationId"] : null
|
|
16716
16768
|
};
|
|
16717
16769
|
}
|
|
16718
16770
|
/** The copy with this id, or `null`. Index read only — no blob is touched. */
|
|
@@ -16780,7 +16832,7 @@ var RetrainFrameStore = class {
|
|
|
16780
16832
|
async readFrame(frame) {
|
|
16781
16833
|
try {
|
|
16782
16834
|
return await this.storage.read({
|
|
16783
|
-
location:
|
|
16835
|
+
location: frame.locationId ?? "eventMedia",
|
|
16784
16836
|
relativePath: framePath(frame.deviceId, frame.trackId, frame.frameId)
|
|
16785
16837
|
});
|
|
16786
16838
|
} catch (err) {
|
|
@@ -16860,9 +16912,10 @@ var RetrainFrameStore = class {
|
|
|
16860
16912
|
sizeBytes: bytes.length,
|
|
16861
16913
|
width: size.width,
|
|
16862
16914
|
height: size.height,
|
|
16863
|
-
copiedAt: this.now()
|
|
16915
|
+
copiedAt: this.now(),
|
|
16916
|
+
locationId: null
|
|
16864
16917
|
};
|
|
16865
|
-
|
|
16918
|
+
const persist = async () => {
|
|
16866
16919
|
await this.storage.write({
|
|
16867
16920
|
location: RETRAIN_MEDIA_LOCATION,
|
|
16868
16921
|
relativePath: path,
|
|
@@ -16880,9 +16933,14 @@ var RetrainFrameStore = class {
|
|
|
16880
16933
|
sizeBytes: frame.sizeBytes,
|
|
16881
16934
|
width: frame.width,
|
|
16882
16935
|
height: frame.height,
|
|
16883
|
-
copiedAt: frame.copiedAt
|
|
16936
|
+
copiedAt: frame.copiedAt,
|
|
16937
|
+
locationId: null
|
|
16884
16938
|
}
|
|
16885
16939
|
});
|
|
16940
|
+
};
|
|
16941
|
+
try {
|
|
16942
|
+
if (this.writeGate) await this.writeGate.run(persist);
|
|
16943
|
+
else await persist();
|
|
16886
16944
|
} catch (err) {
|
|
16887
16945
|
this.logger.error("retrain copy failed to persist", {
|
|
16888
16946
|
tags: { deviceId: input.deviceId },
|
|
@@ -16923,26 +16981,30 @@ var RetrainFrameStore = class {
|
|
|
16923
16981
|
* best-effort write is how a half-deleted annotation set happens.
|
|
16924
16982
|
*/
|
|
16925
16983
|
async deleteFrame(frame) {
|
|
16926
|
-
|
|
16927
|
-
|
|
16928
|
-
|
|
16929
|
-
|
|
16930
|
-
|
|
16931
|
-
|
|
16932
|
-
|
|
16933
|
-
|
|
16934
|
-
|
|
16935
|
-
|
|
16936
|
-
|
|
16937
|
-
|
|
16938
|
-
|
|
16939
|
-
|
|
16940
|
-
|
|
16941
|
-
|
|
16942
|
-
|
|
16943
|
-
|
|
16944
|
-
|
|
16945
|
-
|
|
16984
|
+
const remove = async () => {
|
|
16985
|
+
try {
|
|
16986
|
+
await this.storage.delete({
|
|
16987
|
+
location: frame.locationId ?? "eventMedia",
|
|
16988
|
+
relativePath: framePath(frame.deviceId, frame.trackId, frame.frameId)
|
|
16989
|
+
});
|
|
16990
|
+
} catch {}
|
|
16991
|
+
try {
|
|
16992
|
+
await this.store.delete.mutate({
|
|
16993
|
+
collection: RETRAIN_FRAMES_COLLECTION,
|
|
16994
|
+
key: frame.frameId
|
|
16995
|
+
});
|
|
16996
|
+
} catch (err) {
|
|
16997
|
+
this.logger.warn("RetrainFrameStore.deleteFrame row delete failed", {
|
|
16998
|
+
tags: { deviceId: frame.deviceId },
|
|
16999
|
+
meta: {
|
|
17000
|
+
frameId: frame.frameId,
|
|
17001
|
+
error: String(err)
|
|
17002
|
+
}
|
|
17003
|
+
});
|
|
17004
|
+
}
|
|
17005
|
+
};
|
|
17006
|
+
if (this.writeGate) await this.writeGate.run(remove);
|
|
17007
|
+
else await remove();
|
|
16946
17008
|
}
|
|
16947
17009
|
};
|
|
16948
17010
|
//#endregion
|
|
@@ -17981,10 +18043,15 @@ var MediaStore = class {
|
|
|
17981
18043
|
storage;
|
|
17982
18044
|
store;
|
|
17983
18045
|
logger;
|
|
18046
|
+
writeGate;
|
|
17984
18047
|
constructor(deps) {
|
|
17985
18048
|
this.storage = deps.storage;
|
|
17986
18049
|
this.store = deps.store;
|
|
17987
18050
|
this.logger = deps.logger;
|
|
18051
|
+
this.writeGate = deps.writeGate;
|
|
18052
|
+
}
|
|
18053
|
+
gated(mutation) {
|
|
18054
|
+
return this.writeGate ? this.writeGate.run(mutation) : mutation();
|
|
17988
18055
|
}
|
|
17989
18056
|
static async declare(store) {
|
|
17990
18057
|
await store.declareCollection.mutate({
|
|
@@ -17996,6 +18063,10 @@ var MediaStore = class {
|
|
|
17996
18063
|
/** Persist a media blob and register its metadata row. Returns the
|
|
17997
18064
|
* collection key (MediaFile.key) that callers store as reference. */
|
|
17998
18065
|
async put(params) {
|
|
18066
|
+
if (this.writeGate) return this.writeGate.run(() => this.putUnchecked(params));
|
|
18067
|
+
return this.putUnchecked(params);
|
|
18068
|
+
}
|
|
18069
|
+
async putUnchecked(params) {
|
|
17999
18070
|
const key = buildKey(params);
|
|
18000
18071
|
const path = buildPath(params);
|
|
18001
18072
|
const record = {
|
|
@@ -18066,6 +18137,9 @@ var MediaStore = class {
|
|
|
18066
18137
|
* scheme (transition back-compat); once migrated it is a no-op.
|
|
18067
18138
|
*/
|
|
18068
18139
|
async putReplacing(params) {
|
|
18140
|
+
return this.gated(() => this.putReplacingUnchecked(params));
|
|
18141
|
+
}
|
|
18142
|
+
async putReplacingUnchecked(params) {
|
|
18069
18143
|
const existing = await this.store.query.query({
|
|
18070
18144
|
collection: MEDIA_COLLECTION,
|
|
18071
18145
|
filter: { where: {
|
|
@@ -18074,7 +18148,7 @@ var MediaStore = class {
|
|
|
18074
18148
|
kind: params.kind
|
|
18075
18149
|
} }
|
|
18076
18150
|
});
|
|
18077
|
-
const newKey = await this.
|
|
18151
|
+
const newKey = await this.putUnchecked(params);
|
|
18078
18152
|
for (const row of existing) {
|
|
18079
18153
|
if (row.id === newKey) continue;
|
|
18080
18154
|
const rowData = row.data;
|
|
@@ -18117,8 +18191,11 @@ var MediaStore = class {
|
|
|
18117
18191
|
* own kind.
|
|
18118
18192
|
*/
|
|
18119
18193
|
async promoteToLastFrame(input) {
|
|
18194
|
+
return this.gated(() => this.promoteToLastFrameUnchecked(input));
|
|
18195
|
+
}
|
|
18196
|
+
async promoteToLastFrameUnchecked(input) {
|
|
18120
18197
|
const data = Buffer.from(input.snapshot.base64, "base64");
|
|
18121
|
-
const newKey = await this.
|
|
18198
|
+
const newKey = await this.putReplacingUnchecked({
|
|
18122
18199
|
deviceId: input.deviceId,
|
|
18123
18200
|
ownerKind: "track",
|
|
18124
18201
|
ownerId: input.trackId,
|
|
@@ -18126,7 +18203,7 @@ var MediaStore = class {
|
|
|
18126
18203
|
timestamp: input.snapshot.timestamp,
|
|
18127
18204
|
data
|
|
18128
18205
|
});
|
|
18129
|
-
if (input.keepSource !== true) await this.
|
|
18206
|
+
if (input.keepSource !== true) await this.deleteByKeyUnchecked(input.snapshot.key);
|
|
18130
18207
|
return newKey;
|
|
18131
18208
|
}
|
|
18132
18209
|
/**
|
|
@@ -18164,6 +18241,9 @@ var MediaStore = class {
|
|
|
18164
18241
|
* Deletes the blob from storage (best-effort) and removes the index row.
|
|
18165
18242
|
*/
|
|
18166
18243
|
async deleteByKey(key) {
|
|
18244
|
+
await this.gated(() => this.deleteByKeyUnchecked(key));
|
|
18245
|
+
}
|
|
18246
|
+
async deleteByKeyUnchecked(key) {
|
|
18167
18247
|
const row = await this.store.get.query({
|
|
18168
18248
|
collection: MEDIA_COLLECTION,
|
|
18169
18249
|
key
|
|
@@ -18274,6 +18354,9 @@ var MediaStore = class {
|
|
|
18274
18354
|
* Best-effort per row; returns the number of index rows removed.
|
|
18275
18355
|
*/
|
|
18276
18356
|
async deleteForOwner(ownerKind, ownerIds) {
|
|
18357
|
+
return this.gated(() => this.deleteForOwnerUnchecked(ownerKind, ownerIds));
|
|
18358
|
+
}
|
|
18359
|
+
async deleteForOwnerUnchecked(ownerKind, ownerIds) {
|
|
18277
18360
|
let removed = 0;
|
|
18278
18361
|
for (const ownerId of ownerIds) {
|
|
18279
18362
|
const rows = await this.store.query.query({
|
|
@@ -18355,6 +18438,9 @@ var MediaStore = class {
|
|
|
18355
18438
|
* The old-entry deletion is best-effort: on failure a warning is logged
|
|
18356
18439
|
* and the method still returns the new key. */
|
|
18357
18440
|
async reown(input) {
|
|
18441
|
+
return this.gated(() => this.reownUnchecked(input));
|
|
18442
|
+
}
|
|
18443
|
+
async reownUnchecked(input) {
|
|
18358
18444
|
const { fromOwnerKind, fromOwnerId, toOwnerKind, toOwnerId, key } = input;
|
|
18359
18445
|
const row = (await this.store.query.query({
|
|
18360
18446
|
collection: MEDIA_COLLECTION,
|
|
@@ -18388,7 +18474,7 @@ var MediaStore = class {
|
|
|
18388
18474
|
relativePath: oldPath
|
|
18389
18475
|
})
|
|
18390
18476
|
};
|
|
18391
|
-
const newKey = await this.
|
|
18477
|
+
const newKey = await this.putUnchecked(newParams);
|
|
18392
18478
|
try {
|
|
18393
18479
|
await this.storage.delete({
|
|
18394
18480
|
location: mediaRowLocation(meta),
|
|
@@ -18442,8 +18528,10 @@ var MediaStore = class {
|
|
|
18442
18528
|
* batch.
|
|
18443
18529
|
*/
|
|
18444
18530
|
async deleteByTracks(trackIds) {
|
|
18445
|
-
await this.
|
|
18446
|
-
|
|
18531
|
+
await this.gated(async () => {
|
|
18532
|
+
await this.deleteForOwnerUnchecked("track", trackIds);
|
|
18533
|
+
await this.deleteForOwnerUnchecked("face", trackIds.map((id) => FACE_MEDIA_OWNER_PREFIX + id));
|
|
18534
|
+
});
|
|
18447
18535
|
await this.deleteForOwner("plate", trackIds.map((id) => PLATE_MEDIA_OWNER_PREFIX + id));
|
|
18448
18536
|
}
|
|
18449
18537
|
/**
|
|
@@ -18485,7 +18573,7 @@ function snapshot(j) {
|
|
|
18485
18573
|
fromLocationId: j.fromLocationId,
|
|
18486
18574
|
toLocationId: j.toLocationId,
|
|
18487
18575
|
deviceId: j.deviceId,
|
|
18488
|
-
entities: ["media"],
|
|
18576
|
+
entities: ["media", "retrainFrames"],
|
|
18489
18577
|
filesMoved: j.filesMoved,
|
|
18490
18578
|
bytesMoved: j.bytesMoved,
|
|
18491
18579
|
filesTotal: null,
|
|
@@ -18516,7 +18604,7 @@ var MediaRelocateEngine = class {
|
|
|
18516
18604
|
state: "running",
|
|
18517
18605
|
fromLocationId: "*",
|
|
18518
18606
|
toLocationId: input.toLocationId,
|
|
18519
|
-
deviceId:
|
|
18607
|
+
deviceId: null,
|
|
18520
18608
|
filesMoved: 0,
|
|
18521
18609
|
bytesMoved: 0,
|
|
18522
18610
|
startedAt: this.deps.now(),
|
|
@@ -18533,24 +18621,25 @@ var MediaRelocateEngine = class {
|
|
|
18533
18621
|
const bytesPerMs = throttleMbps * 1024 * 1024 / 1e3;
|
|
18534
18622
|
try {
|
|
18535
18623
|
await this.deps.resolveTargetRoot(job.toLocationId);
|
|
18624
|
+
let failedRows = 0;
|
|
18536
18625
|
let cursor = 0;
|
|
18537
|
-
let
|
|
18626
|
+
let offsetAtCursor = 0;
|
|
18538
18627
|
for (;;) {
|
|
18539
18628
|
if (job.cancelRequested) break;
|
|
18540
|
-
const
|
|
18629
|
+
const rows = await this.deps.store.query.query({
|
|
18541
18630
|
collection: MEDIA_COLLECTION,
|
|
18542
18631
|
filter: {
|
|
18543
|
-
...job.deviceId !== null ? { where: { deviceId: job.deviceId } } : {},
|
|
18544
18632
|
whereBetween: { timestamp: [cursor, Number.MAX_SAFE_INTEGER] },
|
|
18545
18633
|
orderBy: {
|
|
18546
18634
|
field: "timestamp",
|
|
18547
18635
|
direction: "asc"
|
|
18548
18636
|
},
|
|
18549
|
-
limit: PAGE_SIZE
|
|
18637
|
+
limit: PAGE_SIZE,
|
|
18638
|
+
offset: offsetAtCursor
|
|
18550
18639
|
}
|
|
18551
|
-
})
|
|
18552
|
-
if (
|
|
18553
|
-
for (const row of
|
|
18640
|
+
});
|
|
18641
|
+
if (rows.length === 0) break;
|
|
18642
|
+
for (const row of rows) {
|
|
18554
18643
|
if (job.cancelRequested) break;
|
|
18555
18644
|
const data = row.data;
|
|
18556
18645
|
const from = mediaRowLocation(data);
|
|
@@ -18558,6 +18647,17 @@ var MediaRelocateEngine = class {
|
|
|
18558
18647
|
const relativePath = String(data["path"] ?? "");
|
|
18559
18648
|
if (relativePath.length === 0) continue;
|
|
18560
18649
|
try {
|
|
18650
|
+
if (await this.samePhysicalLocation(from, job.toLocationId)) {
|
|
18651
|
+
await this.deps.store.set.mutate({
|
|
18652
|
+
collection: MEDIA_COLLECTION,
|
|
18653
|
+
key: row.id,
|
|
18654
|
+
value: {
|
|
18655
|
+
...data,
|
|
18656
|
+
locationId: job.toLocationId
|
|
18657
|
+
}
|
|
18658
|
+
});
|
|
18659
|
+
continue;
|
|
18660
|
+
}
|
|
18561
18661
|
const bytes = await this.deps.storage.read({
|
|
18562
18662
|
location: from,
|
|
18563
18663
|
relativePath
|
|
@@ -18567,6 +18667,10 @@ var MediaRelocateEngine = class {
|
|
|
18567
18667
|
relativePath,
|
|
18568
18668
|
data: bytes
|
|
18569
18669
|
});
|
|
18670
|
+
if ((await this.deps.storage.read({
|
|
18671
|
+
location: job.toLocationId,
|
|
18672
|
+
relativePath
|
|
18673
|
+
})).length !== bytes.length) throw new Error(`relocated media size mismatch for ${relativePath}`);
|
|
18570
18674
|
await this.deps.store.set.mutate({
|
|
18571
18675
|
collection: MEDIA_COLLECTION,
|
|
18572
18676
|
key: row.id,
|
|
@@ -18583,20 +18687,99 @@ var MediaRelocateEngine = class {
|
|
|
18583
18687
|
job.bytesMoved += bytes.length;
|
|
18584
18688
|
await sleep(bytes.length / bytesPerMs);
|
|
18585
18689
|
} catch (err) {
|
|
18690
|
+
failedRows++;
|
|
18586
18691
|
this.deps.logger.debug("media relocate row failed", { meta: {
|
|
18587
18692
|
key: row.id,
|
|
18588
18693
|
error: String(err)
|
|
18589
18694
|
} });
|
|
18590
18695
|
}
|
|
18591
18696
|
}
|
|
18592
|
-
const last =
|
|
18697
|
+
const last = rows[rows.length - 1];
|
|
18593
18698
|
const lastTs = Number(last.data["timestamp"] ?? cursor);
|
|
18594
|
-
if (lastTs === cursor)
|
|
18699
|
+
if (lastTs === cursor) offsetAtCursor += rows.length;
|
|
18595
18700
|
else {
|
|
18596
18701
|
cursor = lastTs;
|
|
18597
|
-
|
|
18702
|
+
offsetAtCursor = rows.filter((row) => Number(row.data["timestamp"]) === lastTs).length;
|
|
18598
18703
|
}
|
|
18704
|
+
if (rows.length < PAGE_SIZE) break;
|
|
18599
18705
|
}
|
|
18706
|
+
let retrainOffset = 0;
|
|
18707
|
+
for (;;) {
|
|
18708
|
+
if (job.cancelRequested) break;
|
|
18709
|
+
const rows = await this.deps.store.query.query({
|
|
18710
|
+
collection: RETRAIN_FRAMES_COLLECTION,
|
|
18711
|
+
filter: {
|
|
18712
|
+
orderBy: {
|
|
18713
|
+
field: "copiedAt",
|
|
18714
|
+
direction: "asc"
|
|
18715
|
+
},
|
|
18716
|
+
limit: PAGE_SIZE,
|
|
18717
|
+
offset: retrainOffset
|
|
18718
|
+
}
|
|
18719
|
+
});
|
|
18720
|
+
if (rows.length === 0) break;
|
|
18721
|
+
for (const row of rows) {
|
|
18722
|
+
if (job.cancelRequested) break;
|
|
18723
|
+
const data = row.data;
|
|
18724
|
+
const from = mediaRowLocation(data);
|
|
18725
|
+
if (from === job.toLocationId) continue;
|
|
18726
|
+
const relativePath = String(data["path"] ?? "");
|
|
18727
|
+
if (relativePath.length === 0) {
|
|
18728
|
+
failedRows++;
|
|
18729
|
+
continue;
|
|
18730
|
+
}
|
|
18731
|
+
try {
|
|
18732
|
+
if (await this.samePhysicalLocation(from, job.toLocationId)) {
|
|
18733
|
+
await this.deps.store.set.mutate({
|
|
18734
|
+
collection: RETRAIN_FRAMES_COLLECTION,
|
|
18735
|
+
key: row.id,
|
|
18736
|
+
value: {
|
|
18737
|
+
...data,
|
|
18738
|
+
locationId: job.toLocationId
|
|
18739
|
+
}
|
|
18740
|
+
});
|
|
18741
|
+
continue;
|
|
18742
|
+
}
|
|
18743
|
+
const bytes = await this.deps.storage.read({
|
|
18744
|
+
location: from,
|
|
18745
|
+
relativePath
|
|
18746
|
+
});
|
|
18747
|
+
await this.deps.storage.write({
|
|
18748
|
+
location: job.toLocationId,
|
|
18749
|
+
relativePath,
|
|
18750
|
+
data: bytes
|
|
18751
|
+
});
|
|
18752
|
+
if ((await this.deps.storage.read({
|
|
18753
|
+
location: job.toLocationId,
|
|
18754
|
+
relativePath
|
|
18755
|
+
})).length !== bytes.length) throw new Error(`relocated retrain frame size mismatch for ${relativePath}`);
|
|
18756
|
+
await this.deps.store.set.mutate({
|
|
18757
|
+
collection: RETRAIN_FRAMES_COLLECTION,
|
|
18758
|
+
key: row.id,
|
|
18759
|
+
value: {
|
|
18760
|
+
...data,
|
|
18761
|
+
locationId: job.toLocationId
|
|
18762
|
+
}
|
|
18763
|
+
});
|
|
18764
|
+
await this.deps.storage.delete({
|
|
18765
|
+
location: from,
|
|
18766
|
+
relativePath
|
|
18767
|
+
});
|
|
18768
|
+
job.filesMoved++;
|
|
18769
|
+
job.bytesMoved += bytes.length;
|
|
18770
|
+
await sleep(bytes.length / bytesPerMs);
|
|
18771
|
+
} catch (err) {
|
|
18772
|
+
failedRows++;
|
|
18773
|
+
this.deps.logger.debug("retrain frame relocate row failed", { meta: {
|
|
18774
|
+
key: row.id,
|
|
18775
|
+
error: String(err)
|
|
18776
|
+
} });
|
|
18777
|
+
}
|
|
18778
|
+
}
|
|
18779
|
+
retrainOffset += rows.length;
|
|
18780
|
+
if (rows.length < PAGE_SIZE) break;
|
|
18781
|
+
}
|
|
18782
|
+
if (failedRows > 0) throw new Error(`event-media relocation left ${failedRows} row(s) unmoved`);
|
|
18600
18783
|
job.state = job.cancelRequested ? "cancelled" : "done";
|
|
18601
18784
|
} catch (err) {
|
|
18602
18785
|
job.state = "failed";
|
|
@@ -18610,6 +18793,18 @@ var MediaRelocateEngine = class {
|
|
|
18610
18793
|
this.deps.onFinished?.(snapshot(job));
|
|
18611
18794
|
}
|
|
18612
18795
|
}
|
|
18796
|
+
async samePhysicalLocation(from, to) {
|
|
18797
|
+
if (from === to) return true;
|
|
18798
|
+
try {
|
|
18799
|
+
const [fromRoot, toRoot] = await Promise.all([this.deps.resolveTargetRoot(from), this.deps.resolveTargetRoot(to)]);
|
|
18800
|
+
const [fromStat, toStat] = await Promise.all([promises.stat(fromRoot), promises.stat(toRoot)]);
|
|
18801
|
+
if (fromStat.dev === toStat.dev && fromStat.ino === toStat.ino) return true;
|
|
18802
|
+
const [fromReal, toReal] = await Promise.all([promises.realpath(path.resolve(fromRoot)), promises.realpath(path.resolve(toRoot))]);
|
|
18803
|
+
return fromReal === toReal;
|
|
18804
|
+
} catch {
|
|
18805
|
+
return false;
|
|
18806
|
+
}
|
|
18807
|
+
}
|
|
18613
18808
|
};
|
|
18614
18809
|
//#endregion
|
|
18615
18810
|
//#region src/pipeline-analytics/media-settings.ts
|
|
@@ -31162,6 +31357,9 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
31162
31357
|
eventsOpsLog = null;
|
|
31163
31358
|
/** Event-media relocation engine (entity-routing Phase 4). */
|
|
31164
31359
|
mediaRelocate = null;
|
|
31360
|
+
mediaLocationStorage = null;
|
|
31361
|
+
mediaWriteGate = new MediaWriteGate();
|
|
31362
|
+
storageMigrationLeaseId = null;
|
|
31165
31363
|
/** Per-camera history of LINKED-device sensor state changes (Part B). */
|
|
31166
31364
|
sensorEventStore = null;
|
|
31167
31365
|
/** Ingest-side reverse index (sensor device → linked camera ids), TTL-cached
|
|
@@ -31962,7 +32160,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
31962
32160
|
logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
|
|
31963
32161
|
}
|
|
31964
32162
|
if (!storage) throw new Error("pipeline-analytics requires ctx.kernel.storage");
|
|
31965
|
-
|
|
32163
|
+
const routed = createLocationAwareMediaStorage({
|
|
31966
32164
|
base: storage,
|
|
31967
32165
|
defaultLocation: "eventMedia",
|
|
31968
32166
|
resolveRoot: (locationId) => this.ctx.api.storage.resolve.query({
|
|
@@ -31970,6 +32168,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
31970
32168
|
relativePath: ""
|
|
31971
32169
|
})
|
|
31972
32170
|
});
|
|
32171
|
+
this.mediaLocationStorage = routed;
|
|
32172
|
+
return routed;
|
|
31973
32173
|
}
|
|
31974
32174
|
/** Constructs every SQLite-backed store plus the stationary/package-drop/
|
|
31975
32175
|
* sensor plumbing, in the exact pre-S7 order. Returns the non-null bundle
|
|
@@ -32014,7 +32214,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
32014
32214
|
const mediaStore = new MediaStore({
|
|
32015
32215
|
storage,
|
|
32016
32216
|
store: api.settingsStore,
|
|
32017
|
-
logger: logger.child("MediaStore")
|
|
32217
|
+
logger: logger.child("MediaStore"),
|
|
32218
|
+
writeGate: this.mediaWriteGate
|
|
32018
32219
|
});
|
|
32019
32220
|
this.mediaStore = mediaStore;
|
|
32020
32221
|
const retrainFrames = new RetrainFrameStore({
|
|
@@ -32022,6 +32223,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
32022
32223
|
storage,
|
|
32023
32224
|
logger: logger.child("RetrainFrames"),
|
|
32024
32225
|
now: () => Date.now(),
|
|
32226
|
+
writeGate: this.mediaWriteGate,
|
|
32025
32227
|
readSource: async (mediaKey) => {
|
|
32026
32228
|
const file = await mediaStore.getByKey(mediaKey);
|
|
32027
32229
|
return file === null ? null : Buffer.from(file.base64, "base64");
|
|
@@ -36263,14 +36465,38 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
36263
36465
|
return this.queryFacade.deleteDeviceEvents(input);
|
|
36264
36466
|
}
|
|
36265
36467
|
/** The events ops-log rows (newest-first), optionally scoped to one camera. */
|
|
36266
|
-
async
|
|
36468
|
+
async pauseForStorageMigration(input) {
|
|
36469
|
+
if (this.storageMigrationLeaseId !== null && this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media writes are already paused by another storage migration");
|
|
36470
|
+
this.storageMigrationLeaseId = input.leaseId;
|
|
36471
|
+
try {
|
|
36472
|
+
await this.mediaWriteGate.pause();
|
|
36473
|
+
return { paused: true };
|
|
36474
|
+
} catch (err) {
|
|
36475
|
+
this.storageMigrationLeaseId = null;
|
|
36476
|
+
throw err;
|
|
36477
|
+
}
|
|
36478
|
+
}
|
|
36479
|
+
async resumeForStorageMigration(input) {
|
|
36480
|
+
if (this.storageMigrationLeaseId === null) return { resumed: true };
|
|
36481
|
+
if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
|
|
36482
|
+
this.mediaWriteGate.resume();
|
|
36483
|
+
this.storageMigrationLeaseId = null;
|
|
36484
|
+
return { resumed: true };
|
|
36485
|
+
}
|
|
36486
|
+
async refreshStorageLocationsForMigration(input) {
|
|
36487
|
+
if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
|
|
36488
|
+
this.mediaLocationStorage?.invalidateRoots();
|
|
36489
|
+
return { refreshed: true };
|
|
36490
|
+
}
|
|
36491
|
+
async startStorageMigrationMove(input) {
|
|
36267
36492
|
if (!this.mediaRelocate) throw new Error("media relocation unavailable");
|
|
36493
|
+
if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
|
|
36268
36494
|
return { jobId: this.mediaRelocate.start(input) };
|
|
36269
36495
|
}
|
|
36270
|
-
async
|
|
36271
|
-
return this.mediaRelocate?.list() ??
|
|
36496
|
+
async getStorageMigrationMoveStatus(input) {
|
|
36497
|
+
return this.mediaRelocate?.list().find((job) => job.jobId === input.jobId) ?? null;
|
|
36272
36498
|
}
|
|
36273
|
-
async
|
|
36499
|
+
async cancelStorageMigrationMove(input) {
|
|
36274
36500
|
return { cancelled: this.mediaRelocate?.cancel(input.jobId) ?? false };
|
|
36275
36501
|
}
|
|
36276
36502
|
async listOpsLog(input) {
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-lSMqbbY-.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-post-analysis",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.64",
|
|
4
4
|
"description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
{
|
|
100
100
|
"id": "eventMedia",
|
|
101
101
|
"displayName": "Event Media",
|
|
102
|
-
"cardinality": "
|
|
102
|
+
"cardinality": "multi",
|
|
103
103
|
"description": "Detection snapshots, thumbnails and crops.",
|
|
104
104
|
"defaultsTo": "recordings"
|
|
105
105
|
}
|