From a5921e9f9159cc9dea0a4734043693a4c8270091 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sun, 29 Jan 2023 20:44:08 -0800 Subject: [PATCH] Import cleanups to avoid circular imports. --- src/camera-manager/types.ts | 8 +- src/components/viewer.ts | 11 +- src/types.ts | 45 +---- src/utils/ha/browse-media.ts | 375 ----------------------------------- src/view/view.ts | 4 - 5 files changed, 13 insertions(+), 430 deletions(-) delete mode 100644 src/utils/ha/browse-media.ts diff --git a/src/camera-manager/types.ts b/src/camera-manager/types.ts index c7ca2380..d2d9b5ba 100644 --- a/src/camera-manager/types.ts +++ b/src/camera-manager/types.ts @@ -1,4 +1,3 @@ -import { RecordingSegment } from '../types'; import { FrigateEvent, FrigateRecording } from './frigate/types'; // ==== @@ -48,6 +47,13 @@ export interface QueryResults { cached?: boolean; } +// Generic recording segment type (inspired by Frigate recording segments). +export interface RecordingSegment { + start_time: number; + end_time: number; + id: string; +} + export type QueryReturnType = QT extends EventQuery ? EventQueryResults : QT extends RecordingQuery diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 4c1ba8d7..d68f0dea 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -28,7 +28,6 @@ import { } from '../types.js'; import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; import { contentsChanged, errorToConsole } from '../utils/basic.js'; -import { getFullDependentBrowseMediaQueryParametersOrDispatchError } from '../utils/ha/browse-media.js'; import { ResolvedMediaCache, resolveMedia } from '../utils/ha/resolved-media.js'; import { View } from '../view/view.js'; import { MediaQueriesClassifier } from '../view/media-queries-classifier'; @@ -106,21 +105,13 @@ export class FrigateCardViewer extends LitElement { return; } - const browseMediaQueryParameters = - getFullDependentBrowseMediaQueryParametersOrDispatchError( - this, - this.hass, - this.cameras, - this.view.camera, - ); - if (!this.view.queryResults?.hasResults()) { // If the query is not specified, the view must tell us which mediaType to // search for. When the query *is* specified, the view is not required to // indicate the media type (e.g. the mixed 'media' view from the // timeline). const mediaType = this.view.getDefaultMediaType(); - if (!browseMediaQueryParameters || !mediaType) { + if (!mediaType) { return; } diff --git a/src/types.ts b/src/types.ts index 57d0b471..4788a607 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,7 +13,6 @@ import { } from 'custom-card-helpers'; import { StyleInfo } from 'lit/directives/style-map.js'; import { z } from 'zod'; -import { eventSchema, FrigateEvent, FrigateRecording } from './camera-manager/frigate/types.js'; import { deepRemoveDefaults } from './utils/zod.js'; // The min allowed size of buttons. @@ -1224,9 +1223,11 @@ const debugConfigDefault = { logging: false, }; -const debugConfigSchema = z.object({ - logging: z.boolean().default(debugConfigDefault.logging), -}).default(debugConfigDefault); +const debugConfigSchema = z + .object({ + logging: z.boolean().default(debugConfigDefault.logging), + }) + .default(debugConfigDefault); export type DebugConfig = z.infer; export interface CardWideConfig { @@ -1401,35 +1402,6 @@ export interface BrowseMediaSource { children?: BrowseMediaSource[] | null; } -export interface FrigateBrowseMediaSource extends BrowseMediaSource { - children?: FrigateBrowseMediaSource[] | null; - frigate?: { - event?: FrigateEvent; - recording?: FrigateRecording; - cameraID?: string; - }; -} - -export const frigateBrowseMediaSourceSchema: z.ZodSchema = z.lazy( - () => - z.object({ - title: z.string(), - media_class: z.string(), - media_content_type: z.string(), - media_content_id: z.string(), - can_play: z.boolean(), - can_expand: z.boolean(), - children_media_class: z.string().nullable().optional(), - thumbnail: z.string().nullable(), - children: z.array(frigateBrowseMediaSourceSchema).nullable().optional(), - frigate: z - .object({ - event: eventSchema, - }) - .optional(), - }), -); - // Server side data-type defined here: https://github.com/home-assistant/core/blob/dev/homeassistant/components/media_source/models.py export const resolvedMediaSchema = z.object({ url: z.string(), @@ -1458,10 +1430,3 @@ 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/ha/browse-media.ts b/src/utils/ha/browse-media.ts deleted file mode 100644 index b78de684..00000000 --- a/src/utils/ha/browse-media.ts +++ /dev/null @@ -1,375 +0,0 @@ -import { HomeAssistant } from 'custom-card-helpers'; -import { homeAssistantWSRequest } from '.'; -import { FrigateEvent, FrigateRecording } from '../../camera-manager/frigate/types'; -import { dispatchErrorMessageEvent } from '../../components/message.js'; -import { localize } from '../../localize/localize.js'; -import { - BrowseMediaQueryParameters, - CameraConfig, - ClipsOrSnapshots, - FrigateBrowseMediaSource, - frigateBrowseMediaSourceSchema, - MEDIA_CLASS_PLAYLIST, - MEDIA_CLASS_VIDEO, - MEDIA_TYPE_PLAYLIST, - MEDIA_TYPE_VIDEO, -} from '../../types.js'; -import { getAllDependentCameras } from '../camera.js'; - -/** - * Return the Frigate event_id given a FrigateBrowseMediaSource object. - * @param media The event to get the id from. - * @returns The `event_id` or `null` if not successfully parsed. - */ -export const getEventID = (media: FrigateBrowseMediaSource): string | null => { - return media.frigate?.event?.id ?? null; -}; - -/** - * Return the event start time given a FrigateBrowseMediaSource object. - * @param browseMedia The media object to get the start time from. - * @returns The start time in unix/epoch time, or null if it cannot be determined. - */ -export const getEventStartTime = (media: FrigateBrowseMediaSource): number | null => { - return media.frigate?.event?.start_time ?? null; -}; - -/** - * Determine if a FrigateBrowseMediaSource object is truly a media item (vs a folder). - * @param media The media object. - * @returns `true` if it's truly a media item, `false` otherwise. - */ -export const isTrueMedia = (media?: FrigateBrowseMediaSource): boolean => { - return !!media && !media.can_expand; -}; - -/** - * From a FrigateBrowseMediaSource item extract the first true media item from the - * children (i.e. a clip/snapshot, not a folder). - * @param media The media object with children. - * @returns The first true media item found. - */ -export const getFirstTrueMediaChildIndex = ( - media: FrigateBrowseMediaSource | null, -): number | null => { - if (!media || !media.children) { - return null; - } - const index = media.children.findIndex((child) => isTrueMedia(child)); - return index >= 0 ? index : null; -}; - -/** - * Browse Frigate media with a media content id. May throw. - * @param hass The HomeAssistant object. - * @param media_content_id The media content id to browse. - * @returns A FrigateBrowseMediaSource object or null on malformed. - */ -const browseMedia = async ( - hass: HomeAssistant, - media_content_id: string, -): Promise => { - const request = { - type: 'media_source/browse_media', - media_content_id: media_content_id, - }; - return await homeAssistantWSRequest(hass, frigateBrowseMediaSourceSchema, request); -}; - -/** - * Browse Frigate media with a media query. May throw. - * @param hass The HomeAssistant object. - * @param params The search parameters to use to search for media. - * @returns A FrigateBrowseMediaSource object or null on malformed. - */ -const browseMediaQuery = async ( - hass: HomeAssistant, - params: BrowseMediaQueryParameters, -): Promise => { - const result = await browseMedia( - hass, - // Defined in: - // https://github.com/blakeblackshear/frigate-hass-integration/blob/master/custom_components/frigate/media_source.py - [ - 'media-source://frigate', - params.clientId, - 'event-search', - params.mediaType, - - // If the name field ends in '.all' the integration will return up to 10K events. - params.unlimited ? '.all' : '', - params.after ? String(Math.floor(params.after)) : '', - params.before ? String(Math.ceil(params.before)) : '', - params.cameraName, - params.label, - params.zone, - ].join('/'), - ); - // If a cameraID was specified, imprint each child with that id for - // traceability. - if (params.cameraID) { - result.children?.forEach((child: FrigateBrowseMediaSource) => { - (child.frigate ??= {}).cameraID = params.cameraID; - }); - } - return result; -}; - -/** - * Browse multiple Frigate media queries. May throw. - * @param hass The HomeAssistant object. - * @param params An array of search parameters to use to search for media. - * @returns A map of FrigateBrowseMediaSource object or null on malformed. - */ -export const multipleBrowseMediaQuery = async ( - hass: HomeAssistant, - params: BrowseMediaQueryParameters | BrowseMediaQueryParameters[], -): Promise> => { - params = Array.isArray(params) ? params : [params]; - const output: Map = new Map(); - await Promise.all( - params.map(async (param: BrowseMediaQueryParameters): Promise => { - output.set(param, await browseMediaQuery(hass, param)); - }), - ); - return output; -}; - -/** - * Browse multiple Frigate media queries, then merged them. May throw. - * @param hass The HomeAssistant object. - * @param params An array of search parameters to use to search for media. - * @returns A single FrigateBrowseMediaSource object or null on malformed. - */ -export const multipleBrowseMediaQueryMerged = async ( - hass: HomeAssistant, - params: BrowseMediaQueryParameters | BrowseMediaQueryParameters[], -): Promise => { - return mergeFrigateBrowseMediaSources(await multipleBrowseMediaQuery(hass, params)); -}; - -/** - * Merge multiple FrigateBrowseMediaSource into a single. Note that this may - * use information from the query to differentiate results that may otherwise - * be identical. - * @param input A map of query -> result. - * @returns A single FrigateBrowseMediaSource object. - */ -export const mergeFrigateBrowseMediaSources = async ( - input: Map, -): Promise => { - const children: FrigateBrowseMediaSource[] = []; - - for (const [query, result] of input.entries()) { - for (const child of result.children || []) { - if (isTrueMedia(child)) { - children.push(child); - } else { - // If there are multiple inputs, separate the folder names with the - // query title (if available). - if (query.title && input.size > 1) { - children.push({ ...child, title: `[${query.title}] ${child.title}` }); - } else { - children.push(child); - } - } - } - } - - return createEventParentForChildren( - 'Merged events', - children - // TODO: Restore sort or remove: children.sort(sortYoungestToOldest), - ); -}; - -/** - * Get the parameters to search for media. - * @returns A BrowseMediaQueryParameters object. - */ -export const getBrowseMediaQueryParameters = ( - _hass: HomeAssistant, - cameraID: string, - cameraConfig?: CameraConfig, - overrides?: Partial, -): BrowseMediaQueryParameters | null => { - if (!cameraConfig || !cameraConfig.frigate.camera_name) { - return null; - } - return { - clientId: cameraConfig.frigate.client_id, - cameraName: cameraConfig.frigate.camera_name, - label: cameraConfig.frigate.label, - zone: cameraConfig.frigate.zone, - title: '', // TODO: Replace: getCameraTitle(hass, cameraConfig), - cameraID: cameraID, - ...overrides, - }; -}; - -/** - * Apply overrides to multiple query parameters. - * @param parameters An array of query parameters. - * @param overrides The overrides to apply. - * @returns The override query parameters. - */ -export const overrideMultiBrowseMediaQueryParameters = ( - parameters: BrowseMediaQueryParameters[], - overrides: Partial, -): BrowseMediaQueryParameters[] => { - const output: BrowseMediaQueryParameters[] = []; - parameters.forEach((param) => { - output.push({ ...param, ...overrides }); - }); - return output; -}; - -/** - * Get BrowseMediaQueryParameters for a camera (including its dependencies). - * @param hass Home Assistant object. - * @param cameras Cameras map. - * @param camera Name of the current camera. - * @param mediaType Optional media type to include in the parameters. - * @returns An array of query parameters. - */ -export const getFullDependentBrowseMediaQueryParameters = ( - hass: HomeAssistant, - cameras: Map, - camera: string, - mediaType?: ClipsOrSnapshots, -): BrowseMediaQueryParameters[] | null => { - const cameraIDs = getAllDependentCameras(cameras, camera); - const params: BrowseMediaQueryParameters[] = []; - for (const cameraID of cameraIDs) { - const param = getBrowseMediaQueryParameters( - hass, - cameraID, - cameras.get(cameraID), - mediaType ? { mediaType: mediaType } : {}, - ); - if (param) { - params.push(param); - } - } - return params.length ? params : null; -}; - -/** - * Get BrowseMediaQueryParameters for a camera (including its dependencies) or dispatch an error. - * @param element The element from which to dispatch the error. - * @param hass Home Assistant object. - * @param cameras Cameras map. - * @param camera Name of the current camera. - * @param mediaType Optional media type to include in the parameters. - * @returns An array of query parameters. - */ -export const getFullDependentBrowseMediaQueryParametersOrDispatchError = ( - element: HTMLElement, - hass: HomeAssistant, - cameras: Map, - camera: string, - mediaType?: ClipsOrSnapshots, -): BrowseMediaQueryParameters[] | null => { - const params = getFullDependentBrowseMediaQueryParameters( - hass, - cameras, - camera, - mediaType, - ); - if (!params) { - dispatchErrorMessageEvent(element, localize('error.no_camera_name'), { - context: cameras.get(camera), - }); - return null; - } - return params; -}; - -/** - * Given an array of media children, create a parent for them. - * @param title The title to use for the parent. - * @param children The children media items. - * @returns A single parent containing the children. - */ -export const createEventParentForChildren = ( - title: string, - children: FrigateBrowseMediaSource[], -): FrigateBrowseMediaSource => { - return { - title: title, - media_class: MEDIA_CLASS_PLAYLIST, - media_content_type: MEDIA_TYPE_PLAYLIST, - media_content_id: '', - can_play: false, - can_expand: true, - children_media_class: MEDIA_CLASS_PLAYLIST, - thumbnail: null, - children: children, - }; -}; - -/** - * Given a media video child with a given media_content_id. - * @param title The title to use for the child. - * @param mediaContentID The media content id to use for the child. - * @param children The children media items. - * @returns A single parent containing the children. - */ -export const createChild = ( - title: string, - mediaContentID: string, - options?: { - thumbnail?: string; - recording?: FrigateRecording; - event?: FrigateEvent; - cameraID?: string; - }, -): FrigateBrowseMediaSource => { - const result: FrigateBrowseMediaSource = { - title: title, - media_class: MEDIA_CLASS_VIDEO, - media_content_type: MEDIA_TYPE_VIDEO, - media_content_id: mediaContentID, - can_play: true, - can_expand: false, - thumbnail: options?.thumbnail ?? null, - children: null, - }; - if (options?.recording || options?.cameraID || options?.event) { - result.frigate = {}; - if (options?.event) { - result.frigate.event = options.event; - } - if (options?.recording) { - result.frigate.recording = options.recording; - } - if (options?.cameraID) { - result.frigate.cameraID = options.cameraID; - } - } - return result; -}; - -// TODO Remove or fix this if not needed post gallery. -// /** -// * 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; -// }; diff --git a/src/view/view.ts b/src/view/view.ts index 679f3900..36244521 100644 --- a/src/view/view.ts +++ b/src/view/view.ts @@ -5,10 +5,6 @@ // Gallery: // - TODO: Filter panel expands from right can occasionally 'stick' open. -// Hard: -// - TODO: Remove FrigateBrowseMediaSource if not necessary (post-gallery). -// - TODO: Remove browse-media.ts TODOs. - import { ViewContext } from 'view'; import { FrigateCardUserSpecifiedView,