@camstack/addon-post-analysis 1.2.63 → 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 +273 -54
- package/dist/pipeline-analytics/index.mjs +273 -54
- 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";
|
|
@@ -15771,6 +15771,7 @@ function resolveFaceSettings(raw) {
|
|
|
15771
15771
|
*/
|
|
15772
15772
|
function createLocationAwareMediaStorage(deps) {
|
|
15773
15773
|
const roots = /* @__PURE__ */ new Map();
|
|
15774
|
+
let defaultBypassInvalidated = false;
|
|
15774
15775
|
const rootOf = async (locationId) => {
|
|
15775
15776
|
const cached = roots.get(locationId);
|
|
15776
15777
|
if (cached !== void 0) return cached;
|
|
@@ -15781,21 +15782,55 @@ function createLocationAwareMediaStorage(deps) {
|
|
|
15781
15782
|
const absOf = async (locationId, relativePath) => path.join(await rootOf(locationId), relativePath);
|
|
15782
15783
|
return {
|
|
15783
15784
|
write: async (input) => {
|
|
15784
|
-
if (input.location === deps.defaultLocation) return deps.base.write(input);
|
|
15785
|
+
if (input.location === deps.defaultLocation && !defaultBypassInvalidated) return deps.base.write(input);
|
|
15785
15786
|
const abs = await absOf(input.location, input.relativePath);
|
|
15786
15787
|
await promises.mkdir(path.dirname(abs), { recursive: true });
|
|
15787
15788
|
await promises.writeFile(abs, input.data);
|
|
15788
15789
|
},
|
|
15789
15790
|
read: async (input) => {
|
|
15790
|
-
if (input.location === deps.defaultLocation) return deps.base.read(input);
|
|
15791
|
+
if (input.location === deps.defaultLocation && !defaultBypassInvalidated) return deps.base.read(input);
|
|
15791
15792
|
return promises.readFile(await absOf(input.location, input.relativePath));
|
|
15792
15793
|
},
|
|
15793
15794
|
delete: async (input) => {
|
|
15794
|
-
if (input.location === deps.defaultLocation) return deps.base.delete(input);
|
|
15795
|
+
if (input.location === deps.defaultLocation && !defaultBypassInvalidated) return deps.base.delete(input);
|
|
15795
15796
|
await promises.rm(await absOf(input.location, input.relativePath), { force: true });
|
|
15797
|
+
},
|
|
15798
|
+
invalidateRoots: () => {
|
|
15799
|
+
roots.clear();
|
|
15800
|
+
defaultBypassInvalidated = true;
|
|
15796
15801
|
}
|
|
15797
15802
|
};
|
|
15798
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
|
+
};
|
|
15799
15834
|
var MACRO_SET = new Set([
|
|
15800
15835
|
"person",
|
|
15801
15836
|
"vehicle",
|
|
@@ -16666,7 +16701,14 @@ var RETRAIN_FRAME_COLUMNS = [
|
|
|
16666
16701
|
name: "copiedAt",
|
|
16667
16702
|
type: "INTEGER",
|
|
16668
16703
|
notNull: true
|
|
16669
|
-
}
|
|
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
|
+
})
|
|
16670
16712
|
];
|
|
16671
16713
|
var RETRAIN_FRAME_INDEXES = [{
|
|
16672
16714
|
name: "idx_retrain_frames_track",
|
|
@@ -16695,12 +16737,14 @@ var RetrainFrameStore = class {
|
|
|
16695
16737
|
logger;
|
|
16696
16738
|
readSource;
|
|
16697
16739
|
now;
|
|
16740
|
+
writeGate;
|
|
16698
16741
|
constructor(deps) {
|
|
16699
16742
|
this.store = deps.store;
|
|
16700
16743
|
this.storage = deps.storage;
|
|
16701
16744
|
this.logger = deps.logger;
|
|
16702
16745
|
this.readSource = deps.readSource;
|
|
16703
16746
|
this.now = deps.now;
|
|
16747
|
+
this.writeGate = deps.writeGate;
|
|
16704
16748
|
}
|
|
16705
16749
|
static async declare(store) {
|
|
16706
16750
|
await store.declareCollection.mutate({
|
|
@@ -16719,7 +16763,8 @@ var RetrainFrameStore = class {
|
|
|
16719
16763
|
sizeBytes: Number(data["sizeBytes"] ?? 0),
|
|
16720
16764
|
width: Number(data["width"] ?? 0),
|
|
16721
16765
|
height: Number(data["height"] ?? 0),
|
|
16722
|
-
copiedAt: Number(data["copiedAt"] ?? 0)
|
|
16766
|
+
copiedAt: Number(data["copiedAt"] ?? 0),
|
|
16767
|
+
locationId: typeof data["locationId"] === "string" && data["locationId"].length > 0 ? data["locationId"] : null
|
|
16723
16768
|
};
|
|
16724
16769
|
}
|
|
16725
16770
|
/** The copy with this id, or `null`. Index read only — no blob is touched. */
|
|
@@ -16787,7 +16832,7 @@ var RetrainFrameStore = class {
|
|
|
16787
16832
|
async readFrame(frame) {
|
|
16788
16833
|
try {
|
|
16789
16834
|
return await this.storage.read({
|
|
16790
|
-
location:
|
|
16835
|
+
location: frame.locationId ?? "eventMedia",
|
|
16791
16836
|
relativePath: framePath(frame.deviceId, frame.trackId, frame.frameId)
|
|
16792
16837
|
});
|
|
16793
16838
|
} catch (err) {
|
|
@@ -16867,9 +16912,10 @@ var RetrainFrameStore = class {
|
|
|
16867
16912
|
sizeBytes: bytes.length,
|
|
16868
16913
|
width: size.width,
|
|
16869
16914
|
height: size.height,
|
|
16870
|
-
copiedAt: this.now()
|
|
16915
|
+
copiedAt: this.now(),
|
|
16916
|
+
locationId: null
|
|
16871
16917
|
};
|
|
16872
|
-
|
|
16918
|
+
const persist = async () => {
|
|
16873
16919
|
await this.storage.write({
|
|
16874
16920
|
location: RETRAIN_MEDIA_LOCATION,
|
|
16875
16921
|
relativePath: path,
|
|
@@ -16887,9 +16933,14 @@ var RetrainFrameStore = class {
|
|
|
16887
16933
|
sizeBytes: frame.sizeBytes,
|
|
16888
16934
|
width: frame.width,
|
|
16889
16935
|
height: frame.height,
|
|
16890
|
-
copiedAt: frame.copiedAt
|
|
16936
|
+
copiedAt: frame.copiedAt,
|
|
16937
|
+
locationId: null
|
|
16891
16938
|
}
|
|
16892
16939
|
});
|
|
16940
|
+
};
|
|
16941
|
+
try {
|
|
16942
|
+
if (this.writeGate) await this.writeGate.run(persist);
|
|
16943
|
+
else await persist();
|
|
16893
16944
|
} catch (err) {
|
|
16894
16945
|
this.logger.error("retrain copy failed to persist", {
|
|
16895
16946
|
tags: { deviceId: input.deviceId },
|
|
@@ -16930,26 +16981,30 @@ var RetrainFrameStore = class {
|
|
|
16930
16981
|
* best-effort write is how a half-deleted annotation set happens.
|
|
16931
16982
|
*/
|
|
16932
16983
|
async deleteFrame(frame) {
|
|
16933
|
-
|
|
16934
|
-
|
|
16935
|
-
|
|
16936
|
-
|
|
16937
|
-
|
|
16938
|
-
|
|
16939
|
-
|
|
16940
|
-
|
|
16941
|
-
|
|
16942
|
-
|
|
16943
|
-
|
|
16944
|
-
|
|
16945
|
-
|
|
16946
|
-
|
|
16947
|
-
|
|
16948
|
-
|
|
16949
|
-
|
|
16950
|
-
|
|
16951
|
-
|
|
16952
|
-
|
|
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();
|
|
16953
17008
|
}
|
|
16954
17009
|
};
|
|
16955
17010
|
//#endregion
|
|
@@ -17988,10 +18043,15 @@ var MediaStore = class {
|
|
|
17988
18043
|
storage;
|
|
17989
18044
|
store;
|
|
17990
18045
|
logger;
|
|
18046
|
+
writeGate;
|
|
17991
18047
|
constructor(deps) {
|
|
17992
18048
|
this.storage = deps.storage;
|
|
17993
18049
|
this.store = deps.store;
|
|
17994
18050
|
this.logger = deps.logger;
|
|
18051
|
+
this.writeGate = deps.writeGate;
|
|
18052
|
+
}
|
|
18053
|
+
gated(mutation) {
|
|
18054
|
+
return this.writeGate ? this.writeGate.run(mutation) : mutation();
|
|
17995
18055
|
}
|
|
17996
18056
|
static async declare(store) {
|
|
17997
18057
|
await store.declareCollection.mutate({
|
|
@@ -18003,6 +18063,10 @@ var MediaStore = class {
|
|
|
18003
18063
|
/** Persist a media blob and register its metadata row. Returns the
|
|
18004
18064
|
* collection key (MediaFile.key) that callers store as reference. */
|
|
18005
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) {
|
|
18006
18070
|
const key = buildKey(params);
|
|
18007
18071
|
const path = buildPath(params);
|
|
18008
18072
|
const record = {
|
|
@@ -18073,6 +18137,9 @@ var MediaStore = class {
|
|
|
18073
18137
|
* scheme (transition back-compat); once migrated it is a no-op.
|
|
18074
18138
|
*/
|
|
18075
18139
|
async putReplacing(params) {
|
|
18140
|
+
return this.gated(() => this.putReplacingUnchecked(params));
|
|
18141
|
+
}
|
|
18142
|
+
async putReplacingUnchecked(params) {
|
|
18076
18143
|
const existing = await this.store.query.query({
|
|
18077
18144
|
collection: MEDIA_COLLECTION,
|
|
18078
18145
|
filter: { where: {
|
|
@@ -18081,7 +18148,7 @@ var MediaStore = class {
|
|
|
18081
18148
|
kind: params.kind
|
|
18082
18149
|
} }
|
|
18083
18150
|
});
|
|
18084
|
-
const newKey = await this.
|
|
18151
|
+
const newKey = await this.putUnchecked(params);
|
|
18085
18152
|
for (const row of existing) {
|
|
18086
18153
|
if (row.id === newKey) continue;
|
|
18087
18154
|
const rowData = row.data;
|
|
@@ -18124,8 +18191,11 @@ var MediaStore = class {
|
|
|
18124
18191
|
* own kind.
|
|
18125
18192
|
*/
|
|
18126
18193
|
async promoteToLastFrame(input) {
|
|
18194
|
+
return this.gated(() => this.promoteToLastFrameUnchecked(input));
|
|
18195
|
+
}
|
|
18196
|
+
async promoteToLastFrameUnchecked(input) {
|
|
18127
18197
|
const data = Buffer.from(input.snapshot.base64, "base64");
|
|
18128
|
-
const newKey = await this.
|
|
18198
|
+
const newKey = await this.putReplacingUnchecked({
|
|
18129
18199
|
deviceId: input.deviceId,
|
|
18130
18200
|
ownerKind: "track",
|
|
18131
18201
|
ownerId: input.trackId,
|
|
@@ -18133,7 +18203,7 @@ var MediaStore = class {
|
|
|
18133
18203
|
timestamp: input.snapshot.timestamp,
|
|
18134
18204
|
data
|
|
18135
18205
|
});
|
|
18136
|
-
if (input.keepSource !== true) await this.
|
|
18206
|
+
if (input.keepSource !== true) await this.deleteByKeyUnchecked(input.snapshot.key);
|
|
18137
18207
|
return newKey;
|
|
18138
18208
|
}
|
|
18139
18209
|
/**
|
|
@@ -18171,6 +18241,9 @@ var MediaStore = class {
|
|
|
18171
18241
|
* Deletes the blob from storage (best-effort) and removes the index row.
|
|
18172
18242
|
*/
|
|
18173
18243
|
async deleteByKey(key) {
|
|
18244
|
+
await this.gated(() => this.deleteByKeyUnchecked(key));
|
|
18245
|
+
}
|
|
18246
|
+
async deleteByKeyUnchecked(key) {
|
|
18174
18247
|
const row = await this.store.get.query({
|
|
18175
18248
|
collection: MEDIA_COLLECTION,
|
|
18176
18249
|
key
|
|
@@ -18281,6 +18354,9 @@ var MediaStore = class {
|
|
|
18281
18354
|
* Best-effort per row; returns the number of index rows removed.
|
|
18282
18355
|
*/
|
|
18283
18356
|
async deleteForOwner(ownerKind, ownerIds) {
|
|
18357
|
+
return this.gated(() => this.deleteForOwnerUnchecked(ownerKind, ownerIds));
|
|
18358
|
+
}
|
|
18359
|
+
async deleteForOwnerUnchecked(ownerKind, ownerIds) {
|
|
18284
18360
|
let removed = 0;
|
|
18285
18361
|
for (const ownerId of ownerIds) {
|
|
18286
18362
|
const rows = await this.store.query.query({
|
|
@@ -18362,6 +18438,9 @@ var MediaStore = class {
|
|
|
18362
18438
|
* The old-entry deletion is best-effort: on failure a warning is logged
|
|
18363
18439
|
* and the method still returns the new key. */
|
|
18364
18440
|
async reown(input) {
|
|
18441
|
+
return this.gated(() => this.reownUnchecked(input));
|
|
18442
|
+
}
|
|
18443
|
+
async reownUnchecked(input) {
|
|
18365
18444
|
const { fromOwnerKind, fromOwnerId, toOwnerKind, toOwnerId, key } = input;
|
|
18366
18445
|
const row = (await this.store.query.query({
|
|
18367
18446
|
collection: MEDIA_COLLECTION,
|
|
@@ -18395,7 +18474,7 @@ var MediaStore = class {
|
|
|
18395
18474
|
relativePath: oldPath
|
|
18396
18475
|
})
|
|
18397
18476
|
};
|
|
18398
|
-
const newKey = await this.
|
|
18477
|
+
const newKey = await this.putUnchecked(newParams);
|
|
18399
18478
|
try {
|
|
18400
18479
|
await this.storage.delete({
|
|
18401
18480
|
location: mediaRowLocation(meta),
|
|
@@ -18449,8 +18528,10 @@ var MediaStore = class {
|
|
|
18449
18528
|
* batch.
|
|
18450
18529
|
*/
|
|
18451
18530
|
async deleteByTracks(trackIds) {
|
|
18452
|
-
await this.
|
|
18453
|
-
|
|
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
|
+
});
|
|
18454
18535
|
await this.deleteForOwner("plate", trackIds.map((id) => PLATE_MEDIA_OWNER_PREFIX + id));
|
|
18455
18536
|
}
|
|
18456
18537
|
/**
|
|
@@ -18492,7 +18573,7 @@ function snapshot(j) {
|
|
|
18492
18573
|
fromLocationId: j.fromLocationId,
|
|
18493
18574
|
toLocationId: j.toLocationId,
|
|
18494
18575
|
deviceId: j.deviceId,
|
|
18495
|
-
entities: ["media"],
|
|
18576
|
+
entities: ["media", "retrainFrames"],
|
|
18496
18577
|
filesMoved: j.filesMoved,
|
|
18497
18578
|
bytesMoved: j.bytesMoved,
|
|
18498
18579
|
filesTotal: null,
|
|
@@ -18523,7 +18604,7 @@ var MediaRelocateEngine = class {
|
|
|
18523
18604
|
state: "running",
|
|
18524
18605
|
fromLocationId: "*",
|
|
18525
18606
|
toLocationId: input.toLocationId,
|
|
18526
|
-
deviceId:
|
|
18607
|
+
deviceId: null,
|
|
18527
18608
|
filesMoved: 0,
|
|
18528
18609
|
bytesMoved: 0,
|
|
18529
18610
|
startedAt: this.deps.now(),
|
|
@@ -18540,24 +18621,25 @@ var MediaRelocateEngine = class {
|
|
|
18540
18621
|
const bytesPerMs = throttleMbps * 1024 * 1024 / 1e3;
|
|
18541
18622
|
try {
|
|
18542
18623
|
await this.deps.resolveTargetRoot(job.toLocationId);
|
|
18624
|
+
let failedRows = 0;
|
|
18543
18625
|
let cursor = 0;
|
|
18544
|
-
let
|
|
18626
|
+
let offsetAtCursor = 0;
|
|
18545
18627
|
for (;;) {
|
|
18546
18628
|
if (job.cancelRequested) break;
|
|
18547
|
-
const
|
|
18629
|
+
const rows = await this.deps.store.query.query({
|
|
18548
18630
|
collection: MEDIA_COLLECTION,
|
|
18549
18631
|
filter: {
|
|
18550
|
-
...job.deviceId !== null ? { where: { deviceId: job.deviceId } } : {},
|
|
18551
18632
|
whereBetween: { timestamp: [cursor, Number.MAX_SAFE_INTEGER] },
|
|
18552
18633
|
orderBy: {
|
|
18553
18634
|
field: "timestamp",
|
|
18554
18635
|
direction: "asc"
|
|
18555
18636
|
},
|
|
18556
|
-
limit: PAGE_SIZE
|
|
18637
|
+
limit: PAGE_SIZE,
|
|
18638
|
+
offset: offsetAtCursor
|
|
18557
18639
|
}
|
|
18558
|
-
})
|
|
18559
|
-
if (
|
|
18560
|
-
for (const row of
|
|
18640
|
+
});
|
|
18641
|
+
if (rows.length === 0) break;
|
|
18642
|
+
for (const row of rows) {
|
|
18561
18643
|
if (job.cancelRequested) break;
|
|
18562
18644
|
const data = row.data;
|
|
18563
18645
|
const from = mediaRowLocation(data);
|
|
@@ -18565,6 +18647,17 @@ var MediaRelocateEngine = class {
|
|
|
18565
18647
|
const relativePath = String(data["path"] ?? "");
|
|
18566
18648
|
if (relativePath.length === 0) continue;
|
|
18567
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
|
+
}
|
|
18568
18661
|
const bytes = await this.deps.storage.read({
|
|
18569
18662
|
location: from,
|
|
18570
18663
|
relativePath
|
|
@@ -18574,6 +18667,10 @@ var MediaRelocateEngine = class {
|
|
|
18574
18667
|
relativePath,
|
|
18575
18668
|
data: bytes
|
|
18576
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}`);
|
|
18577
18674
|
await this.deps.store.set.mutate({
|
|
18578
18675
|
collection: MEDIA_COLLECTION,
|
|
18579
18676
|
key: row.id,
|
|
@@ -18590,20 +18687,99 @@ var MediaRelocateEngine = class {
|
|
|
18590
18687
|
job.bytesMoved += bytes.length;
|
|
18591
18688
|
await sleep(bytes.length / bytesPerMs);
|
|
18592
18689
|
} catch (err) {
|
|
18690
|
+
failedRows++;
|
|
18593
18691
|
this.deps.logger.debug("media relocate row failed", { meta: {
|
|
18594
18692
|
key: row.id,
|
|
18595
18693
|
error: String(err)
|
|
18596
18694
|
} });
|
|
18597
18695
|
}
|
|
18598
18696
|
}
|
|
18599
|
-
const last =
|
|
18697
|
+
const last = rows[rows.length - 1];
|
|
18600
18698
|
const lastTs = Number(last.data["timestamp"] ?? cursor);
|
|
18601
|
-
if (lastTs === cursor)
|
|
18699
|
+
if (lastTs === cursor) offsetAtCursor += rows.length;
|
|
18602
18700
|
else {
|
|
18603
18701
|
cursor = lastTs;
|
|
18604
|
-
|
|
18702
|
+
offsetAtCursor = rows.filter((row) => Number(row.data["timestamp"]) === lastTs).length;
|
|
18605
18703
|
}
|
|
18704
|
+
if (rows.length < PAGE_SIZE) break;
|
|
18606
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`);
|
|
18607
18783
|
job.state = job.cancelRequested ? "cancelled" : "done";
|
|
18608
18784
|
} catch (err) {
|
|
18609
18785
|
job.state = "failed";
|
|
@@ -18617,6 +18793,18 @@ var MediaRelocateEngine = class {
|
|
|
18617
18793
|
this.deps.onFinished?.(snapshot(job));
|
|
18618
18794
|
}
|
|
18619
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
|
+
}
|
|
18620
18808
|
};
|
|
18621
18809
|
//#endregion
|
|
18622
18810
|
//#region src/pipeline-analytics/media-settings.ts
|
|
@@ -31169,6 +31357,9 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
31169
31357
|
eventsOpsLog = null;
|
|
31170
31358
|
/** Event-media relocation engine (entity-routing Phase 4). */
|
|
31171
31359
|
mediaRelocate = null;
|
|
31360
|
+
mediaLocationStorage = null;
|
|
31361
|
+
mediaWriteGate = new MediaWriteGate();
|
|
31362
|
+
storageMigrationLeaseId = null;
|
|
31172
31363
|
/** Per-camera history of LINKED-device sensor state changes (Part B). */
|
|
31173
31364
|
sensorEventStore = null;
|
|
31174
31365
|
/** Ingest-side reverse index (sensor device → linked camera ids), TTL-cached
|
|
@@ -31969,7 +32160,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
31969
32160
|
logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
|
|
31970
32161
|
}
|
|
31971
32162
|
if (!storage) throw new Error("pipeline-analytics requires ctx.kernel.storage");
|
|
31972
|
-
|
|
32163
|
+
const routed = createLocationAwareMediaStorage({
|
|
31973
32164
|
base: storage,
|
|
31974
32165
|
defaultLocation: "eventMedia",
|
|
31975
32166
|
resolveRoot: (locationId) => this.ctx.api.storage.resolve.query({
|
|
@@ -31977,6 +32168,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
31977
32168
|
relativePath: ""
|
|
31978
32169
|
})
|
|
31979
32170
|
});
|
|
32171
|
+
this.mediaLocationStorage = routed;
|
|
32172
|
+
return routed;
|
|
31980
32173
|
}
|
|
31981
32174
|
/** Constructs every SQLite-backed store plus the stationary/package-drop/
|
|
31982
32175
|
* sensor plumbing, in the exact pre-S7 order. Returns the non-null bundle
|
|
@@ -32021,7 +32214,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
32021
32214
|
const mediaStore = new MediaStore({
|
|
32022
32215
|
storage,
|
|
32023
32216
|
store: api.settingsStore,
|
|
32024
|
-
logger: logger.child("MediaStore")
|
|
32217
|
+
logger: logger.child("MediaStore"),
|
|
32218
|
+
writeGate: this.mediaWriteGate
|
|
32025
32219
|
});
|
|
32026
32220
|
this.mediaStore = mediaStore;
|
|
32027
32221
|
const retrainFrames = new RetrainFrameStore({
|
|
@@ -32029,6 +32223,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
32029
32223
|
storage,
|
|
32030
32224
|
logger: logger.child("RetrainFrames"),
|
|
32031
32225
|
now: () => Date.now(),
|
|
32226
|
+
writeGate: this.mediaWriteGate,
|
|
32032
32227
|
readSource: async (mediaKey) => {
|
|
32033
32228
|
const file = await mediaStore.getByKey(mediaKey);
|
|
32034
32229
|
return file === null ? null : Buffer.from(file.base64, "base64");
|
|
@@ -36270,14 +36465,38 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
36270
36465
|
return this.queryFacade.deleteDeviceEvents(input);
|
|
36271
36466
|
}
|
|
36272
36467
|
/** The events ops-log rows (newest-first), optionally scoped to one camera. */
|
|
36273
|
-
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) {
|
|
36274
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");
|
|
36275
36494
|
return { jobId: this.mediaRelocate.start(input) };
|
|
36276
36495
|
}
|
|
36277
|
-
async
|
|
36278
|
-
return this.mediaRelocate?.list() ??
|
|
36496
|
+
async getStorageMigrationMoveStatus(input) {
|
|
36497
|
+
return this.mediaRelocate?.list().find((job) => job.jobId === input.jobId) ?? null;
|
|
36279
36498
|
}
|
|
36280
|
-
async
|
|
36499
|
+
async cancelStorageMigrationMove(input) {
|
|
36281
36500
|
return { cancelled: this.mediaRelocate?.cancel(input.jobId) ?? false };
|
|
36282
36501
|
}
|
|
36283
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
|
}
|