Fix multi-sensor cameras collapsing into one tile
Channels were keyed solely on the metadata `Name` field. Multi-sensor cameras (e.g. Avigilon 32C-H5A) export one file per sensor that share an identical `Name` and `Serial`, so their sensors merged into a single tile and one sensor's clips were lost to start-time dedup — surfacing as "only 3 of 4 cameras loaded". Add cameraIdFromFile(), which derives a per-sensor id from the export filename prefix (stripping the Alta timestamp, e.g. "Backyard_4-...Z.mp4" -> "Backyard 4"), and key channels by it, falling back to the metadata Name for non-Alta filenames. Verified: all 4 sensors now load as distinct tiles (4 cameras | 13 clips). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+16
-2
@@ -1623,6 +1623,20 @@
|
|||||||
return name.replace(/\.(mp4|mov|avi|mkv|txt)$/i, '');
|
return name.replace(/\.(mp4|mov|avi|mkv|txt)$/i, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Derive a per-sensor camera id from an Alta export filename by stripping the
|
||||||
|
// trailing timestamp, e.g. "Backyard_4-2026-06-03T10-02-48.000Z.mp4" -> "Backyard 4".
|
||||||
|
// Multi-sensor cameras (e.g. Avigilon 32C-H5A) export one file per sensor that
|
||||||
|
// share the SAME metadata Name and Serial, so the filename prefix is the only
|
||||||
|
// thing distinguishing the sensors. Keying channels by it stops distinct sensors
|
||||||
|
// from collapsing into one tile. Returns '' when no Alta timestamp is present so
|
||||||
|
// callers can fall back to the metadata Name.
|
||||||
|
function cameraIdFromFile(fileName) {
|
||||||
|
const base = baseName(fileName);
|
||||||
|
const stripped = base.replace(/-\d{4}-\d{2}-\d{2}T[\d\-.]+Z?$/i, '');
|
||||||
|
if (stripped === base) return '';
|
||||||
|
return stripped.replace(/_/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Channel / Segment Management ───
|
// ─── Channel / Segment Management ───
|
||||||
|
|
||||||
function getOrCreateChannel(name) {
|
function getOrCreateChannel(name) {
|
||||||
@@ -1700,7 +1714,7 @@
|
|||||||
for (let mi = pendingMetas.length - 1; mi >= 0; mi--) {
|
for (let mi = pendingMetas.length - 1; mi >= 0; mi--) {
|
||||||
const m = pendingMetas[mi];
|
const m = pendingMetas[mi];
|
||||||
if (vBase === baseName(m.fileName)) {
|
if (vBase === baseName(m.fileName)) {
|
||||||
const cameraName = m.meta['Name'] || `Camera ${channels.size + 1}`;
|
const cameraName = cameraIdFromFile(v.fileName) || m.meta['Name'] || `Camera ${channels.size + 1}`;
|
||||||
addSegment(cameraName, m.meta, v.blob);
|
addSegment(cameraName, m.meta, v.blob);
|
||||||
pendingVideos.splice(vi, 1);
|
pendingVideos.splice(vi, 1);
|
||||||
pendingMetas.splice(mi, 1);
|
pendingMetas.splice(mi, 1);
|
||||||
@@ -1717,7 +1731,7 @@
|
|||||||
|
|
||||||
// Remaining videos without metadata
|
// Remaining videos without metadata
|
||||||
for (const v of pendingVideos) {
|
for (const v of pendingVideos) {
|
||||||
const name = baseName(v.fileName).replace(/_/g, ' ');
|
const name = cameraIdFromFile(v.fileName) || baseName(v.fileName).replace(/_/g, ' ');
|
||||||
const ch = getOrCreateChannel(name);
|
const ch = getOrCreateChannel(name);
|
||||||
const url = URL.createObjectURL(v.blob);
|
const url = URL.createObjectURL(v.blob);
|
||||||
ch.segments.push({ startTime: null, endTime: null, url, blob: v.blob, meta: {}, videoEl: null, loaded: false });
|
ch.segments.push({ startTime: null, endTime: null, url, blob: v.blob, meta: {}, videoEl: null, loaded: false });
|
||||||
|
|||||||
Reference in New Issue
Block a user