@camstack/addon-pipeline 1.2.106 → 1.2.115
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/{addon-utils-BEzVJ0xg.js → addon-utils-C0ht2KPO.js} +51 -15
- package/dist/{addon-utils-A2S9D7pu.mjs → addon-utils-CgBCF-mE.mjs} +50 -14
- package/dist/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +3 -3
- package/dist/detection-pipeline/index.js +198 -38
- package/dist/detection-pipeline/index.mjs +197 -37
- package/dist/{dist-BSeR_hVv.js → dist-BqIbYW1A.js} +290 -17
- package/dist/{dist-lITI1Bn5.mjs → dist-DGQkkWc6.mjs} +273 -18
- package/dist/{event-loop-stall-monitor-0VvjTRDP.mjs → event-loop-stall-monitor-D5jK5v4Z.mjs} +223 -2
- package/dist/{event-loop-stall-monitor-BNsp9k8r.js → event-loop-stall-monitor-ylMGpz48.js} +234 -1
- package/dist/{lazy-sharp-CBohM8D8.js → lazy-sharp-cOsy7l_P.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +915 -107
- package/dist/pipeline-runner/index.mjs +914 -106
- package/dist/{process-memory-C33u3tQz.js → process-memory-45juuHpN.js} +1 -1
- package/dist/{process-memory-CEKVKfwg.mjs → process-memory-BtT7WfbJ.mjs} +1 -1
- package/dist/recorder/index.js +187 -47
- package/dist/recorder/index.mjs +187 -47
- package/dist/session-decode/decode-worker-child.js +30 -10
- package/dist/session-decode/decode-worker-child.mjs +29 -9
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-w2XLD3uI.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C_SFFNGT.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B1LDFHY9.mjs +26 -0
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DsIi4G5Q.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BAMyzCWw.mjs} +1 -1
- package/dist/stream-broker/{hostInit-B-h24cDy.mjs → hostInit-CG28NvlV.mjs} +3 -3
- package/dist/stream-broker/index.js +279 -198
- package/dist/stream-broker/index.mjs +279 -198
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-CiKfal2P.js → worker-protocol-CIrqGbpG.js} +6 -3
- package/dist/{worker-protocol-BDiL8TBm.mjs → worker-protocol-DyocTIx_.mjs} +6 -3
- package/package.json +1 -1
- package/python/inference_pool.py +4 -1
- package/python/test_inference_pool_preprocess.py +87 -16
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DvZWLWk1.mjs +0 -26
package/dist/recorder/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { At as
|
|
1
|
+
import { At as nodePin, B as deriveRecordingMode, Ct as DeviceFeature, Et as hydrateSchema, Ht as object, Kt as EventCategory, M as batteryCapability, Nt as selectAssignedProfileSlots, R as deriveBatteryPresence, T as RecordingConfigSchema, Ut as record, Vt as number, Wt as string, at as recordingExportCapability, b as OpsLogEntrySchema, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, dt as storageEvictableCapability, g as ExportRecordSchema, ht as errMsg, i as BatteryStatusSchema, it as recordingCapability, lt as resolveRecordingProfiles, m as EVENT_PAD_MS, w as RECORDING_EXPORT_MAX_READ_BYTES, wt as DeviceType, xt as BaseAddon } from "../dist-DGQkkWc6.mjs";
|
|
2
2
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
3
3
|
import { r as withRecordingIntent } from "../restream-intent-B4BXZra7.mjs";
|
|
4
|
-
import { n as createFileDataPlaneHandler, s as parseRangeHeader, t as contentTypeFor } from "../addon-utils-
|
|
4
|
+
import { n as createFileDataPlaneHandler, s as parseRangeHeader, t as contentTypeFor } from "../addon-utils-CgBCF-mE.mjs";
|
|
5
5
|
import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DMolfhsP.mjs";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
@@ -756,6 +756,23 @@ var RecordingIndex = class {
|
|
|
756
756
|
segments(deviceId, profile) {
|
|
757
757
|
return [...this.sortedSegments(deviceId, profile)];
|
|
758
758
|
}
|
|
759
|
+
/**
|
|
760
|
+
* Start-sorted rows whose `startMs` is in `[fromMs, toMs)`.
|
|
761
|
+
* Slices the live view — does not copy the rest of the archive.
|
|
762
|
+
*/
|
|
763
|
+
segmentsStartingIn(deviceId, profile, fromMs, toMs) {
|
|
764
|
+
const segs = this.sortedSegments(deviceId, profile);
|
|
765
|
+
let lo = 0;
|
|
766
|
+
let hi = segs.length;
|
|
767
|
+
while (lo < hi) {
|
|
768
|
+
const mid = lo + hi >> 1;
|
|
769
|
+
if (segs[mid].startMs < fromMs) lo = mid + 1;
|
|
770
|
+
else hi = mid;
|
|
771
|
+
}
|
|
772
|
+
let end = lo;
|
|
773
|
+
while (end < segs.length && segs[end].startMs < toMs) end += 1;
|
|
774
|
+
return lo === 0 && end === segs.length ? segs : segs.slice(lo, end);
|
|
775
|
+
}
|
|
759
776
|
/** True when `relPath` is currently indexed for `deviceId`. O(1). */
|
|
760
777
|
hasSegment(deviceId, relPath) {
|
|
761
778
|
return this.byDevice.get(deviceId)?.has(relPath) === true;
|
|
@@ -2113,6 +2130,29 @@ function utcDayOf(epochMs) {
|
|
|
2113
2130
|
*/
|
|
2114
2131
|
var RANGE_MERGE_GAP_MS$1 = 5e3;
|
|
2115
2132
|
/**
|
|
2133
|
+
* Prefer the RAM index for one hour of the timeline.
|
|
2134
|
+
*
|
|
2135
|
+
* A hydrated hour is closed: the walk (or a targeted `hydrateWindow`) already
|
|
2136
|
+
* looked, so an empty array is a real gap, not "unknown". Write-through
|
|
2137
|
+
* `addSegment` rows ride along once that hour is walked — they do NOT by
|
|
2138
|
+
* themselves make the hour known. A recorder that just respawned at 11:04
|
|
2139
|
+
* has only the post-restart finalizes in RAM; treating those as the whole
|
|
2140
|
+
* hour would hide the 11:00–11:04 footage until the next walk (the 2026-08-22
|
|
2141
|
+
* deploy gap). Unknown hours still fall through to one directory read.
|
|
2142
|
+
*/
|
|
2143
|
+
function hourSegmentsFromIndex(index, deviceId, profile, hourStartMs) {
|
|
2144
|
+
const hour = Math.floor(hourStartMs / HOUR_MS$4) * HOUR_MS$4;
|
|
2145
|
+
const hourEnd = hour + HOUR_MS$4;
|
|
2146
|
+
if (index.hydrationOf(deviceId, hour, hourEnd) !== "hydrated") return null;
|
|
2147
|
+
const rows = [];
|
|
2148
|
+
for (const s of index.segmentsStartingIn(deviceId, profile, hour, hourEnd)) rows.push({
|
|
2149
|
+
startMs: s.startMs,
|
|
2150
|
+
durMs: s.durMs,
|
|
2151
|
+
bytes: s.bytes
|
|
2152
|
+
});
|
|
2153
|
+
return rows;
|
|
2154
|
+
}
|
|
2155
|
+
/**
|
|
2116
2156
|
* How long the CURRENT hour's answer may be reused.
|
|
2117
2157
|
*
|
|
2118
2158
|
* Not caching it at all was the first instinct — it is still being written, so
|
|
@@ -2343,6 +2383,11 @@ var CalendarIndex = class {
|
|
|
2343
2383
|
async segmentsOfChecked(deviceId, profile, hourStartMs, background = false) {
|
|
2344
2384
|
if (!background) this.lastForegroundAt = this.deps.now?.() ?? Date.now();
|
|
2345
2385
|
const hour = Math.floor(hourStartMs / HOUR_MS$4) * HOUR_MS$4;
|
|
2386
|
+
const fromIndex = this.deps.indexHour?.(deviceId, profile, hour);
|
|
2387
|
+
if (fromIndex !== null && fromIndex !== void 0) return {
|
|
2388
|
+
segments: fromIndex,
|
|
2389
|
+
complete: true
|
|
2390
|
+
};
|
|
2346
2391
|
const key = `${deviceId}:${profile}:${hour}`;
|
|
2347
2392
|
const now = this.deps.now?.() ?? Date.now();
|
|
2348
2393
|
const isCurrentHour = hour === Math.floor(now / HOUR_MS$4) * HOUR_MS$4;
|
|
@@ -2412,6 +2457,32 @@ var CalendarIndex = class {
|
|
|
2412
2457
|
return mergeRanges((await mapBounded(Array.from({ length: 24 }, (_, h) => day + h * HOUR_MS$4), READ_CONCURRENCY, (hour) => this.segmentsOf(deviceId, profile, hour))).flat(), RANGE_MERGE_GAP_MS$1);
|
|
2413
2458
|
}
|
|
2414
2459
|
/**
|
|
2460
|
+
* Exact ranges of `[fromMs, toMs)`, scanning ONLY the hours the window
|
|
2461
|
+
* overlaps.
|
|
2462
|
+
*
|
|
2463
|
+
* `dayRanges` always walks 24 hours. The theater asks for a lookback of a
|
|
2464
|
+
* couple of hours, then every 30 s poll (and every scrub that extends
|
|
2465
|
+
* lookback) called that full-day walk PLUS `warmAdjacent` (48 more hours)
|
|
2466
|
+
* on the same FUSE share the feeder was reading. 2026-08-22 15:50Z: locate
|
|
2467
|
+
* was 3 ms from RAM, the .m4s read hit `segment read timeout`.
|
|
2468
|
+
*/
|
|
2469
|
+
async rangesIn(deviceId, profile, fromMs, toMs) {
|
|
2470
|
+
if (toMs <= fromMs) return [];
|
|
2471
|
+
const first = Math.floor(fromMs / HOUR_MS$4) * HOUR_MS$4;
|
|
2472
|
+
const hours = [];
|
|
2473
|
+
for (let hour = first; hour < toMs; hour += HOUR_MS$4) hours.push(hour);
|
|
2474
|
+
const perHour = await mapBounded(hours, READ_CONCURRENCY, (hour) => this.segmentsOf(deviceId, profile, hour));
|
|
2475
|
+
const ranges = [];
|
|
2476
|
+
for (const r of mergeRanges(perHour.flat(), RANGE_MERGE_GAP_MS$1)) {
|
|
2477
|
+
if (r.endMs <= fromMs || r.startMs >= toMs) continue;
|
|
2478
|
+
ranges.push({
|
|
2479
|
+
startMs: Math.max(r.startMs, fromMs),
|
|
2480
|
+
endMs: Math.min(r.endMs, toMs)
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
return ranges;
|
|
2484
|
+
}
|
|
2485
|
+
/**
|
|
2415
2486
|
* Warm the days either side of the one being viewed, in the BACKGROUND.
|
|
2416
2487
|
*
|
|
2417
2488
|
* Changing day is one click and the operator expects the timeline to be
|
|
@@ -4404,16 +4475,11 @@ function buildRecordingProvider(deps) {
|
|
|
4404
4475
|
ranges: []
|
|
4405
4476
|
};
|
|
4406
4477
|
const ranges = [];
|
|
4407
|
-
const
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
startMs: r.startMs,
|
|
4413
|
-
endMs: r.endMs
|
|
4414
|
-
});
|
|
4415
|
-
}
|
|
4416
|
-
calendar.warmAdjacent(deviceId, profile, firstDay);
|
|
4478
|
+
for (const r of await calendar.rangesIn(deviceId, profile, fromMs, toMs)) ranges.push({
|
|
4479
|
+
profile,
|
|
4480
|
+
startMs: r.startMs,
|
|
4481
|
+
endMs: r.endMs
|
|
4482
|
+
});
|
|
4417
4483
|
return {
|
|
4418
4484
|
deviceId,
|
|
4419
4485
|
ranges
|
|
@@ -4490,6 +4556,13 @@ function buildRecordingProvider(deps) {
|
|
|
4490
4556
|
},
|
|
4491
4557
|
getDeviceConfig: async ({ deviceId }) => loadDeviceConfig(deps.configStore, deviceId),
|
|
4492
4558
|
locateSegment: async ({ deviceId, profile, epochMs }) => {
|
|
4559
|
+
const covering = deps.index.segmentAt(deviceId, profile, epochMs);
|
|
4560
|
+
if (covering) return {
|
|
4561
|
+
kind: "segment",
|
|
4562
|
+
startMs: covering.startMs,
|
|
4563
|
+
durMs: covering.durMs,
|
|
4564
|
+
bytes: covering.bytes
|
|
4565
|
+
};
|
|
4493
4566
|
const hydrateFromMs = Math.max(0, epochMs - LOCATE_HYDRATE_MARGIN_MS);
|
|
4494
4567
|
const hydrateToMs = epochMs + LOCATE_HYDRATE_MARGIN_MS;
|
|
4495
4568
|
if (deps.index.hydrationOf(deviceId, hydrateFromMs, hydrateToMs) !== "hydrated") await deps.hydrateWindow?.(deviceId, hydrateFromMs, hydrateToMs);
|
|
@@ -7996,34 +8069,87 @@ var RecordingController = class {
|
|
|
7996
8069
|
}
|
|
7997
8070
|
/**
|
|
7998
8071
|
* One profile's ffmpeg writer exhausted its bounded inner restarts and gave
|
|
7999
|
-
* up
|
|
8000
|
-
*
|
|
8001
|
-
*
|
|
8002
|
-
*
|
|
8003
|
-
*
|
|
8004
|
-
*
|
|
8005
|
-
*
|
|
8006
|
-
*
|
|
8072
|
+
* up, or the idle watchdog found it silent.
|
|
8073
|
+
*
|
|
8074
|
+
* Recover THAT profile only when siblings are still producing. Whole-device
|
|
8075
|
+
* detach was the 2026-08-22 gap: high stalled 139 s on 615/590/617/586 and
|
|
8076
|
+
* recovery unpinned low too — the profile the timeline and scrub actually
|
|
8077
|
+
* read — punching 38–172 s holes in continuous cameras. The broker re-dial
|
|
8078
|
+
* argument still holds when NOTHING on the device is healthy; then we detach
|
|
8079
|
+
* the whole set so the next evaluate starts clean.
|
|
8080
|
+
*
|
|
8081
|
+
* Bounded OUTER retry (RECONCILE_TICK_MS / fillMissingProfiles cadence),
|
|
8007
8082
|
* never a tight loop.
|
|
8008
8083
|
*/
|
|
8009
|
-
async recoverWriterGaveUp(deviceId) {
|
|
8084
|
+
async recoverWriterGaveUp(deviceId, profile) {
|
|
8010
8085
|
if (this.stopped) return;
|
|
8011
|
-
this.
|
|
8086
|
+
const current = this.active.get(deviceId);
|
|
8087
|
+
if (!current || current.length === 0) return;
|
|
8088
|
+
const stalled = profile === void 0 ? current : current.filter((r) => r.profile === profile);
|
|
8089
|
+
if (stalled.length === 0) return;
|
|
8090
|
+
const keep = profile === void 0 ? [] : current.filter((r) => r.profile !== profile);
|
|
8091
|
+
if (keep.length === 0) {
|
|
8092
|
+
this.deps.logger.warn("recorder: writer gave up — detaching + re-evaluating device", {
|
|
8093
|
+
tags: { deviceId },
|
|
8094
|
+
...profile !== void 0 ? { meta: { profile } } : {}
|
|
8095
|
+
});
|
|
8096
|
+
try {
|
|
8097
|
+
await this.detachDevice(deviceId);
|
|
8098
|
+
await this.evaluateDevice(deviceId);
|
|
8099
|
+
} catch (err) {
|
|
8100
|
+
this.restore?.add(deviceId, true);
|
|
8101
|
+
this.deps.logger.warn("recorder: writer give-up recovery deferred (retries on next broker ready/reconcile)", {
|
|
8102
|
+
tags: { deviceId },
|
|
8103
|
+
meta: { error: errMsg(err) }
|
|
8104
|
+
});
|
|
8105
|
+
}
|
|
8106
|
+
return;
|
|
8107
|
+
}
|
|
8108
|
+
this.deps.logger.warn("recorder: writer gave up — recovering stalled profile, healthy profiles keep recording", {
|
|
8109
|
+
tags: { deviceId },
|
|
8110
|
+
meta: {
|
|
8111
|
+
profile: stalled.map((r) => r.profile),
|
|
8112
|
+
keeping: keep.map((r) => r.profile)
|
|
8113
|
+
}
|
|
8114
|
+
});
|
|
8012
8115
|
try {
|
|
8013
|
-
await this.
|
|
8014
|
-
await this.evaluateDevice(deviceId);
|
|
8116
|
+
await this.recoverStalledProfiles(deviceId, stalled, keep);
|
|
8015
8117
|
} catch (err) {
|
|
8016
8118
|
this.restore?.add(deviceId, true);
|
|
8017
|
-
this.deps.logger.warn("recorder:
|
|
8119
|
+
this.deps.logger.warn("recorder: stalled-profile recovery deferred (retries on next broker ready/reconcile)", {
|
|
8018
8120
|
tags: { deviceId },
|
|
8019
|
-
meta: {
|
|
8121
|
+
meta: {
|
|
8122
|
+
error: errMsg(err),
|
|
8123
|
+
profile: stalled.map((r) => r.profile)
|
|
8124
|
+
}
|
|
8020
8125
|
});
|
|
8021
8126
|
}
|
|
8022
8127
|
}
|
|
8023
8128
|
/**
|
|
8129
|
+
* Tear down only the stalled writers, keep the rest pinned, and retry the
|
|
8130
|
+
* missing profiles through {@link fillMissingProfiles}.
|
|
8131
|
+
*/
|
|
8132
|
+
async recoverStalledProfiles(deviceId, stalled, keep) {
|
|
8133
|
+
for (const r of stalled) {
|
|
8134
|
+
this.lastSegmentAt.delete(idleKey(deviceId, r.profile));
|
|
8135
|
+
this.attachRetained.delete(idleKey(deviceId, r.profile));
|
|
8136
|
+
this.firstSegmentSeen.delete(idleKey(deviceId, r.profile));
|
|
8137
|
+
}
|
|
8138
|
+
this.active.set(deviceId, [...keep]);
|
|
8139
|
+
const skipped = this.skippedProfiles.get(deviceId) ?? /* @__PURE__ */ new Set();
|
|
8140
|
+
for (const r of stalled) skipped.add(r.profile);
|
|
8141
|
+
this.skippedProfiles.set(deviceId, skipped);
|
|
8142
|
+
await Promise.all(stalled.map((r) => this.teardownProfile(deviceId, r)));
|
|
8143
|
+
const config = await this.deps.loadConfig(deviceId);
|
|
8144
|
+
if (this.stopped || this.paused) return;
|
|
8145
|
+
await this.fillMissingProfiles(deviceId, config);
|
|
8146
|
+
}
|
|
8147
|
+
/**
|
|
8024
8148
|
* Find writers that hold a broker pin but have finalized nothing for longer
|
|
8025
|
-
* than their idle bound, and recover them through the same
|
|
8026
|
-
*
|
|
8149
|
+
* than their idle bound, and recover them through the same path a give-up
|
|
8150
|
+
* takes. Per-profile when siblings are still producing (high stall must not
|
|
8151
|
+
* unpin the low timeline/scrub writer); whole-device only when every active
|
|
8152
|
+
* profile on that camera is silent.
|
|
8027
8153
|
*
|
|
8028
8154
|
* The counterpart to `onGaveUp`: that one fires when ffmpeg EXITS, this one
|
|
8029
8155
|
* when it does not exit and does not work. Both must exist — a branch that
|
|
@@ -8044,7 +8170,6 @@ var RecordingController = class {
|
|
|
8044
8170
|
boundMs
|
|
8045
8171
|
});
|
|
8046
8172
|
}
|
|
8047
|
-
const devices = new Set(stalled.map((s) => s.deviceId));
|
|
8048
8173
|
for (const s of stalled) this.deps.logger.warn("recorder: writer pinned but no segment finalized — re-pinning", {
|
|
8049
8174
|
tags: { deviceId: s.deviceId },
|
|
8050
8175
|
meta: {
|
|
@@ -8054,7 +8179,20 @@ var RecordingController = class {
|
|
|
8054
8179
|
segmentSeconds: this.active.get(s.deviceId)?.find((r) => r.profile === s.profile)?.segmentSeconds
|
|
8055
8180
|
}
|
|
8056
8181
|
});
|
|
8057
|
-
|
|
8182
|
+
const byDevice = /* @__PURE__ */ new Map();
|
|
8183
|
+
for (const s of stalled) {
|
|
8184
|
+
const list = byDevice.get(s.deviceId);
|
|
8185
|
+
if (list) list.push(s.profile);
|
|
8186
|
+
else byDevice.set(s.deviceId, [s.profile]);
|
|
8187
|
+
}
|
|
8188
|
+
await Promise.all([...byDevice.entries()].map(async ([deviceId, profiles]) => {
|
|
8189
|
+
const activeCount = this.active.get(deviceId)?.length ?? 0;
|
|
8190
|
+
if (profiles.length >= activeCount) {
|
|
8191
|
+
await this.recoverWriterGaveUp(deviceId);
|
|
8192
|
+
return;
|
|
8193
|
+
}
|
|
8194
|
+
for (const profile of profiles) await this.recoverWriterGaveUp(deviceId, profile);
|
|
8195
|
+
}));
|
|
8058
8196
|
}
|
|
8059
8197
|
/** Current trigger state for a device (cold default = no triggers seen). */
|
|
8060
8198
|
triggersFor(deviceId) {
|
|
@@ -8463,7 +8601,7 @@ var RecordingController = class {
|
|
|
8463
8601
|
spawn: this.deps.spawn,
|
|
8464
8602
|
logger: deviceLog,
|
|
8465
8603
|
onGaveUp: () => {
|
|
8466
|
-
this.recoverWriterGaveUp(deviceId);
|
|
8604
|
+
this.recoverWriterGaveUp(deviceId, profile);
|
|
8467
8605
|
},
|
|
8468
8606
|
onResourcePressure: () => this.spawnPressure.report(deviceId)
|
|
8469
8607
|
});
|
|
@@ -10234,10 +10372,10 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
10234
10372
|
/**
|
|
10235
10373
|
* The calendar rungs — which days, which hours, and a day's exact ranges.
|
|
10236
10374
|
*
|
|
10237
|
-
*
|
|
10238
|
-
*
|
|
10239
|
-
*
|
|
10240
|
-
*
|
|
10375
|
+
* Hours the RAM index has already walked (or the live hour it is
|
|
10376
|
+
* write-through on) never touch the recordings volume. Directory reads
|
|
10377
|
+
* remain only for hours still `unknown` — that FUSE readdir is what queued
|
|
10378
|
+
* behind scrub's `locateSegment` and expired its 8 s budget (2026-08-22).
|
|
10241
10379
|
*/
|
|
10242
10380
|
calendarInstance = null;
|
|
10243
10381
|
/** LAZY: a field initialiser runs before `initialize()` assigns `ctx`, and
|
|
@@ -10245,7 +10383,8 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
10245
10383
|
get calendar() {
|
|
10246
10384
|
this.calendarInstance ??= new CalendarIndex({
|
|
10247
10385
|
roots: () => this.resolvedLocations.map((l) => l.root),
|
|
10248
|
-
logger: this.ctx.logger
|
|
10386
|
+
logger: this.ctx.logger,
|
|
10387
|
+
indexHour: (deviceId, profile, hourStartMs) => hourSegmentsFromIndex(this.index, deviceId, profile, hourStartMs)
|
|
10249
10388
|
});
|
|
10250
10389
|
return this.calendarInstance;
|
|
10251
10390
|
}
|
|
@@ -10802,7 +10941,7 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
10802
10941
|
await this.segmentHours?.load();
|
|
10803
10942
|
this.segmentHours?.hydrateIndex(this.index);
|
|
10804
10943
|
} catch (err) {
|
|
10805
|
-
this.ctx.logger.warn("recorder: segment-hour ledger load failed —
|
|
10944
|
+
this.ctx.logger.warn("recorder: segment-hour ledger load failed — hours stay unknown until a read walks them", { meta: { error: errMsg(err) } });
|
|
10806
10945
|
}
|
|
10807
10946
|
let configs;
|
|
10808
10947
|
try {
|
|
@@ -10812,7 +10951,11 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
10812
10951
|
return;
|
|
10813
10952
|
}
|
|
10814
10953
|
this.warmToday([...configs.keys()]);
|
|
10815
|
-
this.
|
|
10954
|
+
this.ctx.logger.info("recorder: full-archive index walk skipped", { meta: {
|
|
10955
|
+
reason: "on-demand-windows",
|
|
10956
|
+
ledgerHours: this.segmentHours?.snapshot().length ?? 0,
|
|
10957
|
+
deviceCount: configs.size
|
|
10958
|
+
} });
|
|
10816
10959
|
this.cleanupLegacyThumbsDirs();
|
|
10817
10960
|
await this.bootstrapExports();
|
|
10818
10961
|
const provider = this.recordingProvider;
|
|
@@ -11238,23 +11381,20 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
11238
11381
|
*/
|
|
11239
11382
|
async warmToday(deviceIds) {
|
|
11240
11383
|
const now = Date.now();
|
|
11241
|
-
const dayMs = 1440 * 60 * 1e3;
|
|
11242
11384
|
const started = now;
|
|
11385
|
+
const liveHour = Math.floor(now / HOUR_MS) * HOUR_MS;
|
|
11243
11386
|
for (const deviceId of deviceIds) try {
|
|
11244
|
-
|
|
11245
|
-
if (profile === null) continue;
|
|
11246
|
-
await this.calendar.warmDayRanges(deviceId, profile, now);
|
|
11247
|
-
await this.calendar.warmDayRanges(deviceId, profile, now - dayMs);
|
|
11387
|
+
await hydrateWindowFromStorage(this.index, deviceId, liveHour, liveHour + HOUR_MS, this.resolvedLocations, this.ctx.logger);
|
|
11248
11388
|
} catch {}
|
|
11249
11389
|
this.ctx.logger.info("recorder: today warmed", { meta: {
|
|
11250
11390
|
devices: deviceIds.length,
|
|
11251
|
-
ms: Date.now() - started
|
|
11391
|
+
ms: Date.now() - started,
|
|
11392
|
+
scope: "live-hour"
|
|
11252
11393
|
} });
|
|
11253
11394
|
}
|
|
11254
11395
|
/**
|
|
11255
|
-
*
|
|
11256
|
-
*
|
|
11257
|
-
* re-armed, and a walk already running is not duplicated.
|
|
11396
|
+
* Opt-in fleet walk — NOT armed at boot (2026-08-22: starved scrub/play).
|
|
11397
|
+
* Kept so an operator path can still request a full reconcile later.
|
|
11258
11398
|
*/
|
|
11259
11399
|
scheduleDeferredFullWalk(deviceIds) {
|
|
11260
11400
|
if (this.fullWalkTimer !== null || this.fullWalkInFlight) return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_worker_protocol = require("../worker-protocol-
|
|
3
|
-
const require_lazy_sharp = require("../lazy-sharp-
|
|
2
|
+
const require_worker_protocol = require("../worker-protocol-CIrqGbpG.js");
|
|
3
|
+
const require_lazy_sharp = require("../lazy-sharp-cOsy7l_P.js");
|
|
4
4
|
//#region src/session-decode/color-conversion.ts
|
|
5
5
|
/**
|
|
6
6
|
* Explicit YUV→RGB colorspace/range resolution for the session-decode worker.
|
|
@@ -1810,18 +1810,36 @@ var DecodeWorkerChild = class {
|
|
|
1810
1810
|
this.pendingPull = true;
|
|
1811
1811
|
this.frames.markPendingPull();
|
|
1812
1812
|
}
|
|
1813
|
-
handleToBuffer(frameId, opts) {
|
|
1814
|
-
const frame = this.frames.toBuffer(frameId);
|
|
1813
|
+
async handleToBuffer(frameId, opts) {
|
|
1814
|
+
const frame = this.leaseStore.get(frameId)?.frame ?? this.frames.toBuffer(frameId);
|
|
1815
1815
|
if (!frame) {
|
|
1816
1816
|
this.sendError(`decode-worker-child: toBuffer for unknown or stale frameId ${frameId}`, frameId);
|
|
1817
1817
|
return;
|
|
1818
1818
|
}
|
|
1819
1819
|
try {
|
|
1820
|
-
const
|
|
1820
|
+
const jpeg = opts?.format === "jpeg";
|
|
1821
|
+
const rawOpts = jpeg ? {
|
|
1822
|
+
...opts,
|
|
1823
|
+
format: "rgb"
|
|
1824
|
+
} : opts;
|
|
1825
|
+
const bytes = frame.isHwFrame() ? this.hwScaleToBuffer(frame, rawOpts) : this.scaleRoiToBuffer(frame, rawOpts);
|
|
1826
|
+
const output = jpeg ? await (async () => {
|
|
1827
|
+
const region = resolveCropRegion(frame.width, frame.height, opts?.crop);
|
|
1828
|
+
const target = resolveResizeTarget(region.width, region.height, opts?.resize);
|
|
1829
|
+
const sharp = await require_lazy_sharp.getSharp();
|
|
1830
|
+
return new Uint8Array(await sharp(bytes, { raw: {
|
|
1831
|
+
width: target.width,
|
|
1832
|
+
height: target.height,
|
|
1833
|
+
channels: 3
|
|
1834
|
+
} }).jpeg({
|
|
1835
|
+
quality: 85,
|
|
1836
|
+
mozjpeg: false
|
|
1837
|
+
}).toBuffer());
|
|
1838
|
+
})() : bytes;
|
|
1821
1839
|
this.send({
|
|
1822
1840
|
kind: "buffer",
|
|
1823
1841
|
frameId,
|
|
1824
|
-
bytes
|
|
1842
|
+
bytes: output
|
|
1825
1843
|
});
|
|
1826
1844
|
} catch (err) {
|
|
1827
1845
|
this.sendError(`decode-worker-child: toBuffer failed — ${errMessage(err)}`, frameId);
|
|
@@ -1853,7 +1871,8 @@ var DecodeWorkerChild = class {
|
|
|
1853
1871
|
requestId,
|
|
1854
1872
|
bytes,
|
|
1855
1873
|
width: resolved.target.width,
|
|
1856
|
-
height: resolved.target.height
|
|
1874
|
+
height: resolved.target.height,
|
|
1875
|
+
source: "lease"
|
|
1857
1876
|
});
|
|
1858
1877
|
} catch (err) {
|
|
1859
1878
|
this.nativeCropMisses++;
|
|
@@ -1885,7 +1904,8 @@ var DecodeWorkerChild = class {
|
|
|
1885
1904
|
requestId,
|
|
1886
1905
|
bytes: served.bytes,
|
|
1887
1906
|
width: served.width,
|
|
1888
|
-
height: served.height
|
|
1907
|
+
height: served.height,
|
|
1908
|
+
source: "tile"
|
|
1889
1909
|
});
|
|
1890
1910
|
return;
|
|
1891
1911
|
}
|
|
@@ -2576,7 +2596,7 @@ var DecodeWorkerChild = class {
|
|
|
2576
2596
|
if (!nav || !C) throw new Error("node-av not initialized");
|
|
2577
2597
|
const region = resolveCropRegion(frame.width, frame.height, opts?.crop);
|
|
2578
2598
|
const target = resolveResizeTarget(region.width, region.height, opts?.resize);
|
|
2579
|
-
const format = opts?.format ?? this.sessionFormat;
|
|
2599
|
+
const format = (opts?.format ?? this.sessionFormat) === "gray" ? "gray" : "rgb";
|
|
2580
2600
|
const dstFmt = format === "gray" ? C.AV_PIX_FMT_GRAY8 : C.AV_PIX_FMT_RGB24;
|
|
2581
2601
|
const channels = format === "gray" ? 1 : 3;
|
|
2582
2602
|
this.logColorProvenance(isFullFrameRegion(region, frame.width, frame.height) ? "sw-fullframe" : "sw-crop", this.resolveFrameColor(frame));
|
|
@@ -2649,7 +2669,7 @@ var DecodeWorkerChild = class {
|
|
|
2649
2669
|
try {
|
|
2650
2670
|
const region = resolveCropRegion(frame.width, frame.height, opts?.crop);
|
|
2651
2671
|
const target = resolveResizeTarget(region.width, region.height, opts?.resize);
|
|
2652
|
-
const format = opts?.format ?? this.sessionFormat;
|
|
2672
|
+
const format = (opts?.format ?? this.sessionFormat) === "gray" ? "gray" : "rgb";
|
|
2653
2673
|
const channels = format === "gray" ? 1 : 3;
|
|
2654
2674
|
const color = this.resolveFrameColor(frame);
|
|
2655
2675
|
this.logColorProvenance(isFullFrameRegion(region, frame.width, frame.height) ? "hw-fullframe" : "hw-crop", color);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-
|
|
1
|
+
import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-DyocTIx_.mjs";
|
|
2
2
|
import { n as setSharpWarnSink, r as hostExternalEntryUrls, t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
|
|
3
3
|
//#region src/session-decode/color-conversion.ts
|
|
4
4
|
/**
|
|
@@ -1809,18 +1809,36 @@ var DecodeWorkerChild = class {
|
|
|
1809
1809
|
this.pendingPull = true;
|
|
1810
1810
|
this.frames.markPendingPull();
|
|
1811
1811
|
}
|
|
1812
|
-
handleToBuffer(frameId, opts) {
|
|
1813
|
-
const frame = this.frames.toBuffer(frameId);
|
|
1812
|
+
async handleToBuffer(frameId, opts) {
|
|
1813
|
+
const frame = this.leaseStore.get(frameId)?.frame ?? this.frames.toBuffer(frameId);
|
|
1814
1814
|
if (!frame) {
|
|
1815
1815
|
this.sendError(`decode-worker-child: toBuffer for unknown or stale frameId ${frameId}`, frameId);
|
|
1816
1816
|
return;
|
|
1817
1817
|
}
|
|
1818
1818
|
try {
|
|
1819
|
-
const
|
|
1819
|
+
const jpeg = opts?.format === "jpeg";
|
|
1820
|
+
const rawOpts = jpeg ? {
|
|
1821
|
+
...opts,
|
|
1822
|
+
format: "rgb"
|
|
1823
|
+
} : opts;
|
|
1824
|
+
const bytes = frame.isHwFrame() ? this.hwScaleToBuffer(frame, rawOpts) : this.scaleRoiToBuffer(frame, rawOpts);
|
|
1825
|
+
const output = jpeg ? await (async () => {
|
|
1826
|
+
const region = resolveCropRegion(frame.width, frame.height, opts?.crop);
|
|
1827
|
+
const target = resolveResizeTarget(region.width, region.height, opts?.resize);
|
|
1828
|
+
const sharp = await getSharp();
|
|
1829
|
+
return new Uint8Array(await sharp(bytes, { raw: {
|
|
1830
|
+
width: target.width,
|
|
1831
|
+
height: target.height,
|
|
1832
|
+
channels: 3
|
|
1833
|
+
} }).jpeg({
|
|
1834
|
+
quality: 85,
|
|
1835
|
+
mozjpeg: false
|
|
1836
|
+
}).toBuffer());
|
|
1837
|
+
})() : bytes;
|
|
1820
1838
|
this.send({
|
|
1821
1839
|
kind: "buffer",
|
|
1822
1840
|
frameId,
|
|
1823
|
-
bytes
|
|
1841
|
+
bytes: output
|
|
1824
1842
|
});
|
|
1825
1843
|
} catch (err) {
|
|
1826
1844
|
this.sendError(`decode-worker-child: toBuffer failed — ${errMessage(err)}`, frameId);
|
|
@@ -1852,7 +1870,8 @@ var DecodeWorkerChild = class {
|
|
|
1852
1870
|
requestId,
|
|
1853
1871
|
bytes,
|
|
1854
1872
|
width: resolved.target.width,
|
|
1855
|
-
height: resolved.target.height
|
|
1873
|
+
height: resolved.target.height,
|
|
1874
|
+
source: "lease"
|
|
1856
1875
|
});
|
|
1857
1876
|
} catch (err) {
|
|
1858
1877
|
this.nativeCropMisses++;
|
|
@@ -1884,7 +1903,8 @@ var DecodeWorkerChild = class {
|
|
|
1884
1903
|
requestId,
|
|
1885
1904
|
bytes: served.bytes,
|
|
1886
1905
|
width: served.width,
|
|
1887
|
-
height: served.height
|
|
1906
|
+
height: served.height,
|
|
1907
|
+
source: "tile"
|
|
1888
1908
|
});
|
|
1889
1909
|
return;
|
|
1890
1910
|
}
|
|
@@ -2575,7 +2595,7 @@ var DecodeWorkerChild = class {
|
|
|
2575
2595
|
if (!nav || !C) throw new Error("node-av not initialized");
|
|
2576
2596
|
const region = resolveCropRegion(frame.width, frame.height, opts?.crop);
|
|
2577
2597
|
const target = resolveResizeTarget(region.width, region.height, opts?.resize);
|
|
2578
|
-
const format = opts?.format ?? this.sessionFormat;
|
|
2598
|
+
const format = (opts?.format ?? this.sessionFormat) === "gray" ? "gray" : "rgb";
|
|
2579
2599
|
const dstFmt = format === "gray" ? C.AV_PIX_FMT_GRAY8 : C.AV_PIX_FMT_RGB24;
|
|
2580
2600
|
const channels = format === "gray" ? 1 : 3;
|
|
2581
2601
|
this.logColorProvenance(isFullFrameRegion(region, frame.width, frame.height) ? "sw-fullframe" : "sw-crop", this.resolveFrameColor(frame));
|
|
@@ -2648,7 +2668,7 @@ var DecodeWorkerChild = class {
|
|
|
2648
2668
|
try {
|
|
2649
2669
|
const region = resolveCropRegion(frame.width, frame.height, opts?.crop);
|
|
2650
2670
|
const target = resolveResizeTarget(region.width, region.height, opts?.resize);
|
|
2651
|
-
const format = opts?.format ?? this.sessionFormat;
|
|
2671
|
+
const format = (opts?.format ?? this.sessionFormat) === "gray" ? "gray" : "rgb";
|
|
2652
2672
|
const channels = format === "gray" ? 1 : 3;
|
|
2653
2673
|
const color = this.resolveFrameColor(frame);
|
|
2654
2674
|
this.logColorProvenance(isFullFrameRegion(region, frame.width, frame.height) ? "hw-fullframe" : "hw-crop", color);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, p as c, r as l, s as u, t as d, u as f } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-
|
|
1
|
+
import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, p as c, r as l, s as u, t as d, u as f } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BAMyzCWw.mjs";
|
|
2
2
|
import { a as p, i as m, n as h, o as g, r as _, t as v } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.js-C9j-2lBe.mjs";
|
|
3
3
|
import { n as y, r as b, t as x } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-XO0-Pyu6.mjs";
|
|
4
|
-
import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-
|
|
4
|
+
import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B1LDFHY9.mjs";
|
|
5
5
|
import { n as C, t as w } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-BO7TIbJV.mjs";
|
|
6
6
|
//#region ../../node_modules/lucide-react/dist/esm/shared/src/utils.js
|
|
7
7
|
var T = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), E = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), D = (e) => {
|
|
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
|
|
|
3
3
|
var e = {
|
|
4
4
|
"@camstack/sdk": {
|
|
5
5
|
name: "@camstack/sdk",
|
|
6
|
-
version: "1.2.
|
|
6
|
+
version: "1.2.30",
|
|
7
7
|
scope: ["default"],
|
|
8
8
|
loaded: !1,
|
|
9
9
|
from: "addon_stream_broker_widgets",
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.2.
|
|
21
|
+
version: "1.2.99",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_stream_broker_widgets",
|
|
@@ -33,7 +33,7 @@ var e = {
|
|
|
33
33
|
},
|
|
34
34
|
"@camstack/ui-library": {
|
|
35
35
|
name: "@camstack/ui-library",
|
|
36
|
-
version: "1.2.
|
|
36
|
+
version: "1.2.68",
|
|
37
37
|
scope: ["default"],
|
|
38
38
|
loaded: !1,
|
|
39
39
|
from: "addon_stream_broker_widgets",
|