Add support for arbitrary image entities

This commit is contained in:
Dermot Duffy
2024-09-08 16:48:26 -07:00
parent 35ff8cd500
commit 041656317a
16 changed files with 264 additions and 149 deletions
@@ -48,7 +48,7 @@ export class StatusBarItemManager {
const engineLogoIcon = cameraMetadata?.engineLogo ?? null;
const title = options?.view?.is('live')
? cameraMetadata?.title ?? null
: options?.view?.isAnyMediaView()
: options?.view?.isViewerView()
? options?.view.queryResults?.getSelectedResult()?.getTitle() ?? null
: null;
const resolution = options?.mediaLoadedInfo
+81 -27
View File
@@ -12,8 +12,9 @@ import { customElement, property } from 'lit/decorators.js';
import { live } from 'lit/directives/live.js';
import { createRef, ref, Ref } from 'lit/directives/ref.js';
import isEqual from 'lodash-es/isEqual';
import { CameraManager } from '../camera-manager/manager.js';
import { CachedValueController } from '../components-lib/cached-value-controller.js';
import { CameraConfig, ImageViewConfig } from '../config/types.js';
import { CameraConfig, ImageMode, ImageViewConfig } from '../config/types.js';
import defaultImage from '../images/frigate-bird-in-sky.jpg';
import { localize } from '../localize/localize.js';
import imageStyle from '../scss/image.scss';
@@ -28,7 +29,6 @@ import {
} from '../utils/media-info.js';
import { View } from '../view/view.js';
import { dispatchErrorMessageEvent } from './message.js';
import { CameraManager } from '../camera-manager/manager.js';
// See TOKEN_CHANGE_INTERVAL in https://github.com/home-assistant/core/blob/dev/homeassistant/components/camera/__init__.py .
const HASS_REJECTION_CUTOFF_MS = 5 * 60 * 1000;
@@ -119,14 +119,12 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
return false;
}
const cameraEntity = this._getCameraEntity();
if (
changedProps.has('hass') &&
changedProps.size == 1 &&
this.imageConfig?.mode === 'camera' &&
cameraEntity
) {
if (isHassDifferent(this.hass, changedProps.get('hass'), [cameraEntity])) {
const relevantEntity = this._getRelevantEntityForMode(
this._resolveMode(this.imageConfig?.mode),
);
if (changedProps.has('hass') && changedProps.size == 1 && relevantEntity) {
if (isHassDifferent(this.hass, changedProps.get('hass'), [relevantEntity])) {
// If the state of the camera entity has changed, remove the cached
// value (will be re-calculated in willUpdate). This is important to
// ensure a changed access token is immediately used.
@@ -158,6 +156,10 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
}
}
const relevantEntity = this._getRelevantEntityForMode(
this._resolveMode(this.imageConfig?.mode),
);
// If the camera or view changed, immediately discard the old value (view to
// allow pressing of the image button to fetch a fresh image). Likewise, if
// the state is not acceptable, discard the old value (to allow a stock or
@@ -165,8 +167,7 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
if (
changedProps.has('cameraConfig') ||
changedProps.has('view') ||
(this.imageConfig?.mode === 'camera' &&
!this._getAcceptableState(this._getCameraEntity()))
(relevantEntity && !this._getAcceptableState(relevantEntity))
) {
this._cachedValueController?.clearValue();
}
@@ -245,24 +246,75 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
/**
* Build a working absolute image URL that the browser will not cache.
* @param url An input URL (may be relative to document origin)
* @returns A new URL (absolute, will not be browser cached).
* @returns A new URL as a string (absolute, will not be browser cached).
*/
protected _buildImageURL(url: string): string {
const urlObj = new URL(url, document.baseURI);
urlObj.searchParams.append('_t', String(Date.now()));
return urlObj.toString();
protected _buildImageURL(url: URL): string {
url.searchParams.append('_t', String(Date.now()));
return url.toString();
}
protected _addQueryParametersToURL(url: URL, parameters?: string): URL {
if (parameters) {
const searchParams = new URLSearchParams(parameters);
for (const [key, value] of searchParams.entries()) {
url.searchParams.append(key, value);
}
}
return url;
}
protected _getRelevantEntityForMode(mode: Exclude<ImageMode, 'auto'>): string | null {
return mode === 'camera'
? this._getCameraEntity()
: mode === 'entity'
? this.imageConfig?.entity ?? null
: null;
}
protected _resolveMode(mode?: ImageMode): Exclude<ImageMode, 'auto'> {
if (!mode) {
return 'screensaver';
} else if (mode !== 'auto') {
return mode;
}
const cameraEntity = this._getCameraEntity();
if (this.imageConfig?.entity) {
return 'entity';
} else if (this.imageConfig?.url) {
return 'url';
} else if (cameraEntity) {
return 'camera';
}
return 'screensaver';
}
protected _getImageSource(): string {
if (this.hass && this.imageConfig?.mode === 'camera') {
const mode = this._resolveMode(this.imageConfig?.mode);
if (this.hass && mode === 'camera') {
const state = this._getAcceptableState(this._getCameraEntity());
if (state?.attributes.entity_picture) {
return this._buildImageURL(state.attributes.entity_picture);
const urlObj = new URL(state.attributes.entity_picture, document.baseURI);
this._addQueryParametersToURL(urlObj, this.imageConfig?.entity_parameters);
return this._buildImageURL(urlObj);
}
}
if (this.imageConfig?.mode !== 'screensaver' && this.imageConfig?.url) {
return this._buildImageURL(this.imageConfig.url);
if (this.hass && mode === 'entity' && this.imageConfig?.entity) {
const state = this._getAcceptableState(this.imageConfig?.entity);
if (state?.attributes.entity_picture) {
const urlObj = new URL(state.attributes.entity_picture, document.baseURI);
this._addQueryParametersToURL(urlObj, this.imageConfig?.entity_parameters);
return this._buildImageURL(urlObj);
}
}
if (mode === 'url' && this.imageConfig?.url) {
return this._buildImageURL(new URL(this.imageConfig.url, document.baseURI));
}
return defaultImage;
}
@@ -300,13 +352,15 @@ export class FrigateCardImage extends LitElement implements FrigateCardMediaPlay
}
}}
@error=${() => {
if (this.imageConfig?.mode === 'camera') {
// In camera mode, the user has likely not made an error, but HA
// may be unavailble, so show the stock image. Don't let the URL
// override the stock image in this case, as this could create an
// error loop if that URL subsequently failed to load.
const mode = this._resolveMode(this.imageConfig?.mode);
if (mode === 'camera' || mode === 'entity') {
// In camera or entity mode, the user has likely not made an
// error, but HA may be unavailble, so show the stock image.
// Don't let the URL override the stock image in this case, as
// this could create an error loop if that URL subsequently
// failed to load.
this._forceSafeImage(true);
} else if (this.imageConfig?.mode === 'url') {
} else if (mode === 'url') {
// In url mode, the user likely specified a URL that cannot be
// resolved. Show an error message.
dispatchErrorMessageEvent(this, localize('error.image_load_error'), {
+1 -8
View File
@@ -64,15 +64,8 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia
return html`
<frigate-card-image
${ref(this._refImage)}
.imageConfig=${{
mode: this.cameraConfig.image.url ? ('url' as const) : ('camera' as const),
refresh_seconds: this.cameraConfig.image.refresh_seconds,
url: this.cameraConfig.image.url,
// The live provider will take care of zoom and layout options.
zoomable: false,
}}
.hass=${this.hass}
.imageConfig=${this.cameraConfig.image}
.cameraConfig=${this.cameraConfig}
>
</frigate-card-image>
+12 -17
View File
@@ -917,25 +917,25 @@ export type PTZControlsConfig = z.infer<typeof ptzControlsConfigSchema>;
// `image` card view.
// *************************************************************************
const imageBaseConfigDefault = {
const imageConfigDefault = {
mode: 'auto' as const,
refresh_seconds: 1,
};
const IMAGE_MODES = ['auto', 'camera', 'entity', 'screensaver', 'url'] as const;
export type ImageMode = (typeof IMAGE_MODES)[number];
const imageBaseConfigSchema = z.object({
mode: z.enum(IMAGE_MODES).default(imageConfigDefault.mode),
refresh_seconds: z.number().min(0).default(imageConfigDefault.refresh_seconds),
url: z.string().optional(),
refresh_seconds: z.number().min(0).default(imageBaseConfigDefault.refresh_seconds),
entity: z.string().optional(),
entity_parameters: z.string().optional(),
});
const IMAGE_MODES = ['screensaver', 'camera', 'url'] as const;
const imageConfigDefault = {
mode: 'url' as const,
...imageBaseConfigDefault,
};
const imageConfigSchema = imageBaseConfigSchema
.extend({
mode: z.enum(IMAGE_MODES).default(imageConfigDefault.mode),
})
.merge(actionsSchema)
.default(imageConfigDefault);
export type ImageViewConfig = z.infer<typeof imageConfigSchema>;
@@ -1127,8 +1127,6 @@ const go2rtcConfigSchema = z.object({
stream: z.string().optional(),
});
const liveImageConfigSchema = imageBaseConfigSchema;
const webrtcCardConfigSchema = z
.object({
entity: z.string().optional(),
@@ -1302,9 +1300,6 @@ const cameraConfigDefault = {
frigate: {
client_id: 'frigate' as const,
},
image: {
refresh_seconds: 1,
},
live_provider: 'auto' as const,
motioneye: {
images: {
@@ -1408,7 +1403,7 @@ export const cameraConfigSchema = z
// Live provider options.
live_provider: z.enum(LIVE_PROVIDERS).default(cameraConfigDefault.live_provider),
go2rtc: go2rtcConfigSchema.optional(),
image: liveImageConfigSchema.default(cameraConfigDefault.image),
image: imageBaseConfigSchema.optional().default(imageConfigDefault),
jsmpeg: jsmpegConfigSchema.optional(),
webrtc_card: webrtcCardConfigSchema.optional(),
+6
View File
@@ -31,6 +31,10 @@ export const CONF_CAMERAS_ARRAY_GO2RTC_STREAM =
`${CONF_CAMERAS}.#.go2rtc.stream` as const;
export const CONF_CAMERAS_ARRAY_ICON = `${CONF_CAMERAS}.#.icon` as const;
export const CONF_CAMERAS_ARRAY_ID = `${CONF_CAMERAS}.#.id` as const;
export const CONF_CAMERAS_ARRAY_IMAGE_ENTITY = `${CONF_CAMERAS}.#.image.entity` as const;
export const CONF_CAMERAS_ARRAY_IMAGE_ENTITY_PARAMETERS =
`${CONF_CAMERAS}.#.image.entity_parameters` as const;
export const CONF_CAMERAS_ARRAY_IMAGE_MODE = `${CONF_CAMERAS}.#.image.mode` as const;
export const CONF_CAMERAS_ARRAY_IMAGE_REFRESH_SECONDS =
`${CONF_CAMERAS}.#.image.refresh_seconds` as const;
export const CONF_CAMERAS_ARRAY_IMAGE_URL = `${CONF_CAMERAS}.#.image.url` as const;
@@ -290,6 +294,8 @@ export const CONF_LIVE_MICROPHONE_ALWAYS_CONNECTED =
export const CONF_LIVE_ZOOMABLE = `${CONF_LIVE}.zoomable` as const;
const CONF_IMAGE = 'image' as const;
export const CONF_IMAGE_ENTITY = `${CONF_IMAGE}.entity` as const;
export const CONF_IMAGE_ENTITY_PARAMETERS = `${CONF_IMAGE}.entity_parameters` as const;
export const CONF_IMAGE_MODE = `${CONF_IMAGE}.mode` as const;
export const CONF_IMAGE_REFRESH_SECONDS = `${CONF_IMAGE}.refresh_seconds` as const;
export const CONF_IMAGE_URL = `${CONF_IMAGE}.url` as const;
+59 -17
View File
@@ -66,6 +66,9 @@ import {
CONF_CAMERAS_ARRAY_GO2RTC_STREAM,
CONF_CAMERAS_ARRAY_ICON,
CONF_CAMERAS_ARRAY_ID,
CONF_CAMERAS_ARRAY_IMAGE_ENTITY,
CONF_CAMERAS_ARRAY_IMAGE_ENTITY_PARAMETERS,
CONF_CAMERAS_ARRAY_IMAGE_MODE,
CONF_CAMERAS_ARRAY_IMAGE_REFRESH_SECONDS,
CONF_CAMERAS_ARRAY_IMAGE_URL,
CONF_CAMERAS_ARRAY_LIVE_PROVIDER,
@@ -85,6 +88,8 @@ import {
CONF_DIMENSIONS_ASPECT_RATIO_MODE,
CONF_DIMENSIONS_MAX_HEIGHT,
CONF_DIMENSIONS_MIN_HEIGHT,
CONF_IMAGE_ENTITY,
CONF_IMAGE_ENTITY_PARAMETERS,
CONF_IMAGE_MODE,
CONF_IMAGE_REFRESH_SECONDS,
CONF_IMAGE_URL,
@@ -519,9 +524,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
protected _imageModes: EditorSelectOption[] = [
{ value: '', label: '' },
{ value: 'camera', label: localize('config.image.modes.camera') },
{ value: 'screensaver', label: localize('config.image.modes.screensaver') },
{ value: 'url', label: localize('config.image.modes.url') },
{ value: 'camera', label: localize('config.common.image.modes.camera') },
{ value: 'entity', label: localize('config.common.image.modes.entity') },
{ value: 'screensaver', label: localize('config.common.image.modes.screensaver') },
{ value: 'url', label: localize('config.common.image.modes.url') },
];
protected _timelineEventsMediaTypes: EditorSelectOption[] = [
@@ -1733,6 +1739,36 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
);
}
protected _renderImageOptions(
configPathMode: string,
configPathUrl: string,
configPathEntity: string,
configPathEntityParameters: string,
configPathRefreshSeconds: string,
): TemplateResult {
return html`
${this._renderOptionSelector(configPathMode, this._imageModes, {
label: localize('config.common.image.mode'),
})}
${this._renderStringInput(configPathUrl, {
label: localize('config.common.image.url'),
})}
${this._renderOptionSelector(
configPathEntity,
this.hass ? getEntitiesFromHASS(this.hass) : [],
{
label: localize('config.common.image.entity'),
},
)}
${this._renderStringInput(configPathEntityParameters, {
label: localize('config.common.image.entity_parameters'),
})}
${this._renderNumberInput(configPathRefreshSeconds, {
label: localize('config.common.image.refresh_seconds'),
})}
`;
}
/**
* Render a camera section.
* @param cameras The full array of cameras.
@@ -2006,17 +2042,19 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
true,
'config.cameras.image.editor_label',
{ name: 'mdi:image' },
html`
${this._renderNumberInput(
getArrayConfigPath(
CONF_CAMERAS_ARRAY_IMAGE_REFRESH_SECONDS,
cameraIndex,
),
)}
${this._renderStringInput(
getArrayConfigPath(CONF_CAMERAS_ARRAY_IMAGE_URL, cameraIndex),
)}
`,
this._renderImageOptions(
getArrayConfigPath(CONF_CAMERAS_ARRAY_IMAGE_MODE, cameraIndex),
getArrayConfigPath(CONF_CAMERAS_ARRAY_IMAGE_URL, cameraIndex),
getArrayConfigPath(CONF_CAMERAS_ARRAY_IMAGE_ENTITY, cameraIndex),
getArrayConfigPath(
CONF_CAMERAS_ARRAY_IMAGE_ENTITY_PARAMETERS,
cameraIndex,
),
getArrayConfigPath(
CONF_CAMERAS_ARRAY_IMAGE_REFRESH_SECONDS,
cameraIndex,
),
),
)}
${this._putInSubmenu(
MENU_CAMERAS_WEBRTC_CARD,
@@ -2725,9 +2763,13 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderOptionSetHeader('image')}
${this._expandedMenus[MENU_OPTIONS] === 'image'
? html` <div class="values">
${this._renderOptionSelector(CONF_IMAGE_MODE, this._imageModes)}
${this._renderStringInput(CONF_IMAGE_URL)}
${this._renderNumberInput(CONF_IMAGE_REFRESH_SECONDS)}
${this._renderImageOptions(
CONF_IMAGE_MODE,
CONF_IMAGE_URL,
CONF_IMAGE_ENTITY,
CONF_IMAGE_ENTITY_PARAMETERS,
CONF_IMAGE_REFRESH_SECONDS,
)}
</div>`
: ''}
${this._renderOptionSetHeader('timeline')}
+13 -11
View File
@@ -219,6 +219,19 @@
"grid_selected_width_factor": "Augmenta l'amplada del suport multimèdia seleccionat en aquest factor",
"mode": "Mode"
},
"image": {
"entity": "",
"entity_parameters": "",
"mode": "Mode de visualització d'imatges",
"modes": {
"camera": "Instantània de l'entitat de la càmera de Home Assistant",
"entity": "",
"screensaver": "Logotip de Frigate incrustat",
"url": "Imatge arbitrària especificada per URL"
},
"refresh_seconds": "Nombre de segons després dels quals cal actualitzar (0=mai)",
"url": "URL d'imatge estàtica per a la visualització d'imatges"
},
"media_action_conditions": {
"all": "Totes les oportunitats",
"hidden": "A l'amagat del navegador/pestanya",
@@ -257,17 +270,6 @@
"max_height": "Alçada màxima de la targeta en unitats CSS (p. ex., '100vh')",
"min_height": "Alçada mínima de la targeta en unitats CSS (p. ex., '100 px')"
},
"image": {
"mode": "Mode de visualització d'imatges",
"modes": {
"camera": "Instantània de l'entitat de la càmera de Home Assistant",
"screensaver": "Logotip de Frigate incrustat",
"url": "Imatge arbitrària especificada per URL"
},
"refresh_seconds": "Nombre de segons després dels quals cal actualitzar (0=mai)",
"url": "URL d'imatge estàtica per a la visualització d'imatges",
"zoomable": "La imatge es pot ampliar/escombrar"
},
"live": {
"auto_mute": "Silencia automàticament les càmeres en directe",
"auto_pause": "Posa en pausa automàticament les càmeres en directe",
+13 -11
View File
@@ -219,6 +219,19 @@
"grid_selected_width_factor": "Increase selected media width by this factor",
"mode": "Mode"
},
"image": {
"entity": "Entity for use with entity mode",
"entity_parameters": "Query parameters added to the entity-based picture URLs (e.g. width=1920&height=1080)",
"mode": "Image mode",
"modes": {
"camera": "Home Assistant camera snapshot of camera entity",
"entity": "Entity with entity_picture attribute",
"screensaver": "Embedded Frigate logo",
"url": "Arbitrary image specified by URL"
},
"refresh_seconds": "Number of seconds after which to refresh (0=never)",
"url": "Static image URL"
},
"media_action_conditions": {
"all": "All opportunities",
"hidden": "On browser/tab hiding",
@@ -257,17 +270,6 @@
"max_height": "Maximum card height in CSS units (e.g. '100vh')",
"min_height": "Minimum card height in CSS units (e.g. '100px')"
},
"image": {
"mode": "Image view mode",
"modes": {
"camera": "Home Assistant camera snapshot of camera entity",
"screensaver": "Embedded Frigate logo",
"url": "Arbitrary image specified by URL"
},
"refresh_seconds": "Number of seconds after which to refresh (0=never)",
"url": "Static image URL for image view",
"zoomable": "Image can be zoomed/panned"
},
"live": {
"auto_mute": "Automatically mute live cameras",
"auto_pause": "Automatically pause live cameras",
+13 -11
View File
@@ -219,6 +219,19 @@
"grid_selected_width_factor": "Augmenter la largeur du média sélectionnée par ce facteur",
"mode": "Mode"
},
"image": {
"entity": "",
"entity_parameters": "",
"mode": "Mode d'affichage des images",
"modes": {
"camera": "Instantané de la caméra Home Assistant de l'entité caméra",
"entity": "",
"screensaver": "Logo Frigate intégré",
"url": "Image arbitraire spécifiée par URL"
},
"refresh_seconds": "Nombre de secondes après lesquelles actualiser (0=jamais)",
"url": "URL d'image statique pour l'affichage de l'image"
},
"media_action_conditions": {
"all": "Toutes les opportunités",
"hidden": "Sur le navigateur/onglet masqué",
@@ -257,17 +270,6 @@
"max_height": "Hauteur maximale de la carte en unités CSS (par exemple '100vh')",
"min_height": "Hauteur minimale de la carte en unités CSS (par exemple « 100 px »)"
},
"image": {
"mode": "Mode d'affichage des images",
"modes": {
"camera": "Instantané de la caméra Home Assistant de l'entité caméra",
"screensaver": "Logo Frigate intégré",
"url": "Image arbitraire spécifiée par URL"
},
"refresh_seconds": "Nombre de secondes après lesquelles actualiser (0=jamais)",
"url": "URL d'image statique pour l'affichage de l'image",
"zoomable": "L'image peut être zoomée/panoramique"
},
"live": {
"auto_mute": "Couper automatiquement le son des caméras en direct",
"auto_pause": "Mettre automatiquement en pause les caméras en direct",
+13 -11
View File
@@ -219,6 +219,19 @@
"grid_selected_width_factor": "",
"mode": ""
},
"image": {
"entity": "",
"entity_parameters": "",
"mode": "Modalità Visualizza immagine",
"modes": {
"camera": "Istantanea della telecamera di Home Assistant dell'entità telecamera",
"entity": "",
"screensaver": "Logo Frigate incorporato",
"url": "Immagine arbitraria specificata dall'URL"
},
"refresh_seconds": "Numero di secondi dopo i quali aggiornare (0 = mai)",
"url": "URL di immagine statica per la vista dell'immagine"
},
"media_action_conditions": {
"all": "Tutte le opportunità",
"hidden": "Sul browser/nascondere le schede",
@@ -257,17 +270,6 @@
"max_height": "",
"min_height": ""
},
"image": {
"mode": "Modalità Visualizza immagine",
"modes": {
"camera": "Istantanea della telecamera di Home Assistant dell'entità telecamera",
"screensaver": "Logo Frigate incorporato",
"url": "Immagine arbitraria specificata dall'URL"
},
"refresh_seconds": "Numero di secondi dopo i quali aggiornare (0 = mai)",
"url": "URL di immagine statica per la vista dell'immagine",
"zoomable": ""
},
"live": {
"auto_mute": "Muta automaticamente le telecamere in diretta",
"auto_pause": "Metti in pausa automaticamente le telecamere in diretta",
+13 -11
View File
@@ -219,6 +219,19 @@
"grid_selected_width_factor": "",
"mode": ""
},
"image": {
"entity": "",
"entity_parameters": "",
"mode": "Modo de visualização de imagem",
"modes": {
"camera": "Instantâneo da câmera do Home Assistant, da entidade de câmera",
"entity": "",
"screensaver": "Logo Frigate embutido",
"url": "Imagem arbitrária especificada por URL"
},
"refresh_seconds": "Número de segundos após o qual atualizar (0 = nunca)",
"url": "Imagem arbitrária especificada por URL"
},
"media_action_conditions": {
"all": "Todas as oportunidades",
"hidden": "Ao ocultar o navegador/aba",
@@ -257,17 +270,6 @@
"max_height": "",
"min_height": ""
},
"image": {
"mode": "Modo de visualização de imagem",
"modes": {
"camera": "Instantâneo da câmera do Home Assistant, da entidade de câmera",
"screensaver": "Logo Frigate embutido",
"url": "Imagem arbitrária especificada por URL"
},
"refresh_seconds": "Número de segundos após o qual atualizar (0 = nunca)",
"url": "Imagem arbitrária especificada por URL",
"zoomable": ""
},
"live": {
"auto_mute": "Silenciar câmeras ao vivo automaticamente",
"auto_pause": "Pausar câmeras ao vivo automaticamente",
+13 -11
View File
@@ -219,6 +219,19 @@
"grid_selected_width_factor": "",
"mode": ""
},
"image": {
"entity": "",
"entity_parameters": "",
"mode": "Modo de visualização de imagem",
"modes": {
"camera": "Instantâneo da câmera do Home Assistant, da entidade de câmera",
"entity": "",
"screensaver": "Logo Frigate embutido",
"url": "Imagem arbitrária especificada por URL"
},
"refresh_seconds": "Número de segundos após o qual atualizar (0 = nunca)",
"url": "Imagem arbitrária especificada por URL"
},
"media_action_conditions": {
"all": "Todas as oportunidades",
"hidden": "Ao ocultar o navegador/aba",
@@ -257,17 +270,6 @@
"max_height": "",
"min_height": ""
},
"image": {
"mode": "Modo de visualização de imagem",
"modes": {
"camera": "Instantâneo da câmera do Home Assistant, da entidade de câmera",
"screensaver": "Logo Frigate embutido",
"url": "Imagem arbitrária especificada por URL"
},
"refresh_seconds": "Número de segundos após o qual atualizar (0 = nunca)",
"url": "Imagem arbitrária especificada por URL",
"zoomable": ""
},
"live": {
"auto_mute": "Silenciar câmeras ao vivo automaticamente",
"auto_pause": "Parar câmeras ao vivo automaticamente",