Initial motionEye commit.

This commit is contained in:
Dermot Duffy
2023-03-26 16:42:21 -07:00
parent 05e9db4cc5
commit 304c7e3164
55 changed files with 1780 additions and 245 deletions
@@ -0,0 +1,242 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
width="64"
height="64"
xml:space="preserve"
sodipodi:docname="motioneye-icon.svg"
inkscape:export-filename="/home/ccrisan/projects/motioneye/static/img/motioneye-logo.png"
inkscape:export-xdpi="960"
inkscape:export-ydpi="960"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs6"><linearGradient
id="linearGradient4351"
inkscape:collect="always"><stop
id="stop4353"
offset="0"
style="stop-color:#737373;stop-opacity:1" /><stop
id="stop4355"
offset="1"
style="stop-color:#585858;stop-opacity:1" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient4205"><stop
style="stop-color:#4aa3e0;stop-opacity:1"
offset="0"
id="stop4207" /><stop
style="stop-color:#3096db;stop-opacity:1"
offset="1"
id="stop4209" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient4197"><stop
style="stop-color:#787878;stop-opacity:1"
offset="0"
id="stop4199" /><stop
style="stop-color:#585858;stop-opacity:1"
offset="1"
id="stop4201" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4351"
id="linearGradient4203"
x1="26.445793"
y1="47.517574"
x2="26.445793"
y2="3.8183768"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4205"
id="linearGradient4211"
x1="26.602072"
y1="43.034946"
x2="26.602072"
y2="29.466328"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.96428571,0,0,0.96428571,0.91428571,0.91428571)" /><filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4285"><feFlood
flood-opacity="0.588235"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4287" /><feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4289" /><feGaussianBlur
in="composite1"
stdDeviation="0.6"
result="blur"
id="feGaussianBlur4291" /><feOffset
dx="0"
dy="-1"
result="offset"
id="feOffset4293" /><feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite4295" /></filter><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4197"
id="linearGradient4309"
gradientUnits="userSpaceOnUse"
x1="26.445793"
y1="47.517574"
x2="26.445793"
y2="3.8183768" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4197"
id="linearGradient4311"
gradientUnits="userSpaceOnUse"
x1="26.445793"
y1="47.517574"
x2="26.445793"
y2="3.8183768" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4197"
id="linearGradient4313"
gradientUnits="userSpaceOnUse"
x1="26.445793"
y1="47.517574"
x2="26.445793"
y2="3.8183768" /><filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4315"
x="-0.10000000000000001"
y="-0.16000000000000003"><feFlood
flood-opacity="0.588235"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4317" /><feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4319" /><feGaussianBlur
in="composite1"
stdDeviation="0.6"
result="blur"
id="feGaussianBlur4321" /><feOffset
dx="0"
dy="-1"
result="offset"
id="feOffset4323" /><feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite4325" /></filter><filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4327"><feFlood
flood-opacity="0.588235"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4329" /><feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4331" /><feGaussianBlur
in="composite1"
stdDeviation="0.6"
result="blur"
id="feGaussianBlur4333" /><feOffset
dx="0"
dy="-1"
result="offset"
id="feOffset4335" /><feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite4337" /></filter><filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter4339"><feFlood
flood-opacity="0.588235"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood4341" /><feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite4343" /><feGaussianBlur
in="composite1"
stdDeviation="0.2"
result="blur"
id="feGaussianBlur4345" /><feOffset
dx="0"
dy="-0.5"
result="offset"
id="feOffset4347" /><feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite4349" /></filter></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1025"
id="namedview4"
showgrid="false"
inkscape:zoom="2"
inkscape:cx="-94.597631"
inkscape:cy="10.226517"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="g10"
showguides="true"
inkscape:guide-bbox="true" /><g
id="g10"
inkscape:groupmode="layer"
inkscape:label="ink_ext_XXXXXX"
transform="matrix(1.25,0,0,-1.25,0,64)"><g
id="g4170"
style="fill:url(#linearGradient4203);fill-opacity:1;filter:url(#filter4327)"
transform="matrix(0.96428571,0,0,0.96428571,0.91428571,0.91428571)"><path
id="path4244"
d="M 8.9346154,40.515385 C 5.3647588,36.547307 3.2,31.357779 3.2,25.6 3.2,13.228821 13.228821,3.2 25.6,3.2 37.971179,3.2 48,13.228821 48,25.6 c 0,5.736682 -2.161128,10.952493 -5.707692,14.915385 -1.695935,-0.623286 -3.387833,-1.349065 -5.061539,-2.288462 3.2394,-0.937363 5.6,-3.937988 5.6,-7.457692 0,-4.260339 -3.469626,-7.753846 -7.753846,-7.753846 -3.633936,0 -6.690552,2.51055 -7.538461,5.869231 l -3.876924,0 c -0.840685,-3.360193 -3.903443,-5.869231 -7.538461,-5.869231 -4.284219,0 -7.7807693,3.493507 -7.7807693,7.753846 0,3.56112 2.4570323,6.5856 5.7615383,7.484616 -1.676267,0.912203 -3.404813,1.620556 -5.1692306,2.261538 z M 25.6,26.461538 c 0.532632,-1.981435 1.101793,-3.947553 3.446154,-5.16923 L 25.6,16.123077 22.153846,21.292308 c 2.053593,1.454966 3.000771,3.237758 3.446154,5.16923 z"
style="fill:url(#linearGradient4309);fill-opacity:1;stroke:none"
inkscape:connector-curvature="0" /><path
id="path4242"
d="m 16.123077,33.353847 c -1.427443,0 -2.584616,-1.157173 -2.584616,-2.584616 0,-1.427444 1.157173,-2.584615 2.584616,-2.584615 1.427444,0 2.584615,1.157171 2.584615,2.584615 0,1.427443 -1.157171,2.584616 -2.584615,2.584616 z"
style="fill:url(#linearGradient4311);fill-opacity:1;stroke:none"
inkscape:connector-curvature="0" /><path
id="path4240"
d="m 35.076923,33.353847 c -1.427443,0 -2.584615,-1.157173 -2.584615,-2.584616 0,-1.427444 1.157172,-2.584615 2.584615,-2.584615 1.427443,0 2.584616,1.157171 2.584616,2.584615 0,1.427443 -1.157173,2.584616 -2.584616,2.584616 z"
style="fill:url(#linearGradient4313);fill-opacity:1;stroke:none"
inkscape:connector-curvature="0" /></g><path
inkscape:connector-curvature="0"
style="fill:#737373;fill-opacity:1;stroke:none;filter:url(#filter4339)"
d="m 25.6,47.2 c -4.373944,0 -8.437159,-1.399808 -11.838461,-3.634616 3.677605,-0.394237 7.305921,-1.342945 11.423077,-3.375 4.166157,2.122533 8.434154,3.008875 12.279808,3.452886 C 34.057131,45.890032 29.986674,47.2 25.6,47.2 Z"
id="path4248" /><path
inkscape:connector-curvature="0"
style="fill:url(#linearGradient4211);fill-opacity:1;stroke:none;filter:url(#filter4315)"
d="M 39.723077,42.552884 C 35.394064,42.5242 29.479588,40.397223 25.184616,38.432418 20.668821,40.064102 16.035448,42.649343 10.801923,42.526924 10.453022,42.51873 10.118061,42.50105 9.7634616,42.475 L 5.6615384,42.1375 9.5557693,40.839424 c 5.3417977,-1.74056 10.0398397,-2.851302 14.1749997,-10.025963 0.959101,0 2.845924,-4.15e-4 3.738462,-4.15e-4 4.11884,7.134039 9.059296,8.324614 14.149039,10.026378 L 45.460577,42.085577 41.4625,42.475 c -0.544847,0.05181 -1.120992,0.08198 -1.739423,0.07788 z"
id="path4246"
sodipodi:nodetypes="cccccccccccc" /></g></svg>

