@@ -1,6 +1,6 @@
|
||||
import { Entity, EntityRegistryManager } from '../../ha/registry/entity/types';
|
||||
import { HomeAssistant } from '../../ha/types';
|
||||
import { localize } from '../../localize/localize';
|
||||
import { Entity, EntityRegistryManager } from '../../utils/ha/registry/entity/types';
|
||||
import { Camera, CameraInitializationOptions } from '../camera';
|
||||
import { CameraInitializationError } from '../error';
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { StateWatcherSubscriptionInterface } from '../../card-controller/hass/state-watcher';
|
||||
import { CameraConfig } from '../../config/schema/cameras';
|
||||
import { BROWSE_MEDIA_CACHE_SECONDS } from '../../ha/browse-media/types';
|
||||
import { BrowseMediaWalker } from '../../ha/browse-media/walker';
|
||||
import { getMediaDownloadPath } from '../../ha/download';
|
||||
import { EntityRegistryManager } from '../../ha/registry/entity/types';
|
||||
import { ResolvedMediaCache } from '../../ha/resolved-media';
|
||||
import { HomeAssistant } from '../../ha/types';
|
||||
import { canonicalizeHAURL } from '../../utils/ha';
|
||||
import { BrowseMediaManager } from '../../utils/ha/browse-media/browse-media-manager';
|
||||
import { BROWSE_MEDIA_CACHE_SECONDS } from '../../utils/ha/browse-media/types';
|
||||
import { EntityRegistryManager } from '../../utils/ha/registry/entity/types';
|
||||
import { ResolvedMediaCache, resolveMedia } from '../../utils/ha/resolved-media';
|
||||
import { ViewMedia } from '../../view/media';
|
||||
import { RequestCache } from '../cache';
|
||||
import { Endpoint } from '../../types';
|
||||
import { ViewMedia } from '../../view/item';
|
||||
import { ViewItemCapabilities } from '../../view/types';
|
||||
import { CameraManagerEngine } from '../engine';
|
||||
import { GenericCameraManagerEngine } from '../generic/engine-generic';
|
||||
import { CameraManagerReadOnlyConfigStore } from '../store';
|
||||
import {
|
||||
CameraEndpoint,
|
||||
CameraEventCallback,
|
||||
CameraManagerMediaCapabilities,
|
||||
DataQuery,
|
||||
CameraManagerRequestCache,
|
||||
CameraQuery,
|
||||
EventQuery,
|
||||
PartialEventQuery,
|
||||
QueryType,
|
||||
@@ -28,22 +28,22 @@ export class BrowseMediaCameraManagerEngine
|
||||
extends GenericCameraManagerEngine
|
||||
implements CameraManagerEngine
|
||||
{
|
||||
protected _browseMediaManager: BrowseMediaManager;
|
||||
protected _browseMediaWalker: BrowseMediaWalker;
|
||||
protected _entityRegistryManager: EntityRegistryManager;
|
||||
protected _resolvedMediaCache: ResolvedMediaCache;
|
||||
protected _requestCache: RequestCache;
|
||||
protected _requestCache: CameraManagerRequestCache;
|
||||
|
||||
public constructor(
|
||||
entityRegistryManager: EntityRegistryManager,
|
||||
stateWatcher: StateWatcherSubscriptionInterface,
|
||||
browseMediaManager: BrowseMediaManager,
|
||||
browseMediaManager: BrowseMediaWalker,
|
||||
resolvedMediaCache: ResolvedMediaCache,
|
||||
requestCache: RequestCache,
|
||||
requestCache: CameraManagerRequestCache,
|
||||
eventCallback?: CameraEventCallback,
|
||||
) {
|
||||
super(stateWatcher, eventCallback);
|
||||
this._entityRegistryManager = entityRegistryManager;
|
||||
this._browseMediaManager = browseMediaManager;
|
||||
this._browseMediaWalker = browseMediaManager;
|
||||
this._resolvedMediaCache = resolvedMediaCache;
|
||||
this._requestCache = requestCache;
|
||||
}
|
||||
@@ -66,18 +66,11 @@ export class BrowseMediaCameraManagerEngine
|
||||
hass: HomeAssistant,
|
||||
_cameraConfig: CameraConfig,
|
||||
media: ViewMedia,
|
||||
): Promise<CameraEndpoint | null> {
|
||||
const contentID = media.getContentID();
|
||||
if (!contentID) {
|
||||
return null;
|
||||
}
|
||||
const resolvedMedia = await resolveMedia(hass, contentID, this._resolvedMediaCache);
|
||||
return resolvedMedia
|
||||
? { endpoint: canonicalizeHAURL(hass, resolvedMedia.url) }
|
||||
: null;
|
||||
): Promise<Endpoint | null> {
|
||||
return getMediaDownloadPath(hass, media.getContentID(), this._resolvedMediaCache);
|
||||
}
|
||||
|
||||
public getQueryResultMaxAge(query: DataQuery): number | null {
|
||||
public getQueryResultMaxAge(query: CameraQuery): number | null {
|
||||
if (query.type === QueryType.Event) {
|
||||
return BROWSE_MEDIA_CACHE_SECONDS;
|
||||
}
|
||||
@@ -85,7 +78,7 @@ export class BrowseMediaCameraManagerEngine
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public getMediaCapabilities(_media: ViewMedia): CameraManagerMediaCapabilities {
|
||||
public getMediaCapabilities(_media: ViewMedia): ViewItemCapabilities {
|
||||
return {
|
||||
canFavorite: false,
|
||||
canDownload: true,
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import { format } from 'date-fns';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import { formatDateAndTime } from '../../utils/basic';
|
||||
import { RichBrowseMedia } from '../../utils/ha/browse-media/types';
|
||||
import {
|
||||
ViewMedia,
|
||||
EventViewMedia,
|
||||
ViewMediaType,
|
||||
VideoContentType,
|
||||
} from '../../view/media';
|
||||
import { BrowseMediaMetadata } from '../browse-media/types';
|
||||
|
||||
class BrowseMediaEventViewMedia extends ViewMedia implements EventViewMedia {
|
||||
protected _browseMedia: RichBrowseMedia<BrowseMediaMetadata>;
|
||||
protected _id: string;
|
||||
|
||||
constructor(
|
||||
mediaType: ViewMediaType,
|
||||
cameraID: string,
|
||||
browseMedia: RichBrowseMedia<BrowseMediaMetadata>,
|
||||
) {
|
||||
super(mediaType, cameraID);
|
||||
this._browseMedia = browseMedia;
|
||||
|
||||
// Generate a custom ID that uses the start date (to allow multiple
|
||||
// BrowseMedia objects (e.g. images and movies) to be de-duplicated).
|
||||
if (browseMedia._metadata?.startDate) {
|
||||
this._id = `${cameraID}/${format(
|
||||
browseMedia._metadata.startDate,
|
||||
'yyyy-MM-dd HH:mm:ss',
|
||||
)}`;
|
||||
} else {
|
||||
this._id = browseMedia.media_content_id;
|
||||
}
|
||||
}
|
||||
|
||||
public getStartTime(): Date | null {
|
||||
return this._browseMedia._metadata?.startDate ?? null;
|
||||
}
|
||||
public getEndTime(): Date | null {
|
||||
return this._browseMedia._metadata?.endDate ?? null;
|
||||
}
|
||||
public getVideoContentType(): VideoContentType | null {
|
||||
return VideoContentType.MP4;
|
||||
}
|
||||
public getID(): string {
|
||||
return this._id;
|
||||
}
|
||||
public getContentID(): string {
|
||||
return this._browseMedia.media_content_id;
|
||||
}
|
||||
public getTitle(): string | null {
|
||||
const startTime = this.getStartTime();
|
||||
return startTime ? formatDateAndTime(startTime) : this._browseMedia.title;
|
||||
}
|
||||
public getThumbnail(): string | null {
|
||||
return this._browseMedia.thumbnail;
|
||||
}
|
||||
public getWhat(): string[] | null {
|
||||
return this._browseMedia._metadata?.what ?? null;
|
||||
}
|
||||
public getScore(): number | null {
|
||||
return null;
|
||||
}
|
||||
public getTags(): string[] | null {
|
||||
return null;
|
||||
}
|
||||
public isGroupableWith(that: EventViewMedia): boolean {
|
||||
return (
|
||||
this.getMediaType() === that.getMediaType() &&
|
||||
isEqual(this.getWhere(), that.getWhere()) &&
|
||||
isEqual(this.getWhat(), that.getWhat())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class BrowseMediaViewMediaFactory {
|
||||
static createEventViewMedia(
|
||||
mediaType: 'clip' | 'snapshot',
|
||||
browseMedia: RichBrowseMedia<BrowseMediaMetadata>,
|
||||
cameraID: string,
|
||||
): BrowseMediaEventViewMedia {
|
||||
return new BrowseMediaEventViewMedia(mediaType, cameraID, browseMedia);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export interface BrowseMediaMetadata {
|
||||
cameraID: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
what?: string[];
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import {
|
||||
RichBrowseMedia,
|
||||
MEDIA_CLASS_VIDEO,
|
||||
MEDIA_CLASS_IMAGE,
|
||||
} from '../../../utils/ha/browse-media/types';
|
||||
import { ViewMedia } from '../../../view/media';
|
||||
import { BrowseMediaViewMediaFactory } from '../media';
|
||||
import { BrowseMediaMetadata } from '../types';
|
||||
|
||||
export const getViewMediaFromBrowseMediaArray = (
|
||||
browseMedia: RichBrowseMedia<BrowseMediaMetadata>[],
|
||||
): ViewMedia[] | null => {
|
||||
const lookup: Map<string, ViewMedia> = new Map();
|
||||
for (const browseMediaItem of browseMedia) {
|
||||
const cameraID = browseMediaItem._metadata?.cameraID;
|
||||
if (!cameraID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mediaType =
|
||||
browseMediaItem.media_class === MEDIA_CLASS_VIDEO
|
||||
? 'clip'
|
||||
: browseMediaItem.media_class === MEDIA_CLASS_IMAGE
|
||||
? 'snapshot'
|
||||
: null;
|
||||
|
||||
if (!mediaType) {
|
||||
continue;
|
||||
}
|
||||
const media = BrowseMediaViewMediaFactory.createEventViewMedia(
|
||||
mediaType,
|
||||
browseMediaItem,
|
||||
cameraID,
|
||||
);
|
||||
|
||||
const id = media.getID();
|
||||
const existing = lookup.get(id);
|
||||
|
||||
// De-duplicate events with precisely the same ID (same
|
||||
// hour/minute/second) choosing clip > snapshot.
|
||||
if (
|
||||
!existing ||
|
||||
(existing.getMediaType() === 'snapshot' && media.getMediaType() === 'clip')
|
||||
) {
|
||||
lookup.set(id, media);
|
||||
}
|
||||
}
|
||||
return [...lookup.values()];
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
import { RichBrowseMedia } from '../../../utils/ha/browse-media/types';
|
||||
import { rangesOverlap } from '../../range';
|
||||
import { BrowseMediaMetadata } from '../types';
|
||||
|
||||
/**
|
||||
* A utility method to determine if a browse media object matches against a
|
||||
* start and end date.
|
||||
* @param media The browse media object (with rich metadata).
|
||||
* @param start The optional start date.
|
||||
* @param end The optional end date.
|
||||
* @returns `true` if the media falls within the provided dates.
|
||||
*/
|
||||
|
||||
export const isMediaWithinDates = (
|
||||
media: RichBrowseMedia<BrowseMediaMetadata>,
|
||||
start?: Date,
|
||||
end?: Date,
|
||||
): boolean => {
|
||||
// If there's no metadata, nothing matches.
|
||||
if (!media._metadata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (start && end) {
|
||||
// Determine if:
|
||||
// - The media starts within the query timeframe.
|
||||
// - The media ends within the query timeframe.
|
||||
// - The media entirely encompasses the query timeframe.
|
||||
return rangesOverlap(
|
||||
{
|
||||
start: media._metadata.startDate,
|
||||
end: media._metadata.endDate,
|
||||
},
|
||||
{
|
||||
start: start,
|
||||
end: end,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!start && end) {
|
||||
return media._metadata.startDate <= end;
|
||||
}
|
||||
if (start && !end) {
|
||||
return media._metadata.startDate >= start;
|
||||
}
|
||||
|
||||
// If no date is specified at all, everything matches.
|
||||
return true;
|
||||
};
|
||||
Reference in New Issue
Block a user