Dedup same cameras with different zones.

This commit is contained in:
Dermot Duffy
2022-10-02 19:30:16 -07:00
parent 33e0c0da40
commit 72dca1b712
6 changed files with 83 additions and 39 deletions
+2 -2
View File
@@ -946,7 +946,7 @@ This card supports several different views:
|`snapshot`|Shows a Media viewer for the most recent snapshot for this camera/zone/label. Can also be accessed by holding down the `snapshots` menu icon.| |`snapshot`|Shows a Media viewer for the most recent snapshot for this camera/zone/label. Can also be accessed by holding down the `snapshots` menu icon.|
|`clips`|Shows an event gallery of clips for this camera/zone/label.| |`clips`|Shows an event gallery of clips for this camera/zone/label.|
|`clip`|Shows a Media viewer for the most recent clip for this camera/zone/label. Can also be accessed by holding down the `clips` menu icon.| |`clip`|Shows a Media viewer for the most recent clip for this camera/zone/label. Can also be accessed by holding down the `clips` menu icon.|
|`recordings`|Shows a gallery of recordings for this camera.| |`recordings`|Shows a gallery of recent (last day) recordings for this camera and its dependents.|
|`recording`|Shows a Media viewer for the most recent recording for this camera. Can also be accessed by holding down the `recordings` menu icon.| |`recording`|Shows a Media viewer for the most recent recording for this camera. Can also be accessed by holding down the `recordings` menu icon.|
|`image`|Shows a static image specified by the `image` parameter, can be used as a discrete default view or a screensaver (via `view.timeout_seconds`).| |`image`|Shows a static image specified by the `image` parameter, can be used as a discrete default view or a screensaver (via `view.timeout_seconds`).|
@@ -2693,7 +2693,7 @@ menu:
### Using a dependent camera ### Using a dependent camera
`dependencies.cameras` allows events for other cameras to be shown along with the currently selected camera. For example, this can be used to show events with the `birdseye` camera (since it will not have events of its own). `dependencies.cameras` allows events/recordings for other cameras to be shown along with the currently selected camera. For example, this can be used to show events with the `birdseye` camera (since it will not have events of its own).
<details> <details>
<summary>Expand: Using dependent cameras with birdseye</summary> <summary>Expand: Using dependent cameras with birdseye</summary>
+2 -2
View File
@@ -62,7 +62,7 @@ import {
} from '../utils/media-to-view'; } from '../utils/media-to-view';
import { import {
FrigateCardTimelineItem, FrigateCardTimelineItem,
sortTimelineItemsYoungestToOldest, sortYoungestToOldest,
TimelineDataManager, TimelineDataManager,
} from '../utils/timeline-data-manager'; } from '../utils/timeline-data-manager';
import { View } from '../view'; import { View } from '../view';
@@ -619,7 +619,7 @@ export class FrigateCardTimelineCore extends LitElement {
this._dataview this._dataview
?.get({ ?.get({
filter: (item) => item.type !== 'background', filter: (item) => item.type !== 'background',
order: sortTimelineItemsYoungestToOldest, order: sortYoungestToOldest,
}) })
.forEach((item) => { .forEach((item) => {
const cameraID = item.group ? String(item.group) : null; const cameraID = item.group ? String(item.group) : null;
+31
View File
@@ -107,3 +107,34 @@ export const getAllDependentCameras = (
} }
return cameraIDs; return cameraIDs;
}; };
/**
* Return the cameraIDs of truly unique cameras (some configured cameras may be
* the same Frigate came but with different zone/labels).
* @param cameras The full set of cameras.
* @param cameraIDs The specific IDs to dedup.
*/
export const getTrueCameras = (
cameras: Map<string, CameraConfig>,
cameraIDs: Set<string>,
): Set<string> => {
const getTrueCameraID = (cameraConfig: CameraConfig): string => {
return `${cameraConfig.frigate?.client_id ?? ''}/${
cameraConfig.frigate.camera_name ?? ''
}`;
};
const output = new Set<string>();
const visitedTrueCameras = new Set<string>();
cameraIDs.forEach((cameraID: string) => {
const cameraConfig = cameras.get(cameraID) ?? null;
if (cameraConfig && cameraConfig.frigate.camera_name) {
const trueCameraID = getTrueCameraID(cameraConfig);
if (!visitedTrueCameras.has(trueCameraID)) {
output.add(cameraID);
visitedTrueCameras.add(trueCameraID);
}
}
});
return output;
};
+30 -21
View File
@@ -183,27 +183,7 @@ export const mergeFrigateBrowseMediaSources = async (
} }
} }
const eventSort = ( return createEventParentForChildren('Merged events', children.sort(sortYoungestToOldest));
a: FrigateBrowseMediaSource,
b: FrigateBrowseMediaSource,
): number => {
if (
!a.frigate?.event ||
(b.frigate?.event && b.frigate.event.start_time > a.frigate.event.start_time)
) {
return 1;
}
if (
!b.frigate?.event ||
(a.frigate?.event && b.frigate.event.start_time < a.frigate.event.start_time)
) {
return -1;
}
return 0;
};
return createEventParentForChildren('Merged events', children.sort(eventSort));
}; };
/** /**
@@ -445,3 +425,32 @@ export const createChild = (
} }
return result; return result;
}; };
/**
* Sort the timeline items most recent to least recent.
* @param a The first item.
* @param b The second item.
* @returns -1, 0, 1 (standard array sort function configuration).
*/
export const sortYoungestToOldest = (
a: FrigateBrowseMediaSource,
b: FrigateBrowseMediaSource,
): number => {
const a_source = a.frigate?.event ?? a.frigate?.recording;
const b_source = b.frigate?.event ?? b.frigate?.recording;
if (
!a_source ||
(b_source && b_source.start_time > a_source.start_time)
) {
return 1;
}
if (
!b_source ||
(a_source && b_source.start_time < a_source.start_time)
) {
return -1;
}
return 0;
};
+11 -7
View File
@@ -6,13 +6,13 @@ import { CameraConfig, ExtendedHomeAssistant, FrigateBrowseMediaSource } from '.
import { View } from '../view'; import { View } from '../view';
import { formatDateAndTime, prettifyTitle } from './basic'; import { formatDateAndTime, prettifyTitle } from './basic';
import { getRecordingMediaContentID } from './frigate'; import { getRecordingMediaContentID } from './frigate';
import { createChild, createEventParentForChildren } from './ha/browse-media'; import { createChild, createEventParentForChildren, sortYoungestToOldest } from './ha/browse-media';
import { import {
RecordingSegmentsItem, RecordingSegmentsItem,
sortSegmentsOldestToYoungest, sortOldestToYoungest,
TimelineDataManager, TimelineDataManager,
} from './timeline-data-manager'; } from './timeline-data-manager';
import { getAllDependentCameras } from './camera.js'; import { getAllDependentCameras, getTrueCameras } from './camera.js';
/** /**
* Change the view to a recent recording. * Change the view to a recent recording.
@@ -132,8 +132,10 @@ const createRecordingChildren = (
): FrigateBrowseMediaSource[] => { ): FrigateBrowseMediaSource[] => {
const children: FrigateBrowseMediaSource[] = []; const children: FrigateBrowseMediaSource[] = [];
for (const cameraID of cameraIDs) { for (const cameraID of getTrueCameras(
const config = cameras.get(cameraID); cameras, cameraIDs
)) {
const config = cameras.get(cameraID) ?? null;
const recordingSummary = dataManager.getRecordingSummaryForCamera(cameraID); const recordingSummary = dataManager.getRecordingSummaryForCamera(cameraID);
if (!config?.frigate.camera_name || !recordingSummary) { if (!config?.frigate.camera_name || !recordingSummary) {
continue; continue;
@@ -175,7 +177,9 @@ const createRecordingChildren = (
} }
} }
} }
return children; // Sort the events by time (to align recordings for different cameras at the
// same time).
return children.sort(sortYoungestToOldest);
}; };
/** /**
@@ -208,7 +212,7 @@ export const generateMediaViewerContextForChildren = (
segment.cameraID === child.frigate?.cameraID && segment.cameraID === child.frigate?.cameraID &&
segment.start >= start && segment.start >= start &&
segment.end <= end, segment.end <= end,
order: sortSegmentsOldestToYoungest, order: sortOldestToYoungest,
}); });
seekSeconds = getSeekTimeInSegments( seekSeconds = getSeekTimeInSegments(
// Recordings start from the top of the hour. // Recordings start from the top of the hour.
+7 -7
View File
@@ -49,9 +49,9 @@ export interface RecordingSegmentsItem {
* @param b The second item. * @param b The second item.
* @returns -1, 0, 1 (standard array sort function configuration). * @returns -1, 0, 1 (standard array sort function configuration).
*/ */
export const sortTimelineItemsYoungestToOldest = ( export const sortYoungestToOldest = (
a: FrigateCardTimelineItem, a: RecordingSegmentsItem | FrigateCardTimelineItem,
b: FrigateCardTimelineItem, b: RecordingSegmentsItem | FrigateCardTimelineItem,
): number => { ): number => {
if (a.start < b.start) { if (a.start < b.start) {
return 1; return 1;
@@ -68,9 +68,9 @@ export const sortTimelineItemsYoungestToOldest = (
* @param b The second item. * @param b The second item.
* @returns -1, 0, 1 (standard array sort function configuration). * @returns -1, 0, 1 (standard array sort function configuration).
*/ */
export const sortSegmentsOldestToYoungest = ( export const sortOldestToYoungest = (
a: RecordingSegmentsItem, a: RecordingSegmentsItem | FrigateCardTimelineItem,
b: RecordingSegmentsItem, b: RecordingSegmentsItem | FrigateCardTimelineItem,
): number => { ): number => {
if (a.start < b.start) { if (a.start < b.start) {
return -1; return -1;
@@ -438,7 +438,7 @@ export class TimelineDataManager {
this._cameras.forEach((_, cameraID) => { this._cameras.forEach((_, cameraID) => {
const segments = this._recordingSegments.get({ const segments = this._recordingSegments.get({
filter: (item) => item.cameraID === cameraID, filter: (item) => item.cameraID === cameraID,
order: sortSegmentsOldestToYoungest, order: sortOldestToYoungest,
}); });
let current: RecordingSegmentsItem | null = null; let current: RecordingSegmentsItem | null = null;
for (let i = 0; i < segments.length; ++i) { for (let i = 0; i < segments.length; ++i) {