After

Width:  |  Height:  |  Size: 10 KiB

@@ -0,0 +1,427 @@
import { HomeAssistant } from 'custom-card-helpers';
import { CameraConfig } from '../../types';
import { ViewMedia } from '../../view/media';
import {
CameraConfigs,
CameraEndpoint,
CameraEndpoints,
CameraEndpointsContext,
CameraManagerCameraMetadata,
Engine,
EngineOptions,
EventQuery,
EventQueryResults,
EventQueryResultsMap,
MediaMetadataQuery,
MediaMetadataQueryResults,
MediaMetadataQueryResultsMap,
QueryResults,
QueryResultsType,
QueryReturnType,
} from '../types';
import { CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT } from '../engine';
import {
BrowseMediaStep,
BrowseMediaTarget,
} from '../../utils/ha/browse-media/browse-media-manager';
import { allPromises, formatDate, isValidDate } from '../../utils/basic';
import endOfDay from 'date-fns/endOfDay';
import {
BROWSE_MEDIA_CACHE_SECONDS,
BrowseMedia,
MEDIA_CLASS_IMAGE,
MEDIA_CLASS_VIDEO,
RichBrowseMedia,
} from '../../utils/ha/browse-media/types';
import parse from 'date-fns/parse';
import { MotionEyeEventQueryResults } from './types';
import orderBy from 'lodash-es/orderBy';
import startOfDay from 'date-fns/startOfDay';
import add from 'date-fns/add';
import { BrowseMediaCameraManagerEngine } from '../browse-media/engine-browse-media';
import { BrowseMediaMetadata } from '../browse-media/types';
import { BrowseMediaViewMediaFactory } from '../browse-media/media';
import motioneyeLogo from './assets/motioneye-logo.svg';
class MotionEyeQueryResultsClassifier {
public static isMotionEyeEventQueryResults(
results: QueryResults,
): results is MotionEyeEventQueryResults {
return (
results.engine === Engine.MotionEye && results.type === QueryResultsType.Event
);
}
}
const MOTIONEYE_REPL_SUBSTITUTIONS: Record<string, string> = {
'%Y': 'yyyy',
'%m': 'MM',
'%d': 'dd',
'%H': 'HH',
'%M': 'mm',
'%S': 'SS',
};
const MOTIONEYE_REPL_REGEXP = new RegExp(/(%Y|%m|%d|%H|%M|%S)/g);
export class MotionEyeCameraManagerEngine extends BrowseMediaCameraManagerEngine {
public getEngineType(): Engine {
return Engine.MotionEye;
}
protected _convertMotionEyeTimeFormatToDateFNS(part: string): string {
return part.replace(
MOTIONEYE_REPL_REGEXP,
(_, key) => MOTIONEYE_REPL_SUBSTITUTIONS[key],
);
}
// Get metadata for a MotionEye media file.
protected _motionEyeMetadataGeneratorFile(
cameraID: string,
dateFormat: string | null,
media: BrowseMedia,
parent?: RichBrowseMedia<BrowseMediaMetadata>,
): BrowseMediaMetadata | null {
let startDate = parent?._metadata?.startDate ?? new Date();
if (dateFormat) {
const extensionlessTitle = media.title.replace(/\.[^/.]+$/, '');
startDate = parse(extensionlessTitle, dateFormat, startDate);
if (!isValidDate(startDate)) {
return null;
}
}
return {
cameraID: cameraID,
startDate: startDate,
// MotionEye only has start times, the event is effectively a 'point'
endDate: startDate,
};
}
// Get metadata for a MotionEye media directory.
protected _motionEyeMetadataGeneratorDirectory(
cameraID: string,
dateFormat: string | null,
media: BrowseMedia,
parent?: RichBrowseMedia<BrowseMediaMetadata>,
): BrowseMediaMetadata | null {
let startDate = parent?._metadata?.startDate ?? new Date();
if (dateFormat) {
const parsedDate = parse(media.title, dateFormat, startDate);
if (!isValidDate(parsedDate)) {
return null;
}
startDate = startOfDay(parsedDate);
}
return {
cameraID: cameraID,
startDate: startDate,
endDate: parent?._metadata?.endDate ?? endOfDay(startDate),
};
}
// Get media directories that match a given criteria.
protected async _getMatchingDirectories(
hass: HomeAssistant,
cameras: CameraConfigs,
cameraID: string,
matchOptions?: {
start?: Date;
end?: Date;
hasClip?: boolean;
hasSnapshot?: boolean;
} | null,
engineOptions?: EngineOptions,
): Promise<RichBrowseMedia<BrowseMediaMetadata>[] | null> {
const cameraEntityID = cameras.get(cameraID)?.camera_entity;
const entity = cameraEntityID ? this._cameraEntities.get(cameraEntityID) : null;
const configID = entity?.config_entry_id;
const deviceID = entity?.device_id;
const cameraConfig = cameras.get(cameraID);
if (!configID || !deviceID || !cameraConfig) {
return null;
}
const generateNextStep = (
parts: string[],
media: BrowseMediaTarget<BrowseMediaMetadata>[],
): BrowseMediaStep<BrowseMediaMetadata>[] => {
const next = parts.shift();
if (!next) {
return [];
}
const dateFormat = next.includes('%')
? this._convertMotionEyeTimeFormatToDateFNS(next)
: null;
return [
{
targets: media,
metadataGenerator: (
media: BrowseMedia,
parent?: RichBrowseMedia<BrowseMediaMetadata>,
) =>
this._motionEyeMetadataGeneratorDirectory(
cameraID,
dateFormat,
media,
parent,
),
matcher: (media: RichBrowseMedia<BrowseMediaMetadata>) =>
media.can_expand &&
(!!dateFormat || media.title === next) &&
this._mediaIsWithinDates(media, matchOptions?.start, matchOptions?.end),
advance: (media) => generateNextStep(parts, media),
},
];
};
// For motionEye snapshots and clips are mutually exclusive.
return await this._browseMediaManager.walkBrowseMedias(
hass,
[
...(matchOptions?.hasClip !== false && !matchOptions?.hasSnapshot
? generateNextStep(
cameraConfig.motioneye.movies.directory_pattern.split('/'),
[`media-source://motioneye/${configID}#${deviceID}#movies`],
)
: []),
...(matchOptions?.hasSnapshot !== false && !matchOptions?.hasClip
? generateNextStep(
cameraConfig.motioneye.images.directory_pattern.split('/'),
[`media-source://motioneye/${configID}#${deviceID}#images`],
)
: []),
],
{
useCache: engineOptions?.useCache,
},
);
}
public async getEvents(
hass: HomeAssistant,
cameras: CameraConfigs,
query: EventQuery,
engineOptions?: EngineOptions,
): Promise<EventQueryResultsMap | null> {
// MotionEye does not support these query types and they will never match.
if (query.favorite || query.tags?.size || query.what?.size || query.where?.size) {
return null;
}
const output: EventQueryResultsMap = new Map();
const getEventsForCamera = async (cameraID: string): Promise<void> => {
const perCameraQuery = { ...query, cameraIDs: new Set([cameraID]) };
const cachedResult =
engineOptions?.useCache ?? true ? this._requestCache.get(perCameraQuery) : null;
if (cachedResult) {
output.set(perCameraQuery, cachedResult as EventQueryResults);
return;
}
const cameraConfig = cameras.get(cameraID);
if (!cameraConfig) {
return;
}
const directories = await this._getMatchingDirectories(
hass,
cameras,
cameraID,
perCameraQuery,
engineOptions,
);
if (!directories || !directories.length) {
return;
}
const moviesDateFormat = this._convertMotionEyeTimeFormatToDateFNS(
cameraConfig.motioneye.movies.file_pattern,
);
const imagesDateFormat = this._convertMotionEyeTimeFormatToDateFNS(
cameraConfig.motioneye.images.file_pattern,
);
const media = await this._browseMediaManager.walkBrowseMedias(
hass,
[
{
targets: directories,
metadataGenerator: (
media: BrowseMedia,
parent?: RichBrowseMedia<BrowseMediaMetadata>,
) => {
if (
media.media_class === MEDIA_CLASS_IMAGE ||
media.media_class === MEDIA_CLASS_VIDEO
) {
return this._motionEyeMetadataGeneratorFile(
cameraID,
media.media_class === MEDIA_CLASS_IMAGE
? imagesDateFormat
: moviesDateFormat,
media,
parent,
);
}
return null;
},
matcher: (media: RichBrowseMedia<BrowseMediaMetadata>) =>
!media.can_expand &&
this._mediaIsWithinDates(media, perCameraQuery.start, perCameraQuery.end),
},
],
{ useCache: engineOptions?.useCache },
);
// Sort by most recent then slice at the query limit.
const sortedMedia = orderBy(
media,
(media: RichBrowseMedia<BrowseMediaMetadata>) => media._metadata?.startDate,
'desc',
).slice(0, perCameraQuery.limit ?? CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT);
const result: MotionEyeEventQueryResults = {
type: QueryResultsType.Event,
engine: Engine.MotionEye,
browseMedia: sortedMedia,
};
if (engineOptions?.useCache ?? true) {
this._requestCache.set(
perCameraQuery,
{ ...result, cached: true },
result.expiry,
);
}
output.set(perCameraQuery, result);
};
await allPromises(query.cameraIDs, (cameraID) => getEventsForCamera(cameraID));
return output.size ? output : null;
}
public generateMediaFromEvents(
_hass: HomeAssistant,
_cameras: CameraConfigs,
_query: EventQuery,
results: QueryReturnType<EventQuery>,
): ViewMedia[] | null {
if (!MotionEyeQueryResultsClassifier.isMotionEyeEventQueryResults(results)) {
return null;
}
const output: ViewMedia[] = [];
for (const browseMedia of results.browseMedia) {
const cameraID = browseMedia._metadata?.cameraID;
if (!cameraID) {
continue;
}
const mediaType =
browseMedia.media_class === MEDIA_CLASS_VIDEO
? 'clip'
: browseMedia.media_class === MEDIA_CLASS_IMAGE
? 'snapshot'
: null;
if (!mediaType) {
continue;
}
const media = BrowseMediaViewMediaFactory.createEventViewMedia(
mediaType,
browseMedia,
cameraID,
);
if (media) {
output.push(media);
}
}
return output;
}
public async getMediaMetadata(
hass: HomeAssistant,
cameras: CameraConfigs,
query: MediaMetadataQuery,
engineOptions?: EngineOptions,
): Promise<MediaMetadataQueryResultsMap | null> {
const output: MediaMetadataQueryResultsMap = new Map();
if ((engineOptions?.useCache ?? true) && this._requestCache.has(query)) {
const cachedResult = <MediaMetadataQueryResults | null>(
this._requestCache.get(query)
);
if (cachedResult) {
output.set(query, cachedResult as MediaMetadataQueryResults);
return output;
}
}
const days: Set<string> = new Set();
const getDaysForCamera = async (cameraID: string): Promise<void> => {
const directories = await this._getMatchingDirectories(
hass,
cameras,
cameraID,
null,
engineOptions,
);
for (const dayDirectory of directories ?? []) {
if (dayDirectory._metadata) {
days.add(formatDate(dayDirectory._metadata?.startDate));
}
}
};
await allPromises(query.cameraIDs, (cameraID) => getDaysForCamera(cameraID));
const result: MediaMetadataQueryResults = {
type: QueryResultsType.MediaMetadata,
engine: Engine.MotionEye,
metadata: {
...(days.size && { days: days }),
},
expiry: add(new Date(), { seconds: BROWSE_MEDIA_CACHE_SECONDS }),
cached: false,
};
if (engineOptions?.useCache ?? true) {
this._requestCache.set(query, { ...result, cached: true }, result.expiry);
}
output.set(query, result);
return output;
}
public getCameraMetadata(
hass: HomeAssistant,
cameraConfig: CameraConfig,
): CameraManagerCameraMetadata {
const metadata = super.getCameraMetadata(hass, cameraConfig);
return {
...metadata,
engineLogo: motioneyeLogo,
};
}
public getCameraEndpoints(
cameraConfig: CameraConfig,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_context?: CameraEndpointsContext,
): CameraEndpoints | null {
const getUIEndpoint = (): CameraEndpoint | null => {
return cameraConfig.motioneye?.url
? {
endpoint: cameraConfig.motioneye.url,
}
: null;
};
const ui = getUIEndpoint();
return {
...(ui && { ui: ui }),
};
}
}
+51
View File
@@ -0,0 +1,51 @@
// Converted from https://raw.githubusercontent.com/motioneye-project/motioneye/python2/motioneye/static/img/motioneye-icon.svg .
export const MOTIONEYE_ICON_SVG_VIEWBOX = '0 0 64 64';
export const MOTIONEYE_ICON_SVG_PATH =
'M 49.65,10.81 ' +
'C 44.24,10.84 36.85,13.50 31.48,15.96 ' +
'25.84,13.92 20.04,10.69 13.50,10.84 ' +
'13.07,10.85 12.65,10.87 12.20,10.91 ' +
'12.20,10.91 7.08,11.33 7.08,11.33 ' +
'7.08,11.33 11.94,12.95 11.94,12.95 ' +
'18.62,15.13 24.49,16.51 29.66,25.48 ' +
'30.86,25.48 33.22,25.48 34.34,25.48 ' +
'39.49,16.57 45.66,15.08 52.02,12.95 ' +
'52.02,12.95 56.83,11.39 56.83,11.39 ' +
'56.83,11.39 51.83,10.91 51.83,10.91 ' +
'51.15,10.84 50.43,10.80 49.65,10.81 ' +
'49.65,10.81 49.65,10.81 49.65,10.81 Z ' +
'M 32.00,5.00 ' +
'C 26.53,5.00 21.45,6.75 17.20,9.54 ' +
'21.80,10.04 26.33,11.22 31.48,13.76 ' +
'36.69,11.11 42.02,10.00 46.83,9.45 ' +
'42.57,6.64 37.48,5.00 32.00,5.00 Z ' +
'M 43.42,22.65 ' +
'C 41.70,22.65 40.31,24.05 40.31,25.77 ' +
'40.31,27.49 41.70,28.88 43.42,28.88 ' +
'45.14,28.88 46.54,27.49 46.54,25.77 ' +
'46.54,24.05 45.14,22.65 43.42,22.65 Z ' +
'M 20.58,22.65 ' +
'C 18.86,22.65 17.46,24.05 17.46,25.77 ' +
'17.46,27.49 18.86,28.88 20.58,28.88 ' +
'22.30,28.88 23.69,27.49 23.69,25.77 ' +
'23.69,24.05 22.30,22.65 20.58,22.65 Z ' +
'M 11.91,14.02 ' +
'C 7.61,18.80 5.00,25.06 5.00,32.00 ' +
'5.00,46.91 17.09,59.00 32.00,59.00 ' +
'46.91,59.00 59.00,46.91 59.00,32.00 ' +
'59.00,25.09 56.40,18.80 52.12,14.02 ' +
'50.08,14.77 48.04,15.65 46.02,16.78 ' +
'49.92,17.91 52.77,21.53 52.77,25.77 ' +
'52.77,30.90 48.59,35.12 43.42,35.12 ' +
'39.04,35.12 35.36,32.09 34.34,28.04 ' +
'34.34,28.04 29.66,28.04 29.66,28.04 ' +
'28.65,32.09 24.96,35.12 20.58,35.12 ' +
'15.41,35.12 11.20,30.90 11.20,25.77 ' +
'11.20,21.48 14.16,17.83 18.14,16.75 ' +
'16.12,15.65 14.04,14.79 11.91,14.02 ' +
'11.91,14.02 11.91,14.02 11.91,14.02 Z ' +
'M 32.00,30.96 ' +
'C 32.64,33.35 33.33,35.72 36.15,37.19 ' +
'36.15,37.19 32.00,43.42 32.00,43.42 ' +
'32.00,43.42 27.85,37.19 27.85,37.19 ' +
'30.32,35.44 31.46,33.29 32.00,30.96 Z';
+12
View File
@@ -0,0 +1,12 @@
import { RichBrowseMedia } from '../../utils/ha/browse-media/types';
import { BrowseMediaMetadata } from '../browse-media/types';
import { Engine, EventQueryResults } from '../types';
// ================================
// MotionEye concrete query results
// ================================
export interface MotionEyeEventQueryResults extends EventQueryResults {
engine: Engine.MotionEye;
browseMedia: RichBrowseMedia<BrowseMediaMetadata>[];
}