Dedup same cameras with different zones.
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user