Add support for precise layout cropping options.

This commit is contained in:
Dermot Duffy
2024-04-15 16:10:09 -07:00
parent ad1f30554e
commit c7c85d188d
21 changed files with 251 additions and 27 deletions
+11 -3
View File
@@ -1,6 +1,7 @@
import add from 'date-fns/add';
import cloneDeep from 'lodash-es/cloneDeep';
import sum from 'lodash-es/sum';
import PQueue from 'p-queue';
import { CardCameraAPI } from '../card-controller/types.js';
import { CameraConfig, CamerasConfig, PTZAction, PTZPhase } from '../config/types.js';
import { MEDIA_CHUNK_SIZE_DEFAULT } from '../const.js';
@@ -108,6 +109,7 @@ export class CameraManager {
protected _api: CardCameraAPI;
protected _engineFactory: CameraManagerEngineFactory;
protected _store: CameraManagerStore;
protected _initializationLimit = new PQueue({ concurrency: 1 });
constructor(
api: CardCameraAPI,
@@ -138,8 +140,6 @@ export class CameraManager {
return false;
}
await this.reset();
// For each camera merge the config (which has no defaults) into the camera
// global config (which does have defaults). The merging must happen in this
// order, to ensure that the defaults in the cameras global config do not
@@ -148,8 +148,16 @@ export class CameraManager {
recursivelyMergeObjectsNotArrays({}, cloneDeep(config?.cameras_global), camera),
);
try {
const resetAndInitialize = async () => {
await this.reset();
await this._initializeCameras(cameras);
};
try {
// This concurrency limit prevents multiple rapidly arriving configs from
// generating reset-n-initialize race conditions (e.g. changing values
// rapidly in the config editor).
await this._initializationLimit.add(resetAndInitialize);
} catch (e: unknown) {
this._api.getMessageManager().setErrorIfHigherPriority(e);
return false;
+8
View File
@@ -618,6 +618,14 @@ const mediaLayoutConfigSchema = z.object({
y: z.number().min(0).max(100).optional(),
})
.optional(),
view_box: z
.object({
bottom: z.number().min(0).max(100).optional().default(0),
left: z.number().min(0).max(100).optional().default(0),
right: z.number().min(0).max(100).optional().default(0),
top: z.number().min(0).max(100).optional().default(0),
})
.optional(),
});
export type MediaLayoutConfig = z.infer<typeof mediaLayoutConfigSchema>;
+8
View File
@@ -61,6 +61,14 @@ export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_X =
`${CONF_CAMERAS}.#.dimensions.layout.position.x` as const;
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y =
`${CONF_CAMERAS}.#.dimensions.layout.position.y` as const;
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_TOP =
`${CONF_CAMERAS}.#.dimensions.layout.view_box.top` as const;
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_BOTTOM =
`${CONF_CAMERAS}.#.dimensions.layout.view_box.bottom` as const;
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_LEFT =
`${CONF_CAMERAS}.#.dimensions.layout.view_box.left` as const;
export const CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_RIGHT =
`${CONF_CAMERAS}.#.dimensions.layout.view_box.right` as const;
export const CONF_CAMERAS_ARRAY_TRIGGERS_MOTION =
`${CONF_CAMERAS}.#.triggers.motion` as const;
export const CONF_CAMERAS_ARRAY_TRIGGERS_OCCUPANCY =
+80 -13
View File
@@ -46,6 +46,10 @@ import {
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_FIT,
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_X,
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y,
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_BOTTOM,
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_LEFT,
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_RIGHT,
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_TOP,
CONF_CAMERAS_ARRAY_FRIGATE_CAMERA_NAME,
CONF_CAMERAS_ARRAY_FRIGATE_CLIENT_ID,
CONF_CAMERAS_ARRAY_FRIGATE_LABELS,
@@ -1226,6 +1230,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
configPathFit: string,
configPathPositionX: string,
configPathPositionY: string,
configPathPositionTop: string,
configPathPositionBottom: string,
configPathPositionLeft: string,
configPathPositionRight: string,
): TemplateResult | void {
return this._putInSubmenu(
domain,
@@ -1236,16 +1244,50 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderOptionSelector(configPathFit, this._layoutFits, {
label: localize('config.cameras.dimensions.layout.fit'),
})}
${this._renderNumberInput(configPathPositionX, {
min: 0,
max: 100,
label: localize('config.cameras.dimensions.layout.position.x'),
})}
${this._renderNumberInput(configPathPositionY, {
min: 0,
max: 100,
label: localize('config.cameras.dimensions.layout.position.y'),
})}
${this._putInSubmenu(
`${domain}.position`,
true,
'config.cameras.dimensions.layout.position.editor_label',
{ name: 'mdi:aspect-ratio' },
html` ${this._renderNumberInput(configPathPositionX, {
min: 0,
max: 100,
label: localize('config.cameras.dimensions.layout.position.x'),
})}
${this._renderNumberInput(configPathPositionY, {
min: 0,
max: 100,
label: localize('config.cameras.dimensions.layout.position.y'),
})}`,
)}
${this._putInSubmenu(
`${domain}.view_box`,
true,
'config.cameras.dimensions.layout.view_box.editor_label',
{ name: 'mdi:crop' },
html`
${this._renderNumberInput(configPathPositionTop, {
min: 0,
max: 100,
label: localize('config.cameras.dimensions.layout.view_box.top'),
})}
${this._renderNumberInput(configPathPositionBottom, {
min: 0,
max: 100,
label: localize('config.cameras.dimensions.layout.view_box.bottom'),
})}
${this._renderNumberInput(configPathPositionLeft, {
min: 0,
max: 100,
label: localize('config.cameras.dimensions.layout.view_box.left'),
})}
${this._renderNumberInput(configPathPositionRight, {
min: 0,
max: 100,
label: localize('config.cameras.dimensions.layout.view_box.right'),
})}
`,
)}
`,
);
}
@@ -1971,9 +2013,34 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderMediaLayout(
MENU_CAMERAS_DIMENSIONS_LAYOUT,
'config.cameras.dimensions.layout.editor_label',
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_FIT,
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_X,
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y,
getArrayConfigPath(
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_FIT,
cameraIndex,
),
getArrayConfigPath(
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_X,
cameraIndex,
),
getArrayConfigPath(
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_POSITION_Y,
cameraIndex,
),
getArrayConfigPath(
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_TOP,
cameraIndex,
),
getArrayConfigPath(
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_BOTTOM,
cameraIndex,
),
getArrayConfigPath(
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_LEFT,
cameraIndex,
),
getArrayConfigPath(
CONF_CAMERAS_ARRAY_DIMENSIONS_LAYOUT_VIEW_BOX_RIGHT,
cameraIndex,
),
)}
`,
)}
+8
View File
@@ -56,8 +56,16 @@
"fill": "El suport s'estira per omplir la targeta"
},
"position": {
"editor_label": "",
"x": "Percentatge d'emplaçament horitzontal",
"y": "Percentatge d'emplaçament vertical"
},
"view_box": {
"bottom": "",
"editor_label": "",
"left": "",
"right": "",
"top": ""
}
}
},
+9 -1
View File
@@ -56,8 +56,16 @@
"fill": "Media is stretched to fill the card"
},
"position": {
"editor_label": "Layout Cover Position",
"x": "Horizontal placement percentage",
"y": "Vertical placement percentage"
},
"view_box": {
"bottom": "Bottom inset percentage",
"editor_label": "Layout View Box",
"left": "Left inset percentage",
"right": "Right inset percentage",
"top": "Top inset percentage"
}
}
},
@@ -616,4 +624,4 @@
"timeline": {
"select_date": "Choose date"
}
}
}
+8
View File
@@ -56,8 +56,16 @@
"fill": "Il supporto viene allungato per riempire la scheda"
},
"position": {
"editor_label": "",
"x": "Percentuale di posizionamento orizzontale",
"y": "Percentuale di posizionamento verticale"
},
"view_box": {
"bottom": "",
"editor_label": "",
"left": "",
"right": "",
"top": ""
}
}
},
+8
View File
@@ -56,8 +56,16 @@
"fill": "Il supporto viene allungato per riempire la scheda"
},
"position": {
"editor_label": "",
"x": "Percentuale di posizionamento orizzontale",
"y": "Percentuale di posizionamento verticale"
},
"view_box": {
"bottom": "",
"editor_label": "",
"left": "",
"right": "",
"top": ""
}
}
},
+8
View File
@@ -56,8 +56,16 @@
"fill": "A mídia é esticada para preencher o cartão"
},
"position": {
"editor_label": "",
"x": "Porcentagem do posicionamento horizontal",
"y": "Porcentagem do posicionamento vertical"
},
"view_box": {
"bottom": "",
"editor_label": "",
"left": "",
"right": "",
"top": ""
}
}
},
+8
View File
@@ -56,8 +56,16 @@
"fill": "Preencher"
},
"position": {
"editor_label": "",
"x": "Percentagem da localização horizontal",
"y": "Percentagem da localização vertical"
},
"view_box": {
"bottom": "",
"editor_label": "",
"left": "",
"right": "",
"top": ""
}
}
},
+9
View File
@@ -2,4 +2,13 @@
object-fit: var(--frigate-card-media-layout-fit, contain);
object-position: var(--frigate-card-media-layout-position-x, 50%)
var(--frigate-card-media-layout-position-y, 50%);
// This is supported on limited browsers:
// See: https://caniuse.com/?search=object-view-box
object-view-box: inset(
var(--frigate-card-media-layout-view-box-top, 0%)
var(--frigate-card-media-layout-view-box-right, 0%)
var(--frigate-card-media-layout-view-box-bottom, 0%)
var(--frigate-card-media-layout-view-box-left, 0%)
);
}
+10
View File
@@ -24,4 +24,14 @@ export const updateElementStyleFromMediaLayoutConfig = (
element.style.removeProperty(`--frigate-card-media-layout-position-${dimension}`);
}
}
for (const dimension of ['top', 'bottom', 'left', 'right']) {
if (mediaLayoutConfig?.view_box?.[dimension] !== undefined) {
element.style.setProperty(
`--frigate-card-media-layout-view-box-${dimension}`,
`${mediaLayoutConfig.view_box[dimension]}%`,
);
} else {
element.style.removeProperty(`--frigate-card-media-layout-view-box-${dimension}`);
}
}
};