From e08cb95b1697f0a42627f9938f175b0568c8cb0f Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Mon, 2 Jan 2023 21:52:25 -0800 Subject: [PATCH] Move frigate util under the camera manager hierarchy. --- src/camera/cache.ts | 6 +- src/camera/frigate/engine-frigate.ts | 16 +- src/camera/frigate/frigate.ts | 265 --------------------------- src/camera/frigate/icon.ts | 20 ++ src/camera/frigate/requests.ts | 128 +++++++++++++ src/camera/frigate/schema.ts | 33 ++++ src/camera/frigate/util.ts | 84 +++++++++ src/camera/types.ts | 9 +- src/components/menu.ts | 2 +- src/editor.ts | 2 +- src/types.ts | 7 + src/utils/media-to-view.ts | 5 +- src/utils/timeline-source.ts | 7 +- src/view-media.ts | 2 +- 14 files changed, 289 insertions(+), 297 deletions(-) delete mode 100644 src/camera/frigate/frigate.ts create mode 100644 src/camera/frigate/icon.ts create mode 100644 src/camera/frigate/requests.ts create mode 100644 src/camera/frigate/schema.ts create mode 100644 src/camera/frigate/util.ts diff --git a/src/camera/cache.ts b/src/camera/cache.ts index 5ae93c3f..9679d31b 100644 --- a/src/camera/cache.ts +++ b/src/camera/cache.ts @@ -1,7 +1,7 @@ import isEqual from 'lodash-es/isEqual'; import orderBy from 'lodash-es/orderBy'; import sortedUniqBy from 'lodash-es/sortedUniqBy'; -import { RecordingSegment, RecordingSegments } from './frigate/frigate'; +import { RecordingSegment } from '../types'; import { DateRange, MemoryRangeSet } from './range'; import { DataQuery, QueryResults } from './types'; @@ -108,7 +108,7 @@ export class MemoryRangedCache { export class RecordingSegmentsCache { protected _segments: Map> = new Map(); - public add(cameraID: string, range: DateRange, segments: RecordingSegments) { + public add(cameraID: string, range: DateRange, segments: RecordingSegment[]) { let cameraSegmentCache: MemoryRangedCache | undefined = this._segments.get(cameraID); if (!cameraSegmentCache) { @@ -125,7 +125,7 @@ export class RecordingSegmentsCache { return !!this._segments.get(cameraID)?.hasCoverage(range); } - public get(cameraID: string, range: DateRange): RecordingSegments | null { + public get(cameraID: string, range: DateRange): RecordingSegment[] | null { return this._segments.get(cameraID)?.get(range) ?? null; } } diff --git a/src/camera/frigate/engine-frigate.ts b/src/camera/frigate/engine-frigate.ts index ea2a7332..d7a955fb 100644 --- a/src/camera/frigate/engine-frigate.ts +++ b/src/camera/frigate/engine-frigate.ts @@ -4,20 +4,10 @@ import endOfHour from 'date-fns/endOfHour'; import getUnixTime from 'date-fns/getUnixTime'; import startOfHour from 'date-fns/startOfHour'; import { CAMERA_BIRDSEYE } from '../../const'; -import { CameraConfig, FrigateRecording } from '../../types'; +import { CameraConfig, FrigateRecording, RecordingSegment } from '../../types'; import { MediaQueries, MediaQueriesResults } from '../../view'; import { ViewMedia, ViewMediaClassifier, ViewMediaFactory } from '../../view-media'; import { errorToConsole } from '../../utils/basic'; -import { - getEvents, - getRecordingSegments, - getRecordingsSummary, - NativeFrigateEventQuery, - NativeFrigateRecordingSegmentsQuery, - RecordingSegments, - RecordingSummary, - retainEvent, -} from './frigate'; import { RecordingSegmentsCache } from '../cache'; import { CameraManagerEngine, @@ -41,6 +31,8 @@ import { RecordingQuery, RecordingSegmentsQuery, } from '../types'; +import { RecordingSummary } from './schema'; +import { getEvents, getRecordingSegments, getRecordingsSummary, NativeFrigateEventQuery, NativeFrigateRecordingSegmentsQuery, retainEvent } from './requests'; const EVENT_REQUEST_CACHE_MAX_AGE_SECONDS = 60; const RECORDING_SUMMARY_REQUEST_CACHE_MAX_AGE_SECONDS = 60; @@ -290,7 +282,7 @@ export class FrigateCameraManagerEngine implements CameraManagerEngine { before: Math.floor(query.end.getTime() / 1000), }; - let segments: RecordingSegments; + let segments: RecordingSegment[]; try { segments = await getRecordingSegments(hass, request); } catch (e) { diff --git a/src/camera/frigate/frigate.ts b/src/camera/frigate/frigate.ts deleted file mode 100644 index bf6ddf08..00000000 --- a/src/camera/frigate/frigate.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { HomeAssistant } from 'custom-card-helpers'; -import utcToZonedTime from 'date-fns-tz/utcToZonedTime'; -import fromUnixTime from 'date-fns/fromUnixTime'; -import { z } from 'zod'; -import { localize } from '../../localize/localize'; -import { - ClipsOrSnapshots, - FrigateCardError, - FrigateEvent, - FrigateEvents, - frigateEventsSchema, - FrigateRecording, -} from '../../types'; -import { formatDateAndTime, prettifyTitle } from '../../utils/basic'; -import { homeAssistantWSRequest } from '../../utils/ha'; - -export const FRIGATE_ICON_SVG_PATH = - 'm 4.8759466,22.743573 c 0.0866,0.69274 0.811811,1.16359 0.37885,1.27183 ' + - '-0.43297,0.10824 -2.32718,-3.43665 -2.7601492,-4.95202 -0.4329602,-1.51538 ' + - '-0.6764993,-3.22017 -0.5682593,-4.19434 0.1082301,-0.97417 5.7097085,-2.48955 ' + - '5.7097085,-2.89545 0,-0.4059 -1.81304,-0.0271 -1.89422,-0.35178 -0.0812,-0.32472 ' + - '1.36925,-0.12989 1.75892,-0.64945 0.60885,-0.81181 1.3800713,-0.6765 1.8671505,' + - '-1.1094696 0.4870902,-0.4329599 1.0824089,-2.0836399 1.1906589,-2.7871996 0.108241,' + - '-0.70357 -1.0824084,-1.51538 -1.4071389,-2.05658 -0.3247195,-0.54121 0.7035702,' + - '-0.92005 3.1931099,-1.94834 2.48954,-1.02829 10.39114,-3.30134994 10.49938,' + - '-3.03074994 0.10824,0.27061 -2.59779,1.40713994 -4.492,2.11069994 -1.89422,0.70357 ' + - '-4.97909,2.05658 -4.97909,2.43542 0,0.37885 0.16236,0.67651 0.0541,1.54244 -0.10824,' + - '0.86593 -0.12123,1.2702597 -0.32472,1.8400997 -0.1353,0.37884 -0.2706,1.27183 ' + - '0,2.0836295 0.21648,0.64945 0.92005,1.13653 1.24477,1.24478 0.2706,0.018 1.01746,' + - '0.0433 1.8401,0 1.02829,-0.0541 2.48954,0.0541 2.48954,0.32472 0,0.2706 -2.21894,' + - '0.10824 -2.21894,0.48708 0,0.37885 2.27306,-0.0541 2.21894,0.32473 -0.0541,0.37884 ' + - '-1.89422,0.21648 -2.86839,0.21648 -0.77933,0 -1.93031,-0.0361 -2.43542,-0.21648 ' + - 'l -0.10824,0.37884 c -0.18038,0 -0.55744,0.10824 -0.94711,0.10824 -0.48708,0 ' + - '-0.51414,0.16236 -1.40713,0.16236 -0.892989,0 -0.622391,-0.0541 -1.4341894,-0.10824 ' + - '-0.81181,-0.0541 -3.842561,2.27306 -4.383761,3.03075 -0.54121,0.75768 ' + - '-0.21649,2.59778 -0.21649,3.43665 0,0.75379 -0.10824,2.43542 0,3.30135 z'; - -const recordingSummaryHourSchema = z.object({ - hour: z.preprocess((arg) => Number(arg), z.number().min(0).max(23)), - duration: z.number().min(0), - events: z.number().min(0), -}); - -const recordingSummarySchema = z - .object({ - day: z.preprocess((arg) => { - // Must provide the hour:minute:second on parsing or Javascript will - // assume *UTC* midnight. - return typeof arg === 'string' ? new Date(`${arg}T00:00:00`) : arg; - }, z.date()), - events: z.number(), - hours: recordingSummaryHourSchema.array(), - }) - .array(); -export type RecordingSummary = z.infer; - -const recordingSegmentSchema = z.object({ - start_time: z.number(), - end_time: z.number(), - id: z.string(), -}); -export type RecordingSegment = z.infer; - -const recordingSegmentsSchema = recordingSegmentSchema.array(); -export type RecordingSegments = z.infer; - -const retainResultSchema = z.object({ - success: z.boolean(), - message: z.string(), -}); -export type RetainResult = z.infer; - -/** - * Get the recordings summary. May throw. - * @param hass The Home Assistant object. - * @param client_id The Frigate client_id. - * @param camera_name The Frigate camera name. - * @returns A RecordingSummary object. - */ -export const getRecordingsSummary = async ( - hass: HomeAssistant, - client_id: string, - camera_name: string, -): Promise => { - return await homeAssistantWSRequest( - hass, - recordingSummarySchema, - { - type: 'frigate/recordings/summary', - instance_id: client_id, - camera: camera_name, - }, - true, - ); -}; - -export interface NativeFrigateRecordingSegmentsQuery { - instance_id: string; - camera: string; - after: number; - before: number; -} - -/** - * Get the recording segments. May throw. - * @param hass The Home Assistant object. - * @param params The recording segment query parameters. - * @returns A RecordingSegments object. - */ -export const getRecordingSegments = async ( - hass: HomeAssistant, - params: NativeFrigateRecordingSegmentsQuery, -): Promise => { - return await homeAssistantWSRequest( - hass, - recordingSegmentsSchema, - { - type: 'frigate/recordings/get', - ...params, - }, - true, - ); -}; - -/** - * Request that Frigate retain an event. May throw. - * @param hass The HomeAssistant object. - * @param client_id The Frigate client_id. - * @param eventID The event ID to retain. - * @param retain `true` to retain or `false` to unretain. - */ -export async function retainEvent( - hass: HomeAssistant, - client_id: string, - eventID: string, - retain: boolean, -): Promise { - const retainRequest = { - type: 'frigate/event/retain', - instance_id: client_id, - event_id: eventID, - retain: retain, - }; - const response = await homeAssistantWSRequest( - hass, - retainResultSchema, - retainRequest, - true, - ); - if (!response.success) { - throw new FrigateCardError(localize('error.failed_retain'), { - request: retainRequest, - response: response, - }); - } -} - -export interface NativeFrigateEventQuery { - instance_id?: string; - camera?: string; - label?: string; - zone?: string; - after?: number; - before?: number; - limit?: number; - has_clip?: boolean; - has_snapshot?: boolean; -} - -/** - * Get events over websocket. May throw. - * @param hass The Home Assistant object. - * @param params The events search parameters. - * @returns An array of 'FrigateEvent's. - */ -export const getEvents = async ( - hass: HomeAssistant, - params?: NativeFrigateEventQuery, -): Promise => { - return await homeAssistantWSRequest( - hass, - frigateEventsSchema, - { - type: 'frigate/events/get', - ...params, - }, - true, - ); -}; - -/** - * Given an event generate a title. - * @param event - */ -export const getEventTitle = (event: FrigateEvent): string => { - const localTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const durationSeconds = Math.round( - event.end_time - ? event.end_time - event.start_time - : Date.now() / 1000 - event.start_time, - ); - return `${formatDateAndTime( - utcToZonedTime(event.start_time * 1000, localTimezone), - )} [${durationSeconds}s, ${prettifyTitle(event.label)} ${Math.round( - event.top_score * 100, - )}%]`; -}; - -export const getRecordingTitle = (recording: FrigateRecording): string => { - return `${prettifyTitle(recording.camera)} ${formatDateAndTime( - fromUnixTime(recording.start_time), - )}`; -}; - -/** - * Get a thumbnail URL for an event. - * @param clientId The Frigate client id. - * @param event The event. - * @returns A string URL. - */ -export const getEventThumbnailURL = (clientId: string, event: FrigateEvent): string => { - return `/api/frigate/${clientId}/thumbnail/${event.id}`; -}; - -/** - * Get a media content ID for an event. - * @param clientId The Frigate client id. - * @param cameraName The Frigate camera name. - * @param event The Frigate event. - * @param mediaType The media type required. - * @returns A string media content id. - */ -export const getEventMediaContentID = ( - clientId: string, - cameraName: string, - event: FrigateEvent, - mediaType: ClipsOrSnapshots, -): string => { - return `media-source://frigate/${clientId}/event/${mediaType}/${cameraName}/${event.id}`; -}; - -/** - * Generate a recording identifier. - * @param clientId The Frigate client id. - * @param cameraName The Frigate camera name. - * @param recording The Frigate recording. - * @returns A recording identifier. - */ -export const getRecordingMediaContentID = ( - clientId: string, - cameraName: string, - recording: FrigateRecording, -): string => { - const date = fromUnixTime(recording.start_time); - return [ - 'media-source://frigate', - clientId, - 'recordings', - cameraName, - `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String( - String(date.getDate()).padStart(2, '0'), - )}`, - String(date.getHours()).padStart(2, '0'), - ].join('/'); -}; diff --git a/src/camera/frigate/icon.ts b/src/camera/frigate/icon.ts new file mode 100644 index 00000000..3e075f2e --- /dev/null +++ b/src/camera/frigate/icon.ts @@ -0,0 +1,20 @@ +export const FRIGATE_ICON_SVG_PATH = + 'm 4.8759466,22.743573 c 0.0866,0.69274 0.811811,1.16359 0.37885,1.27183 ' + + '-0.43297,0.10824 -2.32718,-3.43665 -2.7601492,-4.95202 -0.4329602,-1.51538 ' + + '-0.6764993,-3.22017 -0.5682593,-4.19434 0.1082301,-0.97417 5.7097085,-2.48955 ' + + '5.7097085,-2.89545 0,-0.4059 -1.81304,-0.0271 -1.89422,-0.35178 -0.0812,-0.32472 ' + + '1.36925,-0.12989 1.75892,-0.64945 0.60885,-0.81181 1.3800713,-0.6765 1.8671505,' + + '-1.1094696 0.4870902,-0.4329599 1.0824089,-2.0836399 1.1906589,-2.7871996 0.108241,' + + '-0.70357 -1.0824084,-1.51538 -1.4071389,-2.05658 -0.3247195,-0.54121 0.7035702,' + + '-0.92005 3.1931099,-1.94834 2.48954,-1.02829 10.39114,-3.30134994 10.49938,' + + '-3.03074994 0.10824,0.27061 -2.59779,1.40713994 -4.492,2.11069994 -1.89422,0.70357 ' + + '-4.97909,2.05658 -4.97909,2.43542 0,0.37885 0.16236,0.67651 0.0541,1.54244 -0.10824,' + + '0.86593 -0.12123,1.2702597 -0.32472,1.8400997 -0.1353,0.37884 -0.2706,1.27183 ' + + '0,2.0836295 0.21648,0.64945 0.92005,1.13653 1.24477,1.24478 0.2706,0.018 1.01746,' + + '0.0433 1.8401,0 1.02829,-0.0541 2.48954,0.0541 2.48954,0.32472 0,0.2706 -2.21894,' + + '0.10824 -2.21894,0.48708 0,0.37885 2.27306,-0.0541 2.21894,0.32473 -0.0541,0.37884 ' + + '-1.89422,0.21648 -2.86839,0.21648 -0.77933,0 -1.93031,-0.0361 -2.43542,-0.21648 ' + + 'l -0.10824,0.37884 c -0.18038,0 -0.55744,0.10824 -0.94711,0.10824 -0.48708,0 ' + + '-0.51414,0.16236 -1.40713,0.16236 -0.892989,0 -0.622391,-0.0541 -1.4341894,-0.10824 ' + + '-0.81181,-0.0541 -3.842561,2.27306 -4.383761,3.03075 -0.54121,0.75768 ' + + '-0.21649,2.59778 -0.21649,3.43665 0,0.75379 -0.10824,2.43542 0,3.30135 z'; diff --git a/src/camera/frigate/requests.ts b/src/camera/frigate/requests.ts new file mode 100644 index 00000000..6f119f3a --- /dev/null +++ b/src/camera/frigate/requests.ts @@ -0,0 +1,128 @@ +import { HomeAssistant } from 'custom-card-helpers'; +import { localize } from '../../localize/localize'; +import { + FrigateCardError, + FrigateEvents, + frigateEventsSchema, + RecordingSegment, +} from '../../types'; +import { homeAssistantWSRequest } from '../../utils/ha'; +import { recordingSegmentsSchema, RecordingSummary, recordingSummarySchema, RetainResult, retainResultSchema } from './schema'; + +/** + * Get the recordings summary. May throw. + * @param hass The Home Assistant object. + * @param client_id The Frigate client_id. + * @param camera_name The Frigate camera name. + * @returns A RecordingSummary object. + */ +export const getRecordingsSummary = async ( + hass: HomeAssistant, + client_id: string, + camera_name: string, +): Promise => { + return await homeAssistantWSRequest( + hass, + recordingSummarySchema, + { + type: 'frigate/recordings/summary', + instance_id: client_id, + camera: camera_name, + }, + true, + ); +}; + +export interface NativeFrigateRecordingSegmentsQuery { + instance_id: string; + camera: string; + after: number; + before: number; +} + +/** + * Get the recording segments. May throw. + * @param hass The Home Assistant object. + * @param params The recording segment query parameters. + * @returns A RecordingSegments object. + */ +export const getRecordingSegments = async ( + hass: HomeAssistant, + params: NativeFrigateRecordingSegmentsQuery, +): Promise => { + return await homeAssistantWSRequest( + hass, + recordingSegmentsSchema, + { + type: 'frigate/recordings/get', + ...params, + }, + true, + ); +}; + +/** + * Request that Frigate retain an event. May throw. + * @param hass The HomeAssistant object. + * @param client_id The Frigate client_id. + * @param eventID The event ID to retain. + * @param retain `true` to retain or `false` to unretain. + */ +export async function retainEvent( + hass: HomeAssistant, + client_id: string, + eventID: string, + retain: boolean, +): Promise { + const retainRequest = { + type: 'frigate/event/retain', + instance_id: client_id, + event_id: eventID, + retain: retain, + }; + const response = await homeAssistantWSRequest( + hass, + retainResultSchema, + retainRequest, + true, + ); + if (!response.success) { + throw new FrigateCardError(localize('error.failed_retain'), { + request: retainRequest, + response: response, + }); + } +} + +export interface NativeFrigateEventQuery { + instance_id?: string; + camera?: string; + label?: string; + zone?: string; + after?: number; + before?: number; + limit?: number; + has_clip?: boolean; + has_snapshot?: boolean; +} + +/** + * Get events over websocket. May throw. + * @param hass The Home Assistant object. + * @param params The events search parameters. + * @returns An array of 'FrigateEvent's. + */ +export const getEvents = async ( + hass: HomeAssistant, + params?: NativeFrigateEventQuery, +): Promise => { + return await homeAssistantWSRequest( + hass, + frigateEventsSchema, + { + type: 'frigate/events/get', + ...params, + }, + true, + ); +}; diff --git a/src/camera/frigate/schema.ts b/src/camera/frigate/schema.ts new file mode 100644 index 00000000..599de6b3 --- /dev/null +++ b/src/camera/frigate/schema.ts @@ -0,0 +1,33 @@ +import { z } from 'zod'; + +const recordingSummaryHourSchema = z.object({ + hour: z.preprocess((arg) => Number(arg), z.number().min(0).max(23)), + duration: z.number().min(0), + events: z.number().min(0), +}); + +export const recordingSummarySchema = z + .object({ + day: z.preprocess((arg) => { + // Must provide the hour:minute:second on parsing or Javascript will + // assume *UTC* midnight. + return typeof arg === 'string' ? new Date(`${arg}T00:00:00`) : arg; + }, z.date()), + events: z.number(), + hours: recordingSummaryHourSchema.array(), + }) + .array(); +export type RecordingSummary = z.infer; + +const recordingSegmentSchema = z.object({ + start_time: z.number(), + end_time: z.number(), + id: z.string(), +}); +export const recordingSegmentsSchema = recordingSegmentSchema.array(); + +export const retainResultSchema = z.object({ + success: z.boolean(), + message: z.string(), +}); +export type RetainResult = z.infer; diff --git a/src/camera/frigate/util.ts b/src/camera/frigate/util.ts new file mode 100644 index 00000000..01728b4e --- /dev/null +++ b/src/camera/frigate/util.ts @@ -0,0 +1,84 @@ +import utcToZonedTime from 'date-fns-tz/utcToZonedTime'; +import fromUnixTime from 'date-fns/fromUnixTime'; +import { + ClipsOrSnapshots, + FrigateEvent, + FrigateRecording, +} from '../../types'; +import { formatDateAndTime, prettifyTitle } from '../../utils/basic'; + +/** + * Given an event generate a title. + * @param event + */ +export const getEventTitle = (event: FrigateEvent): string => { + const localTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const durationSeconds = Math.round( + event.end_time + ? event.end_time - event.start_time + : Date.now() / 1000 - event.start_time, + ); + return `${formatDateAndTime( + utcToZonedTime(event.start_time * 1000, localTimezone), + )} [${durationSeconds}s, ${prettifyTitle(event.label)} ${Math.round( + event.top_score * 100, + )}%]`; +}; + +export const getRecordingTitle = (recording: FrigateRecording): string => { + return `${prettifyTitle(recording.camera)} ${formatDateAndTime( + fromUnixTime(recording.start_time), + )}`; +}; + +/** + * Get a thumbnail URL for an event. + * @param clientId The Frigate client id. + * @param event The event. + * @returns A string URL. + */ +export const getEventThumbnailURL = (clientId: string, event: FrigateEvent): string => { + return `/api/frigate/${clientId}/thumbnail/${event.id}`; +}; + +/** + * Get a media content ID for an event. + * @param clientId The Frigate client id. + * @param cameraName The Frigate camera name. + * @param event The Frigate event. + * @param mediaType The media type required. + * @returns A string media content id. + */ +export const getEventMediaContentID = ( + clientId: string, + cameraName: string, + event: FrigateEvent, + mediaType: ClipsOrSnapshots, +): string => { + return `media-source://frigate/${clientId}/event/${mediaType}/${cameraName}/${event.id}`; +}; + +/** + * Generate a recording identifier. + * @param clientId The Frigate client id. + * @param cameraName The Frigate camera name. + * @param recording The Frigate recording. + * @returns A recording identifier. + */ +export const getRecordingMediaContentID = ( + clientId: string, + cameraName: string, + recording: FrigateRecording, +): string => { + const date = fromUnixTime(recording.start_time); + return [ + 'media-source://frigate', + clientId, + 'recordings', + cameraName, + `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String( + String(date.getDate()).padStart(2, '0'), + )}`, + String(date.getHours()).padStart(2, '0'), + ].join('/'); +}; diff --git a/src/camera/types.ts b/src/camera/types.ts index 846bd652..0cedc17b 100644 --- a/src/camera/types.ts +++ b/src/camera/types.ts @@ -1,5 +1,4 @@ -import { FrigateEvents, FrigateRecording } from '../types'; -import { RecordingSegments } from './frigate/frigate'; +import { FrigateEvents, FrigateRecording, RecordingSegment } from '../types'; // ==== // Base @@ -83,7 +82,6 @@ export interface EventQuery extends MediaQuery { } export type PartialEventQuery = Partial; -// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface EventQueryResults extends QueryResults { type: QueryResultsType.Event; } @@ -92,13 +90,11 @@ export interface EventQueryResults extends QueryResults { // Recording Query // =============== -// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface RecordingQuery extends MediaQuery { type: QueryType.Recording; } export type PartialRecordingQuery = Partial; -// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface RecordingQueryResults extends QueryResults { type: QueryResultsType.Recording; } @@ -111,11 +107,10 @@ export interface RecordingSegmentsQuery extends DataQuery, TimeBasedDataQuery { type: QueryType.RecordingSegments; } export type PartialRecordingSegmentsQuery = Partial; -//export type PartialRecordingSegmentsQuery = Partial & { type: QueryType.RecordingSegments }; export interface RecordingSegmentsQueryResults extends QueryResults { type: QueryResultsType.RecordingSegments; - segments: RecordingSegments; + segments: RecordingSegment[]; } // ======================== diff --git a/src/components/menu.ts b/src/components/menu.ts index 789deec8..758b1a93 100644 --- a/src/components/menu.ts +++ b/src/components/menu.ts @@ -27,7 +27,7 @@ import { frigateCardHasAction, getActionConfigGivenAction } from '../utils/action.js'; -import { FRIGATE_ICON_SVG_PATH } from '../camera/frigate/frigate.js'; +import { FRIGATE_ICON_SVG_PATH } from '../camera/frigate/icon.js'; import { refreshDynamicStateParameters } from '../utils/ha'; import './submenu.js'; diff --git a/src/editor.ts b/src/editor.ts index 0077cf25..d4389cd0 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -141,7 +141,7 @@ import { } from './types.js'; import { arrayMove } from './utils/basic.js'; import { getCameraID, getCameraTitle } from './utils/camera.js'; -import { FRIGATE_ICON_SVG_PATH } from './camera/frigate/frigate.js'; +import { FRIGATE_ICON_SVG_PATH } from './camera/frigate/icon.js'; import { getEntitiesFromHASS, sideLoadHomeAssistantElements } from './utils/ha'; import { setLowPerformanceProfile } from './performance.js'; diff --git a/src/types.ts b/src/types.ts index a6647bca..67875087 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1498,3 +1498,10 @@ export type ExtendedEntity = z.infer; export const entityListSchema = entitySchema.array(); export type EntityList = z.infer; + +// Generic recording segment type (inspired by Frigate recording segments). +export interface RecordingSegment { + start_time: number; + end_time: number; + id: string; +} diff --git a/src/utils/media-to-view.ts b/src/utils/media-to-view.ts index 9f301698..0e8633b4 100644 --- a/src/utils/media-to-view.ts +++ b/src/utils/media-to-view.ts @@ -3,9 +3,8 @@ import fromUnixTime from 'date-fns/fromUnixTime'; import startOfHour from 'date-fns/startOfHour'; import sub from 'date-fns/sub'; import { ViewContext } from 'view'; -import { CameraConfig, ClipsOrSnapshotsOrAll, FrigateCardView } from '../types'; +import { CameraConfig, ClipsOrSnapshotsOrAll, FrigateCardView, RecordingSegment } from '../types'; import { EventMediaQueries, RecordingMediaQueries, View } from '../view'; -import { RecordingSegments } from '../camera/frigate/frigate'; import { CameraManager } from '../camera/manager'; import { getAllDependentCameras } from './camera.js'; import { ViewMedia, ViewMediaClassifier } from '../view-media'; @@ -279,7 +278,7 @@ export const findClosestMediaIndex = ( const getSeekTimeInSegments = ( startTime: Date, targetTime: Date, - segments: RecordingSegments, + segments: RecordingSegment[], ): number | null => { if (!segments.length) { return null; diff --git a/src/utils/timeline-source.ts b/src/utils/timeline-source.ts index 18e401f2..3e3ee7b1 100644 --- a/src/utils/timeline-source.ts +++ b/src/utils/timeline-source.ts @@ -3,10 +3,9 @@ import add from 'date-fns/add'; import sub from 'date-fns/sub'; import { DataSet } from 'vis-data'; import { IdType, TimelineItem, TimelineWindow } from 'vis-timeline/esnext'; -import { CameraConfig, ClipsOrSnapshotsOrAll } from '../types'; +import { CameraConfig, ClipsOrSnapshotsOrAll, RecordingSegment } from '../types'; import { CameraManager } from '../camera/manager'; import { EventQuery } from '../camera/types'; -import { RecordingSegment, RecordingSegments } from '../camera/frigate/frigate'; import { capEndDate, convertRangeToCacheFriendlyTimes } from '../camera/util'; import { EventMediaQueries } from '../view'; import { ViewMedia } from '../view-media'; @@ -228,9 +227,9 @@ export class TimelineDataSource { const results = await this._cameraManager.getRecordingSegments(hass, queries); - const newSegments: Map = new Map(); + const newSegments: Map = new Map(); for (const [query, result] of results) { - let destination: RecordingSegments | undefined = newSegments.get(query.cameraID); + let destination: RecordingSegment[] | undefined = newSegments.get(query.cameraID); if (!destination) { destination = []; newSegments.set(query.cameraID, destination); diff --git a/src/view-media.ts b/src/view-media.ts index 84448d66..b63067bb 100644 --- a/src/view-media.ts +++ b/src/view-media.ts @@ -14,7 +14,7 @@ import { getEventTitle, getRecordingMediaContentID, getRecordingTitle, -} from './camera/frigate/frigate.js'; +} from './camera/frigate/util.js'; export type ViewMediaType = 'clip' | 'snapshot' | 'recording'; export type ViewMediaSourceType = FrigateEvent | FrigateRecording | BrowseMediaSource;