diff --git a/src/camera-manager/engine.ts b/src/camera-manager/engine.ts index 833990be..baaf2161 100644 --- a/src/camera-manager/engine.ts +++ b/src/camera-manager/engine.ts @@ -22,6 +22,8 @@ import { CameraConfigs, Engine, CameraEndpoints, + MediaMetadataQuery, + MediaMetadataQueryResultsMap, } from './types'; export const CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT = 10000; @@ -106,7 +108,8 @@ export interface CameraManagerEngine { getMediaMetadata( hass: HomeAssistant, cameras: CameraConfigs, - ): Promise; + query: MediaMetadataQuery, + ): Promise; getCameraMetadata( hass: HomeAssistant, diff --git a/src/camera-manager/frigate/engine-frigate.ts b/src/camera-manager/frigate/engine-frigate.ts index 04db5054..7db8c105 100644 --- a/src/camera-manager/frigate/engine-frigate.ts +++ b/src/camera-manager/frigate/engine-frigate.ts @@ -4,7 +4,7 @@ import endOfHour from 'date-fns/endOfHour'; import startOfHour from 'date-fns/startOfHour'; import { CameraConfig, CardWideConfig } from '../../types'; import { ViewMedia } from '../../view/media'; -import { RequestCache, RecordingSegmentsCache } from '../cache'; +import { RecordingSegmentsCache, RequestCache } from '../cache'; import { CameraManagerEngine, CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT, @@ -19,9 +19,6 @@ import { EventQuery, EventQueryResults, EventQueryResultsMap, - FrigateEventQueryResults, - FrigateRecordingQueryResults, - FrigateRecordingSegmentsQueryResults, MediaMetadata, PartialEventQuery, PartialRecordingQuery, @@ -40,8 +37,16 @@ import { CameraConfigs, CameraEndpoints, CameraEndpoint, + MediaMetadataQuery, + MediaMetadataQueryResults, + MediaMetadataQueryResultsMap, } from '../types'; -import { FrigateRecording } from './types'; +import { + FrigateEventQueryResults, + FrigateRecordingQueryResults, + FrigateRecordingSegmentsQueryResults, + FrigateRecording, +} from './types'; import { getEvents, getEventSummary, @@ -76,6 +81,7 @@ import { GenericCameraManagerEngine } from '../generic/engine-generic'; const EVENT_REQUEST_CACHE_MAX_AGE_SECONDS = 60; const RECORDING_SUMMARY_REQUEST_CACHE_MAX_AGE_SECONDS = 60; +const MEDIA_METADATA_REQUEST_CACHE_AGE_SECONDS = 60; const CAMERA_BIRDSEYE = 'birdseye' as const; @@ -802,14 +808,26 @@ export class FrigateCameraManagerEngine public async getMediaMetadata( hass: HomeAssistant, cameras: CameraConfigs, - ): Promise { + query: MediaMetadataQuery, + ): Promise { + const output: MediaMetadataQueryResultsMap = new Map(); + if (this._requestCache.has(query)) { + const cachedResult = ( + this._requestCache.get(query) + ); + if (cachedResult) { + output.set(query, cachedResult as MediaMetadataQueryResults); + return output; + } + } + const what: Set = new Set(); const where: Set = new Set(); const days: Set = new Set(); const instances = this._buildInstanceToCameraIDMapFromQuery( cameras, - new Set(cameras.keys()), + query.cameraIDs, ); const processEventSummary = async ( @@ -865,14 +883,21 @@ export class FrigateCameraManagerEngine })(), ); - if (!what.size && !where.size && !days.size) { - return null; - } - return { - ...(what.size && { what: what }), - ...(where.size && { where: where }), - ...(days.size && { days: days }), + const result: MediaMetadataQueryResults = { + type: QueryResultsType.MediaMetadata, + engine: Engine.Frigate, + metadata: { + ...(what.size && { what: what }), + ...(where.size && { where: where }), + ...(days.size && { days: days }), + }, + expiry: add(new Date(), { seconds: MEDIA_METADATA_REQUEST_CACHE_AGE_SECONDS }), + cached: false, }; + + this._requestCache.set(query, { ...result, cached: true }, result.expiry); + output.set(query, result); + return output; } /** diff --git a/src/camera-manager/frigate/types.ts b/src/camera-manager/frigate/types.ts index a8184d3d..55440667 100644 --- a/src/camera-manager/frigate/types.ts +++ b/src/camera-manager/frigate/types.ts @@ -1,5 +1,11 @@ import { z } from 'zod'; import { dayToDate } from '../../utils/basic'; +import { + Engine, + EventQueryResults, + RecordingQueryResults, + RecordingSegmentsQueryResults, +} from '../types'; const dayStringToDate = (arg: unknown): Date | unknown => { return typeof arg === 'string' ? dayToDate(arg) : arg; @@ -66,3 +72,25 @@ export const eventSummarySchema = z }) .array(); export type EventSummary = z.infer; + +// ============================== +// Frigate concrete query results +// ============================== + +export interface FrigateEventQueryResults extends EventQueryResults { + engine: Engine.Frigate; + instanceID: string; + events: FrigateEvent[]; +} + +export interface FrigateRecordingQueryResults extends RecordingQueryResults { + engine: Engine.Frigate; + instanceID: string; + recordings: FrigateRecording[]; +} + +export interface FrigateRecordingSegmentsQueryResults + extends RecordingSegmentsQueryResults { + engine: Engine.Frigate; + instanceID: string; +} diff --git a/src/camera-manager/generic/engine-generic.ts b/src/camera-manager/generic/engine-generic.ts index 3a990414..999665e5 100644 --- a/src/camera-manager/generic/engine-generic.ts +++ b/src/camera-manager/generic/engine-generic.ts @@ -23,6 +23,8 @@ import { CameraManagerCameraCapabilities, Engine, CameraEndpoints, + MediaMetadataQuery, + MediaMetadataQueryResultsMap, } from '../types'; import { getEntityIcon, getEntityTitle } from '../../utils/ha'; import { EntityRegistryManager } from '../../utils/ha/entity-registry'; @@ -139,7 +141,8 @@ export class GenericCameraManagerEngine implements CameraManagerEngine { public async getMediaMetadata( _hass: HomeAssistant, _cameras: CameraConfigs, - ): Promise { + _query: MediaMetadataQuery, + ): Promise { return null; } diff --git a/src/camera-manager/manager.ts b/src/camera-manager/manager.ts index 1a707984..70be592c 100644 --- a/src/camera-manager/manager.ts +++ b/src/camera-manager/manager.ts @@ -31,6 +31,8 @@ import { ResultsMap, CameraEndpoints, Engine, + MediaMetadataQuery, + MediaMetadataQueryResults, } from './types.js'; import orderBy from 'lodash-es/orderBy'; import { CameraManagerEngineFactory } from './engine-factory.js'; @@ -62,6 +64,11 @@ class QueryClassifier { ): query is RecordingSegmentsQuery { return query.type === QueryType.RecordingSegments; } + public static isMediaMetadataQuery( + query: DataQuery | PartialDataQuery, + ): query is MediaMetadataQuery { + return query.type === QueryType.MediaMetadata; + } } class QueryResultClassifier { @@ -80,6 +87,11 @@ class QueryResultClassifier { ): queryResults is RecordingSegmentsQueryResults { return queryResults.type === QueryResultsType.RecordingSegments; } + public static isMediaMetadataQuery( + queryResults: QueryResults, + ): queryResults is MediaMetadataQueryResults { + return queryResults.type === QueryResultsType.MediaMetadata; + } } export interface ExtendedMediaQueryResult { @@ -268,27 +280,24 @@ export class CameraManager { const where: Set = new Set(); const days: Set = new Set(); - const engines = this._store.getAllEngines(); - - const processMetadata = async (engine: CameraManagerEngine): Promise => { - const engineMetadata = await engine.getMediaMetadata( - hass, - this._store.getCameras(), - ); - if (engineMetadata) { - if (engineMetadata.what) { - engineMetadata.what.forEach(what.add, what); - } - if (engineMetadata.where) { - engineMetadata.where.forEach(where.add, where); - } - if (engineMetadata.days) { - engineMetadata.days.forEach(days.add, days); - } - } + const query: MediaMetadataQuery = { + type: QueryType.MediaMetadata, + cameraIDs: this._store.getCameraIDs(), }; - await allPromises(engines, processMetadata); + const results = await this._handleQuery(hass, query); + + for (const [query, result] of results?.entries() ?? []) { + if (result.metadata.what) { + result.metadata.what.forEach(what.add, what); + } + if (result.metadata.where) { + result.metadata.where.forEach(where.add, where); + } + if (result.metadata.days) { + result.metadata.days.forEach(days.add, days); + } + } if (!what.size && !where.size && !days.size) { return null; @@ -569,6 +578,12 @@ export class CameraManager { this._store.getCameras(), query, )) as Map> | null; + } else if (QueryClassifier.isMediaMetadataQuery(query)) { + engineResult = (await engine.getMediaMetadata( + hass, + this._store.getCameras(), + query, + )) as Map> | null; } engineResult?.forEach((value, key) => results.set(key, value)); diff --git a/src/camera-manager/types.ts b/src/camera-manager/types.ts index 73ab4506..8f1ab6b5 100644 --- a/src/camera-manager/types.ts +++ b/src/camera-manager/types.ts @@ -1,6 +1,5 @@ import { CameraConfig, FrigateCardView } from '../types'; import { ViewMedia } from '../view/media'; -import { FrigateEvent, FrigateRecording } from './frigate/types'; // ==== // Base @@ -10,12 +9,14 @@ export enum QueryType { Event = 'event-query', Recording = 'recording-query', RecordingSegments = 'recording-segments-query', + MediaMetadata = 'media-metadata', } export enum QueryResultsType { Event = 'event-results', Recording = 'recording-results', RecordingSegments = 'recording-segments-results', + MediaMetadata = 'media-metadata-results', } export enum Engine { @@ -65,6 +66,8 @@ export type QueryReturnType = QT extends EventQuery ? RecordingQueryResults : QT extends RecordingSegmentsQuery ? RecordingSegmentsQueryResults + : QT extends MediaMetadataQuery + ? MediaMetadataQueryResults : never; export type PartialQueryConcreteType = PQT extends PartialEventQuery ? EventQuery @@ -78,6 +81,7 @@ export type ResultsMap = Map>; export type EventQueryResultsMap = ResultsMap; export type RecordingQueryResultsMap = ResultsMap; export type RecordingSegmentsQueryResultsMap = ResultsMap; +export type MediaMetadataQueryResultsMap = ResultsMap; export interface MediaMetadata { where?: Set; @@ -176,24 +180,15 @@ export interface RecordingSegmentsQueryResults extends QueryResults { segments: RecordingSegment[]; } -// ======================== -// Frigate concrete results -// ======================== +// ==================== +// Media metadata Query +// ==================== -export interface FrigateEventQueryResults extends EventQueryResults { - engine: Engine.Frigate; - instanceID: string; - events: FrigateEvent[]; +export interface MediaMetadataQuery extends DataQuery { + type: QueryType.MediaMetadata; } -export interface FrigateRecordingQueryResults extends RecordingQueryResults { - engine: Engine.Frigate; - instanceID: string; - recordings: FrigateRecording[]; -} - -export interface FrigateRecordingSegmentsQueryResults - extends RecordingSegmentsQueryResults { - engine: Engine.Frigate; - instanceID: string; +export interface MediaMetadataQueryResults extends QueryResults { + type: QueryResultsType.MediaMetadata; + metadata: MediaMetadata; }