Merge pull request #1360 from dermotduffy/recordings-thumbnails

Allow live thumbnails to load recordings
This commit is contained in:
Dermot Duffy
2024-02-03 14:45:11 -08:00
committed by GitHub
21 changed files with 345 additions and 136 deletions
+14 -11
View File
@@ -564,7 +564,8 @@ live:
| `show_download_control` | `true` | :white_check_mark: | Whether to show the download control on each thumbnail.|
| `show_favorite_control` | `true` | :white_check_mark: | Whether to show the favorite ('star') control on each thumbnail.|
| `show_timeline_control` | `true` | :white_check_mark: | Whether to show the timeline ('target') control on each thumbnail.|
| `media` | `all` | :white_check_mark: | Whether to show `clips`, `snapshots` or `all` in the thumbnail carousel in the `live` view.|
| `media_type` | `events` | :white_check_mark: | Whether to load `events` or `recordings` media.|
| `events_media_type` | `all` | :white_check_mark: | Whether to show `clips`, `snapshots` or `all` in the thumbnail carousel in the `live` view. This setting is only relevant when the `media_type` parameter (above) is set to `events`.|
#### Live Controls: Next / Previous
@@ -597,7 +598,7 @@ live:
| `style` | `ribbon` | :white_check_mark: | Whether the timeline should show events as a single flat `ribbon` or a `stack` of events that are clustered using the `clustering_threshold` (below). |
| `window_seconds` | `3600` | :white_check_mark: | The length of the default timeline in seconds. By default, 1 hour (`3600` seconds) is shown in the timeline. |
| `clustering_threshold` | `3` | :white_check_mark: | The minimum number of overlapping events to allow prior to clustering/grouping them. Higher numbers cause clustering to happen less frequently. Depending on the timescale/zoom of the timeline, the underlying timeline library may still allow overlaps for low values of this parameter -- for a fully "flat" timeline use the `ribbon` style. `0` disables clustering entirely. Only used in the `stack` style of timeline. |
| `media` | `all` | :white_check_mark: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot.|
| `events_media_type` | `all` | :white_check_mark: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot.|
| `show_recordings` | `true` | :white_check_mark: | Whether to show recordings on the timeline (specifically: which hours have any recorded content).|
**Caution**: 🚩 For optimal UX, keep the settings for the mini-timeline in the `live` and `media_viewer` identical. Dragging the timeline may cause the card to change between the `live` view and `media_viewer` based views as the user pans between the past and present -- if the settings are different the timeline must "reset".
@@ -781,7 +782,7 @@ media_viewer:
| `style` | `ribbon` | :heavy_multiplication_x: | Whether the timeline should show events as a single flat `ribbon` or a `stack` of events that are clustered using the `clustering_threshold` (below). |
| `window_seconds` | `3600` | :heavy_multiplication_x: | The length of the default timeline in seconds. By default, 1 hour (`3600` seconds) is shown in the timeline. |
| `clustering_threshold` | `3` | :heavy_multiplication_x: | The minimum number of overlapping events to allow prior to clustering/grouping them. Higher numbers cause clustering to happen less frequently. Depending on the timescale/zoom of the timeline, the underlying timeline library may still allow overlaps for low values of this parameter -- for a fully "flat" timeline use the `ribbon` style. `0` disables clustering entirely. Only used in the `stack` style of timeline. |
| `media` | `all` | :heavy_multiplication_x: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot.|
| `events_media_type` | `all` | :heavy_multiplication_x: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot. |
| `show_recordings` | `true` | :heavy_multiplication_x: | Whether to show recordings on the timeline (specifically: which hours have any recorded content).|
**Caution**: 🚩 For optimal UX, keep the settings for the mini-timeline in the `live` and `media_viewer` identical. Dragging the timeline may cause the card to change between the `live` view and `media_viewer` based views as the user pans between the past and present -- if the settings are different the timeline must "reset".
@@ -911,7 +912,7 @@ See the [fully expanded timeline configuration example](#config-expanded-timelin
| `style` | `stack` | :heavy_multiplication_x: | Whether the timeline should show events as a single flat `ribbon` or a `stack` of events that are clustered using the `clustering_threshold` (below). |
| `window_seconds` | `3600` | :heavy_multiplication_x: | The length of the default timeline in seconds. By default, 1 hour (`3600` seconds) is shown in the timeline. |
| `clustering_threshold` | `3` | :heavy_multiplication_x: | The minimum number of overlapping events to allow prior to clustering/grouping them. Higher numbers cause clustering to happen less frequently. Depending on the timescale/zoom of the timeline, the underlying timeline library may still allow overlaps for low values of this parameter -- for a fully "flat" timeline use the `ribbon` style. `0` disables clustering entirely. Only used in the `stack` style of timeline. |
| `media` | `all` | :heavy_multiplication_x: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot.|
| `events_media_type` | `all` | :heavy_multiplication_x: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot.|
| `show_recordings` | `true` | :heavy_multiplication_x: | Whether to show recordings on the timeline (specifically: which hours have any recorded content).|
| `controls` | | :heavy_multiplication_x: | Configuration for the timeline controls. See below.|
@@ -2092,7 +2093,8 @@ live:
style: chevrons
size: 48
thumbnails:
media: clips
media_type: events
events_media_type: all
size: 100
show_details: false
show_download_control: true
@@ -2103,7 +2105,7 @@ live:
style: ribbon
mode: none
clustering_threshold: 3
media: all
events_media_type: all
show_recordings: true
window_seconds: 3600
title:
@@ -2201,7 +2203,7 @@ media_viewer:
style: ribbon
mode: none
clustering_threshold: 3
media: all
events_media_type: all
show_recordings: true
window_seconds: 3600
title:
@@ -2663,7 +2665,7 @@ Reference: [Timeline Options](#timeline-options).
timeline:
style: stack
clustering_threshold: 3
media: all
events_media_type: all
show_recordings: true
window_seconds: 3600
controls:
@@ -2755,8 +2757,8 @@ overrides:
maxAudioLag: 10
videoBufferSize: 524288
audioBufferSize: 131072
image:
refresh_seconds: 1
image:
refresh_seconds: 1
live:
webrtc_card:
ui: true
@@ -2779,7 +2781,8 @@ overrides:
style: chevrons
size: 48
thumbnails:
media: clips
media_type: events
events_media_type: all
size: 100
show_details: false
show_download_control: true
+5 -5
View File
@@ -47,18 +47,18 @@ export class TimelineDataSource {
protected _eventRanges = new ExpiringMemoryRangeSet();
protected _cameraIDs: Set<string>;
protected _mediaType: ClipsOrSnapshotsOrAll;
protected _eventsMediaType: ClipsOrSnapshotsOrAll;
protected _showRecordings: boolean;
constructor(
cameraManager: CameraManager,
cameraIDs: Set<string>,
media: ClipsOrSnapshotsOrAll,
eventsMediaType: ClipsOrSnapshotsOrAll,
showRecordings: boolean,
) {
this._cameraManager = cameraManager;
this._cameraIDs = cameraIDs;
this._mediaType = media;
this._eventsMediaType = eventsMediaType;
this._showRecordings = showRecordings;
}
@@ -101,8 +101,8 @@ export class TimelineDataSource {
return this._cameraManager.generateDefaultEventQueries(this._cameraIDs, {
start: window.start,
end: window.end,
...(this._mediaType === 'clips' && { hasClip: true }),
...(this._mediaType === 'snapshots' && { hasSnapshot: true }),
...(this._eventsMediaType === 'clips' && { hasClip: true }),
...(this._eventsMediaType === 'snapshots' && { hasSnapshot: true }),
});
}
+2 -2
View File
@@ -87,7 +87,7 @@ export class FrigateCardGallery extends LitElement {
this.view,
);
} else {
const mediaType = this.view.is('snapshots')
const eventsMediaType = this.view.is('snapshots')
? 'snapshots'
: this.view.is('clips')
? 'clips'
@@ -98,7 +98,7 @@ export class FrigateCardGallery extends LitElement {
this.cardWideConfig,
this.view,
{
...(mediaType && { mediaType: mediaType }),
...(eventsMediaType && { eventsMediaType: eventsMediaType }),
},
);
}
+42 -16
View File
@@ -14,9 +14,16 @@ import {
ThumbnailsControlConfig,
} from '../config/types.js';
import basicBlockStyle from '../scss/basic-block.scss';
import { ClipsOrSnapshotsOrAll, ExtendedHomeAssistant } from '../types.js';
import {
ClipsOrSnapshotsOrAll,
EventsOrRecordings,
ExtendedHomeAssistant,
} from '../types.js';
import { contentsChanged, dispatchFrigateCardEvent } from '../utils/basic.js';
import { changeViewToRecentEventsForCameraAndDependents } from '../utils/media-to-view';
import {
changeViewToRecentEventsForCameraAndDependents,
changeViewToRecentRecordingForCameraAndDependents,
} from '../utils/media-to-view';
import { View } from '../view/view.js';
import './surround-basic.js';
import { ThumbnailCarouselTap } from './thumbnail-carousel.js';
@@ -48,7 +55,10 @@ export class FrigateCardSurround extends LitElement {
// If fetchMedia is not specified, no fetching is done.
@property({ attribute: false, hasChanged: contentsChanged })
public fetchMedia?: ClipsOrSnapshotsOrAll;
public fetchMediaType?: EventsOrRecordings;
@property({ attribute: false, hasChanged: contentsChanged })
public fetchEventsMediaType?: ClipsOrSnapshotsOrAll;
@property({ attribute: false })
public cameraManager?: CameraManager;
@@ -68,7 +78,8 @@ export class FrigateCardSurround extends LitElement {
if (
!this.cameraManager ||
!this.cardWideConfig ||
!this.fetchMedia ||
!this.fetchMediaType ||
!this.fetchEventsMediaType ||
!this.hass ||
!this.view ||
this.view.query ||
@@ -78,18 +89,33 @@ export class FrigateCardSurround extends LitElement {
) {
return;
}
await changeViewToRecentEventsForCameraAndDependents(
this,
this.cameraManager,
this.cardWideConfig,
this.view,
{
allCameras: this.view.isGrid(),
targetView: this.view.view,
mediaType: this.fetchMedia,
select: 'latest',
},
);
if (this.fetchMediaType === 'events') {
await changeViewToRecentEventsForCameraAndDependents(
this,
this.cameraManager,
this.cardWideConfig,
this.view,
{
allCameras: this.view.isGrid(),
targetView: this.view.view,
eventsMediaType: this.fetchEventsMediaType,
select: 'latest',
},
);
} else if (this.fetchMediaType === 'recordings') {
await changeViewToRecentRecordingForCameraAndDependents(
this,
this.cameraManager,
this.cardWideConfig,
this.view,
{
allCameras: this.view.isGrid(),
targetView: this.view.view,
select: 'latest',
},
);
}
}
/**
+1 -1
View File
@@ -1161,7 +1161,7 @@ export class FrigateCardTimelineCore extends LitElement {
this._timelineSource = new TimelineDataSource(
this.cameraManager,
cameraIDs,
this.timelineConfig.media,
this.timelineConfig.events_media_type,
this.timelineConfig.show_recordings,
);
} else {
+1 -1
View File
@@ -168,7 +168,7 @@ export class FrigateCardViewer extends LitElement {
{
allCameras: this.view.isGrid(),
targetView: 'media',
mediaType: mediaType,
eventsMediaType: mediaType,
},
);
}
+6 -3
View File
@@ -64,7 +64,7 @@ export class FrigateCardViews extends LitElement {
public microphoneManager?: ReadonlyMicrophoneManager;
@property({ attribute: false })
public triggeredCameraIDs?: Set<string>;
public triggeredCameraIDs?: Set<string>;
protected willUpdate(changedProps: PropertyValues): void {
if (changedProps.has('view') || changedProps.has('config')) {
@@ -163,8 +163,11 @@ export class FrigateCardViews extends LitElement {
class="${classMap(overallClasses)}"
.hass=${this.hass}
.view=${this.view}
.fetchMedia=${this.view?.is('live')
? this.overriddenConfig.live.controls.thumbnails.media
.fetchMediaType=${this.view?.is('live')
? this.overriddenConfig.live.controls.thumbnails.media_type
: undefined}
.fetchEventsMediaType=${this.view?.is('live')
? this.overriddenConfig.live.controls.thumbnails.events_media_type
: undefined}
.thumbnailConfig=${!this.hide ? thumbnailConfig : undefined}
.timelineConfig=${!this.hide ? miniTimelineConfig : undefined}
+20
View File
@@ -10,9 +10,13 @@ import {
CONF_CAMERAS_GLOBAL_JSMPEG,
CONF_CAMERAS_GLOBAL_WEBRTC_CARD,
CONF_ELEMENTS,
CONF_LIVE_CONTROLS_THUMBNAILS_EVENTS_MEDIA_TYPE,
CONF_LIVE_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE,
CONF_MEDIA_GALLERY,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE,
CONF_MENU_BUTTONS_CAMERA_UI,
CONF_OVERRIDES,
CONF_TIMELINE_EVENTS_MEDIA_TYPE,
CONF_VIEW_INTERACTION_SECONDS,
CONF_VIEW_SCAN_ACTIONS_UNTRIGGER,
} from './const';
@@ -516,4 +520,20 @@ const UPGRADES = [
upgradeWithOverrides('media_viewer.auto_unmute', (data) =>
data === 'all' ? ['selected', 'visible'] : data === 'never' ? null : arrayify(data),
),
upgradeMoveToWithOverrides(
'live.controls.thumbnails.media',
CONF_LIVE_CONTROLS_THUMBNAILS_EVENTS_MEDIA_TYPE,
),
upgradeMoveToWithOverrides(
'timeline.media',
CONF_TIMELINE_EVENTS_MEDIA_TYPE,
),
upgradeMoveToWithOverrides(
'live.controls.timeline.media',
CONF_LIVE_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE,
),
upgradeMoveToWithOverrides(
'media_viewer.controls.timeline.media',
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE,
),
];
+17 -8
View File
@@ -626,26 +626,31 @@ const thumbnailsControlSchema = thumbnailsControlBaseSchema.extend({
});
export type ThumbnailsControlConfig = z.infer<typeof thumbnailsControlSchema>;
// *************************************************************************
// Event Media Type Configuration
// *************************************************************************
const eventsMediaTypeSchema = z.enum(['all', 'clips', 'snapshots']);
// *************************************************************************
// Timeline Configuration
// *************************************************************************
const timelineCoreConfigDefault = {
clustering_threshold: 3,
media: 'all' as const,
events_media_type: 'all' as const,
window_seconds: 60 * 60,
show_recordings: true,
style: 'stack' as const,
};
const timelineMediaSchema = z.enum(['all', 'clips', 'snapshots']);
const timelineCoreConfigSchema = z.object({
clustering_threshold: z
.number()
.optional()
.default(timelineCoreConfigDefault.clustering_threshold),
media: timelineMediaSchema.optional().default(timelineCoreConfigDefault.media),
events_media_type: eventsMediaTypeSchema
.optional()
.default(timelineCoreConfigDefault.events_media_type),
window_seconds: z
.number()
.min(1 * 60)
@@ -867,7 +872,8 @@ export type FrigateCardPTZConfig = z.infer<typeof frigateCardPTZSchema>;
const liveThumbnailControlsDefaults = {
...thumbnailControlsDefaults,
media: 'all' as const,
media_type: 'events' as const,
events_media_type: 'all' as const,
};
const liveConfigDefault = {
@@ -899,9 +905,12 @@ const liveConfigDefault = {
};
const livethumbnailsControlSchema = thumbnailsControlSchema.extend({
media: z
.enum(['all', 'clips', 'snapshots'])
.default(liveConfigDefault.controls.thumbnails.media),
media_type: z
.enum(['events', 'recordings'])
.default(liveConfigDefault.controls.thumbnails.media_type),
events_media_type: eventsMediaTypeSchema.default(
liveConfigDefault.controls.thumbnails.events_media_type,
),
});
const liveOverridableConfigSchema = z
+9 -7
View File
@@ -146,8 +146,8 @@ export const CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SIZE =
`${CONF_MEDIA_VIEWER}.controls.thumbnails.size` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD =
`${CONF_MEDIA_VIEWER}.controls.timeline.clustering_threshold` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MEDIA =
`${CONF_MEDIA_VIEWER}.controls.timeline.media` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE =
`${CONF_MEDIA_VIEWER}.controls.timeline.events_media_type` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MODE =
`${CONF_MEDIA_VIEWER}.controls.timeline.mode` as const;
export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS =
@@ -189,8 +189,10 @@ export const CONF_LIVE_CONTROLS_PTZ_ORIENTATION =
`${CONF_LIVE}.controls.ptz.orientation` as const;
export const CONF_LIVE_CONTROLS_PTZ_POSITION =
`${CONF_LIVE}.controls.ptz.position` as const;
export const CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA =
`${CONF_LIVE}.controls.thumbnails.media` as const;
export const CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA_TYPE =
`${CONF_LIVE}.controls.thumbnails.media_type` as const;
export const CONF_LIVE_CONTROLS_THUMBNAILS_EVENTS_MEDIA_TYPE =
`${CONF_LIVE}.controls.thumbnails.events_media_type` as const;
export const CONF_LIVE_CONTROLS_THUMBNAILS_MODE =
`${CONF_LIVE}.controls.thumbnails.mode` as const;
export const CONF_LIVE_CONTROLS_THUMBNAILS_SIZE =
@@ -205,8 +207,8 @@ export const CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL =
`${CONF_LIVE}.controls.thumbnails.show_timeline_control` as const;
export const CONF_LIVE_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD =
`${CONF_LIVE}.controls.timeline.clustering_threshold` as const;
export const CONF_LIVE_CONTROLS_TIMELINE_MEDIA =
`${CONF_LIVE}.controls.timeline.media` as const;
export const CONF_LIVE_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE =
`${CONF_LIVE}.controls.timeline.events_media_type` as const;
export const CONF_LIVE_CONTROLS_TIMELINE_MODE =
`${CONF_LIVE}.controls.timeline.mode` as const;
export const CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS =
@@ -256,7 +258,7 @@ const CONF_TIMELINE = 'timeline' as const;
export const CONF_TIMELINE_WINDOW_SECONDS = `${CONF_TIMELINE}.window_seconds` as const;
export const CONF_TIMELINE_CLUSTERING_THRESHOLD =
`${CONF_TIMELINE}.clustering_threshold` as const;
export const CONF_TIMELINE_MEDIA = `${CONF_TIMELINE}.media` as const;
export const CONF_TIMELINE_EVENTS_MEDIA_TYPE = `${CONF_TIMELINE}.events_media_type` as const;
export const CONF_TIMELINE_SHOW_RECORDINGS = `${CONF_TIMELINE}.show_recordings` as const;
export const CONF_TIMELINE_STYLE = `${CONF_TIMELINE}.style` as const;
export const CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE =
+51 -27
View File
@@ -86,7 +86,8 @@ import {
CONF_LIVE_CONTROLS_PTZ_MODE,
CONF_LIVE_CONTROLS_PTZ_ORIENTATION,
CONF_LIVE_CONTROLS_PTZ_POSITION,
CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA,
CONF_LIVE_CONTROLS_THUMBNAILS_EVENTS_MEDIA_TYPE,
CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA_TYPE,
CONF_LIVE_CONTROLS_THUMBNAILS_MODE,
CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_DETAILS,
CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_DOWNLOAD_CONTROL,
@@ -94,7 +95,7 @@ import {
CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
CONF_LIVE_CONTROLS_THUMBNAILS_SIZE,
CONF_LIVE_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD,
CONF_LIVE_CONTROLS_TIMELINE_MEDIA,
CONF_LIVE_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE,
CONF_LIVE_CONTROLS_TIMELINE_MODE,
CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS,
CONF_LIVE_CONTROLS_TIMELINE_STYLE,
@@ -138,7 +139,7 @@ import {
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SIZE,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MEDIA,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MODE,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_STYLE,
@@ -174,7 +175,7 @@ import {
CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL,
CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL,
CONF_TIMELINE_CONTROLS_THUMBNAILS_SIZE,
CONF_TIMELINE_MEDIA,
CONF_TIMELINE_EVENTS_MEDIA_TYPE,
CONF_TIMELINE_SHOW_RECORDINGS,
CONF_TIMELINE_STYLE,
CONF_TIMELINE_WINDOW_SECONDS,
@@ -452,15 +453,27 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
},
];
protected _thumbnailMedias: EditorSelectOption[] = [
protected _thumbnailMediaTypes: EditorSelectOption[] = [
{ value: '', label: '' },
{
value: 'events',
label: localize('config.common.controls.thumbnails.media_types.events'),
},
{
value: 'recordings',
label: localize('config.common.controls.thumbnails.media_types.recordings'),
},
];
protected _thumbnailEventsMediaTypes: EditorSelectOption[] = [
{ value: '', label: '' },
{
value: 'clips',
label: localize('config.common.controls.thumbnails.medias.clips'),
label: localize('config.common.controls.thumbnails.events_media_types.clips'),
},
{
value: 'snapshots',
label: localize('config.common.controls.thumbnails.medias.snapshots'),
label: localize('config.common.controls.thumbnails.events_media_types.snapshots'),
},
];
@@ -498,11 +511,11 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
{ value: 'url', label: localize('config.image.modes.url') },
];
protected _timelineMediaTypes: EditorSelectOption[] = [
protected _timelineEventsMediaTypes: EditorSelectOption[] = [
{ value: '', label: '' },
{ value: 'all', label: localize('config.common.timeline.medias.all') },
{ value: 'clips', label: localize('config.common.timeline.medias.clips') },
{ value: 'snapshots', label: localize('config.common.timeline.medias.snapshots') },
{ value: 'all', label: localize('config.common.timeline.events_media_types.all') },
{ value: 'clips', label: localize('config.common.timeline.events_media_types.clips') },
{ value: 'snapshots', label: localize('config.common.timeline.events_media_types.snapshots') },
];
protected _timelineStyleTypes: EditorSelectOption[] = [
@@ -1149,7 +1162,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
* @param configPathStyle Timeline style config path.
* @param configPathWindowSeconds Timeline window config path.
* @param configPathClusteringThreshold Clustering threshold config path.
* @param configPathTimelineMedia Timeline media config path.
* @param configPathTimelineEventsMediaType Timeline media config path.
* @param configPathShowRecordings Show recordings config path.
* @param defaultShowRecordings Default value of show_recordings.
* @returns A rendered template.
@@ -1158,7 +1171,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
configPathStyle: string,
configPathWindowSeconds: string,
configPathClusteringThreshold: string,
configPathTimelineMedia: string,
configPathTimelineEventsMediaType: string,
configPathShowRecordings: string,
defaultShowRecordings: boolean,
): TemplateResult {
@@ -1175,8 +1188,8 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderNumberInput(configPathClusteringThreshold, {
label: localize(`config.common.${CONF_TIMELINE_CLUSTERING_THRESHOLD}`),
})}
${this._renderOptionSelector(configPathTimelineMedia, this._timelineMediaTypes, {
label: localize(`config.common.${CONF_TIMELINE_MEDIA}`),
${this._renderOptionSelector(configPathTimelineEventsMediaType, this._timelineEventsMediaTypes, {
label: localize(`config.common.${CONF_TIMELINE_EVENTS_MEDIA_TYPE}`),
})}
${this._renderSwitch(configPathShowRecordings, defaultShowRecordings, {
label: localize(`config.common.${CONF_TIMELINE_SHOW_RECORDINGS}`),
@@ -1188,7 +1201,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
* @param domain The submenu domain.
* @param configPathWindowSeconds Timeline window config path.
* @param configPathClusteringThreshold Clustering threshold config path.
* @param configPathTimelineMedia Timeline media config path.
* @param configPathTimelineEventsMediaType Timeline media config path.
* @param configPathShowRecordings Show recordings config path.
* @returns A rendered template.
*/
@@ -1198,7 +1211,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
configPathStyle: string,
configPathWindowSeconds: string,
configPathClusteringThreshold: string,
configPathTimelineMedia: string,
configPathTimelineEventsMediaType: string,
configPathShowRecordings: string,
showRecordingsDefault: boolean,
): TemplateResult | void {
@@ -1214,7 +1227,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
configPathStyle,
configPathWindowSeconds,
configPathClusteringThreshold,
configPathTimelineMedia,
configPathTimelineEventsMediaType,
configPathShowRecordings,
showRecordingsDefault,
)}`,
@@ -1330,7 +1343,8 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
show_download_control: boolean;
},
options?: {
configPathMedia?: string;
configPathMediaType?: string;
configPathEventsMediaType?: string;
configPathMode?: string;
},
): TemplateResult | void {
@@ -1349,12 +1363,21 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
},
)}`
: html``}
${options?.configPathMedia
${options?.configPathMediaType
? html`${this._renderOptionSelector(
options.configPathMedia,
this._thumbnailMedias,
options.configPathMediaType,
this._thumbnailMediaTypes,
{
label: localize('config.common.controls.thumbnails.media'),
label: localize('config.common.controls.thumbnails.media_type'),
},
)}`
: html``}
${options?.configPathEventsMediaType
? html`${this._renderOptionSelector(
options.configPathEventsMediaType,
this._thumbnailEventsMediaTypes,
{
label: localize('config.common.controls.thumbnails.events_media_type'),
},
)}`
: html``}
@@ -2101,7 +2124,8 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_DOWNLOAD_CONTROL,
this._defaults.live.controls.thumbnails,
{
configPathMedia: CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA,
configPathMediaType: CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA_TYPE,
configPathEventsMediaType: CONF_LIVE_CONTROLS_THUMBNAILS_EVENTS_MEDIA_TYPE,
configPathMode: CONF_LIVE_CONTROLS_THUMBNAILS_MODE,
},
)}
@@ -2116,7 +2140,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
CONF_LIVE_CONTROLS_TIMELINE_STYLE,
CONF_LIVE_CONTROLS_TIMELINE_WINDOW_SECONDS,
CONF_LIVE_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD,
CONF_LIVE_CONTROLS_TIMELINE_MEDIA,
CONF_LIVE_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE,
CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS,
this._defaults.live.controls.timeline.show_recordings,
)}
@@ -2309,7 +2333,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_STYLE,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_WINDOW_SECONDS,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MEDIA,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE,
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS,
this._defaults.media_viewer.controls.timeline.show_recordings,
)}
@@ -2347,7 +2371,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
CONF_TIMELINE_STYLE,
CONF_TIMELINE_WINDOW_SECONDS,
CONF_TIMELINE_CLUSTERING_THRESHOLD,
CONF_TIMELINE_MEDIA,
CONF_TIMELINE_EVENTS_MEDIA_TYPE,
CONF_TIMELINE_SHOW_RECORDINGS,
this._defaults.timeline.show_recordings,
)}
+33 -28
View File
@@ -18,8 +18,8 @@
"editor_label": "Cast Options",
"method": "Cast method",
"methods": {
"standard": "Standard",
"dashboard": "Dashboard"
"dashboard": "Dashboard",
"standard": "Standard"
}
},
"dependencies": {
@@ -119,11 +119,16 @@
},
"thumbnails": {
"editor_label": "Thumbnails",
"media": "Whether to show thumbnails of clips or snapshots",
"medias": {
"events_media_type": "Whether to show event thumbnails of clips or snapshots",
"events_media_types": {
"clips": "Clip thumbnails",
"snapshots": "Snapshot thumbnails"
},
"media_type": "Whether to show thumnails of events or recordings",
"media_types": {
"events": "Events",
"recordings": "Recordings"
},
"mode": "Thumbnails mode",
"modes": {
"above": "Thumbnails above",
@@ -191,9 +196,9 @@
},
"timeline": {
"clustering_threshold": "The count of events at which they are clustered (0=no clustering)",
"media": "The media the timeline displays",
"medias": {
"all": "All media types",
"events_media_type": "The events media the timeline displays",
"events_media_types": {
"all": "All event media types",
"clips": "Clips",
"snapshots": "Snapshots"
},
@@ -238,27 +243,27 @@
"editor_label": "Live Controls",
"ptz": {
"editor_label": "PTZ",
"hide_home": "Hide home control",
"hide_pan_tilt": "Hide pan & tilt control",
"hide_zoom": "Hide zoom control",
"mode": "Mode",
"modes": {
"on": "On",
"auto": "Automatic",
"off": "Off",
"auto": "Automatic"
"on": "On"
},
"orientation": "Orientation",
"orientations": {
"vertical": "Vertical",
"horizontal": "Horizontal"
"horizontal": "Horizontal",
"vertical": "Vertical"
},
"position": "Position",
"positions": {
"top-left": "Top left",
"top-right": "Top right",
"bottom-left": "Bottom left",
"bottom-right": "Bottom right"
},
"hide_zoom": "Hide zoom control",
"hide_pan_tilt": "Hide pan & tilt control",
"hide_home": "Hide home control"
"bottom-right": "Bottom right",
"top-left": "Top left",
"top-right": "Top right"
}
}
},
"draggable": "Live cameras view can be dragged/swiped",
@@ -314,8 +319,8 @@
"camera_ui": "Camera user interface",
"cameras": "Cameras",
"clips": "Clips",
"download": "Download",
"display_mode": "Display mode",
"download": "Download",
"enabled": "Button enabled",
"expand": "Expand",
"frigate": "Frigate menu / Default view",
@@ -387,6 +392,8 @@
"on": "On"
},
"default": "Default view",
"interaction_seconds": "Seconds after user action to remain interacted with (0=never)",
"reset_after_interaction": "Reset to the default view after user interaction",
"scan": {
"actions": {
"editor_label": "Scan mode actions",
@@ -408,13 +415,11 @@
}
},
"editor_label": "Scan mode",
"filter_selected_camera": "Only trigger on selected camera",
"enabled": "Scan mode enabled",
"filter_selected_camera": "Only trigger on selected camera",
"show_trigger_status": "Show pulsing border when triggered",
"untrigger_seconds": "Seconds after inactive state change to untrigger"
},
"interaction_seconds": "Seconds after user action to remain interacted with (0=never)",
"reset_after_interaction": "Reset to the default view after user interaction",
"update_cycle_camera": "Cycle through cameras when default view updates",
"update_force": "Force card updates (ignore user interaction)",
"update_seconds": "Refresh default view every X seconds (0=never)",
@@ -433,8 +438,8 @@
}
},
"display_modes": {
"single": "Show single media viewer",
"grid": "Show media viewer for each camera in a grid"
"grid": "Show media viewer for each camera in a grid",
"single": "Show single media viewer"
},
"editor": {
"add_new_camera": "Add new camera",
@@ -528,9 +533,6 @@
"what": "What",
"where": "Where"
},
"media_viewer": {
"unseekable": "Seek time not found in media"
},
"media_filter": {
"all": "All",
"camera": "Camera",
@@ -560,6 +562,9 @@
},
"where": "Where"
},
"media_viewer": {
"unseekable": "Seek time not found in media"
},
"recording": {
"camera": "Camera",
"duration": "Duration",
@@ -583,4 +588,4 @@
},
"select_date": "Choose date"
}
}
}
+9 -4
View File
@@ -119,11 +119,16 @@
},
"thumbnails": {
"editor_label": "Miniature",
"media": "Se mostrare miniature di clip o istantanee",
"medias": {
"events_media_type": "Se mostrare miniature di clip o istantanee",
"events_media_types": {
"clips": "Miniature di clip",
"snapshots": "Miniature istantanee"
},
"media_type": "",
"media_types": {
"events": "",
"recordings": ""
},
"mode": "Modalità miniatura",
"modes": {
"above": "Miniature sopra",
@@ -191,8 +196,8 @@
},
"timeline": {
"clustering_threshold": "Il conteggio degli eventi in cui sono raggruppati (0 = nessun clustering)",
"media": "I media vengono visualizzati la sequenza temporale",
"medias": {
"events_media_type": "I media vengono visualizzati la sequenza temporale",
"events_media_types": {
"all": "Tutti i tipi di media",
"clips": "Clip",
"snapshots": "Istantanee"
+9 -4
View File
@@ -119,11 +119,16 @@
},
"thumbnails": {
"editor_label": "Miniaturas",
"media": "Se deve mostrar miniaturas de clipes ou snapshots",
"medias": {
"events_media_type": "Se deve mostrar miniaturas de clipes ou snapshots",
"events_media_types": {
"clips": "Miniaturas de clipes",
"snapshots": "Miniaturas de Snapshots"
},
"media_type": "",
"media_types": {
"events": "",
"recordings": ""
},
"mode": "Modo de miniaturas",
"modes": {
"above": "Miniaturas acima da mídia",
@@ -191,8 +196,8 @@
},
"timeline": {
"clustering_threshold": "A contagem de eventos nos quais eles são agrupados (0 = sem agrupamento)",
"media": "A mídia que a linha do tempo exibe",
"medias": {
"events_media_type": "A mídia que a linha do tempo exibe",
"events_media_types": {
"all": "Todos os tipos de mídia",
"clips": "Clipes",
"snapshots": "Instantâneos"
+9 -4
View File
@@ -119,11 +119,16 @@
},
"thumbnails": {
"editor_label": "Editor de titulos",
"media": "Mostrar miniaturas de clipes ou snapshots",
"medias": {
"events_media_type": "Mostrar miniaturas de clipes ou snapshots",
"events_media_types": {
"clips": "Miniaturas de clipes",
"snapshots": "Miniaturas de Snapshots"
},
"media_type": "",
"media_types": {
"events": "",
"recordings": ""
},
"mode": "Modos",
"modes": {
"above": "Miniaturas acima da mídia",
@@ -191,8 +196,8 @@
},
"timeline": {
"clustering_threshold": "A contagem de eventos nos quais eles são agrupados (0 = sem agrupamento)",
"media": "A mídia que a linha do tempo exibe",
"medias": {
"events_media_type": "A mídia que a linha do tempo exibe",
"events_media_types": {
"all": "Todos os tipos de mídia",
"clips": "Clipes",
"snapshots": "Instantâneos"
+1
View File
@@ -4,6 +4,7 @@ import { z } from 'zod';
export type ClipsOrSnapshots = 'clips' | 'snapshots';
export type ClipsOrSnapshotsOrAll = 'clips' | 'snapshots' | 'all';
export type EventsOrRecordings = 'events' | 'recordings';
export class FrigateCardError extends Error {
context?: unknown;
+6 -6
View File
@@ -24,7 +24,7 @@ export const changeViewToRecentEventsForCameraAndDependents = async (
view: View,
options?: {
allCameras?: boolean;
mediaType?: ClipsOrSnapshotsOrAll;
eventsMediaType?: ClipsOrSnapshotsOrAll;
targetView?: FrigateCardView;
select?: ResultSelectType;
},
@@ -37,7 +37,7 @@ export const changeViewToRecentEventsForCameraAndDependents = async (
}
const queries = createQueriesForEventsView(cameraManager, cardWideConfig, cameraIDs, {
mediaType: options?.mediaType,
eventsMediaType: options?.eventsMediaType,
});
if (!queries) {
return;
@@ -62,15 +62,15 @@ const createQueriesForEventsView = (
cardWideConfig: CardWideConfig,
cameraIDs: Set<string>,
options?: {
mediaType?: ClipsOrSnapshotsOrAll;
eventsMediaType?: ClipsOrSnapshotsOrAll;
},
): EventMediaQueries | null => {
const limit =
cardWideConfig.performance?.features.media_chunk_size ?? MEDIA_CHUNK_SIZE_DEFAULT;
const eventQueries = cameraManager.generateDefaultEventQueries(cameraIDs, {
limit: limit,
...(options?.mediaType === 'clips' && { hasClip: true }),
...(options?.mediaType === 'snapshots' && { hasSnapshot: true }),
...(options?.eventsMediaType === 'clips' && { hasClip: true }),
...(options?.eventsMediaType === 'snapshots' && { hasSnapshot: true }),
});
return eventQueries ? new EventMediaQueries(eventQueries) : null;
};
@@ -91,7 +91,7 @@ export const changeViewToRecentRecordingForCameraAndDependents = async (
view: View,
options?: {
allCameras?: boolean;
targetView?: 'recording' | 'recordings';
targetView?: FrigateCardView;
select?: ResultSelectType;
},
): Promise<void> => {
+100
View File
@@ -1590,5 +1590,105 @@ describe('should handle version specific upgrades', () => {
});
});
});
describe('should rename thumbnails.media to thumbnails.events_media_type', () => {
it.each([['all' as const], ['clips' as const], ['snapshots' as const]])(
'%s',
(mediaEventType: string) => {
const config = {
live: {
controls: {
thumbnails: {
media: mediaEventType,
},
},
},
};
expect(upgradeConfig(config)).toBeTruthy();
expect(config).toEqual({
live: {
controls: {
thumbnails: {
events_media_type: mediaEventType,
},
},
},
});
},
);
});
describe('should rename timeline.media to timeline.events_media_type', () => {
it.each([['all' as const], ['clips' as const], ['snapshots' as const]])(
'%s',
(mediaEventType: string) => {
const config = {
timeline: {
media: mediaEventType,
},
};
expect(upgradeConfig(config)).toBeTruthy();
expect(config).toEqual({
timeline: {
events_media_type: mediaEventType,
},
});
},
);
});
describe('should rename live.controls.timeline.media to live.controls.timeline.events_media_type', () => {
it.each([['all' as const], ['clips' as const], ['snapshots' as const]])(
'%s',
(mediaEventType: string) => {
const config = {
live: {
controls: {
timeline: {
media: mediaEventType,
},
},
},
};
expect(upgradeConfig(config)).toBeTruthy();
expect(config).toEqual({
live: {
controls: {
timeline: {
events_media_type: mediaEventType,
},
},
},
});
},
);
});
describe('should rename media_viewer.controls.timeline.media to media_viewer.controls.timeline.events_media_type', () => {
it.each([['all' as const], ['clips' as const], ['snapshots' as const]])(
'%s',
(mediaEventType: string) => {
const config = {
media_viewer: {
controls: {
timeline: {
media: mediaEventType,
},
},
},
};
expect(upgradeConfig(config)).toBeTruthy();
expect(config).toEqual({
media_viewer: {
controls: {
timeline: {
events_media_type: mediaEventType,
},
},
},
});
},
);
});
});
});
+5 -4
View File
@@ -77,7 +77,8 @@ describe('config defaults', () => {
position: 'bottom-right',
},
thumbnails: {
media: 'all',
media_type: 'events',
events_media_type: 'all',
mode: 'right',
show_details: true,
show_download_control: true,
@@ -87,7 +88,7 @@ describe('config defaults', () => {
},
timeline: {
clustering_threshold: 3,
media: 'all',
events_media_type: 'all',
mode: 'none',
show_recordings: true,
style: 'ribbon',
@@ -143,7 +144,7 @@ describe('config defaults', () => {
},
timeline: {
clustering_threshold: 3,
media: 'all',
events_media_type: 'all',
mode: 'none',
show_recordings: true,
style: 'ribbon',
@@ -268,7 +269,7 @@ describe('config defaults', () => {
size: 100,
},
},
media: 'all',
events_media_type: 'all',
show_recordings: true,
style: 'stack',
window_seconds: 3600,
+2 -2
View File
@@ -101,7 +101,7 @@ describe('changeViewToRecentEventsForCameraAndDependents', () => {
{},
createView(),
{
mediaType: 'clips',
eventsMediaType: 'clips',
},
);
expect(elementHandler.viewHandler).not.toBeCalled();
@@ -222,7 +222,7 @@ describe('changeViewToRecentEventsForCameraAndDependents', () => {
{},
createView(),
{
mediaType: mediaType,
eventsMediaType: mediaType,
},
);
+3 -3
View File
@@ -10,10 +10,10 @@ export default defineConfig({
// Thresholds will automatically be updated as coverage improves to avoid
// back-sliding.
thresholdAutoUpdate: true,
statements: 72.72,
branches: 61.97,
statements: 72.73,
branches: 61.93,
functions: 74.01,
lines: 72.62,
lines: 72.63,
},
},
});