Initial motionEye commit.
This commit is contained in:
@@ -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 }),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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>[];
|
||||
}
|
||||
Reference in New Issue
Block a user