Add recordings timeline entry for frigate+ attributes (#8063)

* Add attribute item to timeline

* Add face icon

* Add support for other icons

* Cleanup

* Ensure attributes are only updated once

* don't show _ in attributes
This commit is contained in:
Nicolas Mowen 2023-10-07 08:17:18 -06:00 committed by GitHub
parent 79fabbb6b0
commit 08ef69bac4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 5 deletions

View File

@ -42,6 +42,9 @@ def should_update_state(prev_event: Event, current_event: Event) -> bool:
if prev_event["stationary"] != current_event["stationary"]:
return True
if prev_event["attributes"] != current_event["attributes"]:
return True
return False

View File

@ -74,6 +74,7 @@ class TimelineProcessor(threading.Thread):
camera_config.detect.height,
event_data["region"],
),
"attribute": "",
},
}
if event_type == "start":
@ -93,6 +94,12 @@ class TimelineProcessor(threading.Thread):
"stationary" if event_data["stationary"] else "active"
)
Timeline.insert(timeline_entry).execute()
elif prev_event_data["attributes"] == {} and event_data["attributes"] != {}:
timeline_entry[Timeline.class_type] = "attribute"
timeline_entry[Timeline.data]["attribute"] = list(
event_data["attributes"].keys()
)[0]
Timeline.insert(timeline_entry).execute()
elif event_type == "end":
timeline_entry[Timeline.class_type] = "gone"
Timeline.insert(timeline_entry).execute()

View File

@ -7,7 +7,10 @@ import ActiveObjectIcon from '../icons/ActiveObject';
import PlayIcon from '../icons/Play';
import ExitIcon from '../icons/Exit';
import StationaryObjectIcon from '../icons/StationaryObject';
import { Zone } from '../icons/Zone';
import FaceIcon from '../icons/Face';
import LicensePlateIcon from '../icons/LicensePlate';
import DeliveryTruckIcon from '../icons/DeliveryTruck';
import ZoneIcon from '../icons/Zone';
import { useMemo, useState } from 'preact/hooks';
import Button from './Button';
@ -88,7 +91,7 @@ export default function TimelineSummary({ event, onFrameSelected }) {
aria-label={window.innerWidth > 640 ? getTimelineItemDescription(config, item, event) : ''}
onClick={() => onSelectMoment(index)}
>
{getTimelineIcon(item.class_type)}
{getTimelineIcon(item)}
</Button>
))}
</div>
@ -113,8 +116,8 @@ export default function TimelineSummary({ event, onFrameSelected }) {
);
}
function getTimelineIcon(classType) {
switch (classType) {
function getTimelineIcon(timelineItem) {
switch (timelineItem.class_type) {
case 'visible':
return <PlayIcon className="w-8" />;
case 'gone':
@ -124,7 +127,16 @@ function getTimelineIcon(classType) {
case 'stationary':
return <StationaryObjectIcon className="w-8" />;
case 'entered_zone':
return <Zone className="w-8" />;
return <ZoneIcon className="w-8" />;
case 'attribute':
switch (timelineItem.data.attribute) {
case 'face':
return <FaceIcon className="w-8" />;
case 'license_plate':
return <LicensePlateIcon className="w-8" />;
default:
return <DeliveryTruckIcon className="w-8" />;
}
}
}
@ -156,6 +168,15 @@ function getTimelineItemDescription(config, timelineItem, event) {
time_style: 'medium',
time_format: config.ui.time_format,
})}`;
case 'attribute':
return `${timelineItem.data.attribute.replaceAll("_", " ")} detected for ${event.label} at ${formatUnixTimestampToDateTime(
timelineItem.timestamp,
{
date_style: 'short',
time_style: 'medium',
time_format: config.ui.time_format,
}
)}`;
case 'gone':
return `${event.label} left at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
date_style: 'short',

View File

@ -0,0 +1,15 @@
import { h } from 'preact';
import { memo } from 'preact/compat';
export function StationaryObject({ className = '' }) {
return (
<svg className={`fill-current ${className}`} viewBox="0 -960 960 960">
<path
fill="currentColor"
d="M240-160q-50 0-85-35t-35-85H40v-440q0-33 23.5-56.5T120-800h560v160h120l120 160v200h-80q0 50-35 85t-85 35q-50 0-85-35t-35-85H360q0 50-35 85t-85 35Zm0-80q17 0 28.5-11.5T280-280q0-17-11.5-28.5T240-320q-17 0-28.5 11.5T200-280q0 17 11.5 28.5T240-240ZM120-360h32q17-18 39-29t49-11q27 0 49 11t39 29h272v-360H120v360Zm600 120q17 0 28.5-11.5T760-280q0-17-11.5-28.5T720-320q-17 0-28.5 11.5T680-280q0 17 11.5 28.5T720-240Zm-40-200h170l-90-120h-80v120ZM360-540Z"
/>
</svg>
);
}
export default memo(StationaryObject);

22
web/src/icons/Face.jsx Normal file
View File

@ -0,0 +1,22 @@
import { h } from 'preact';
import { memo } from 'preact/compat';
export function Zone({ className = 'h-6 w-6', stroke = 'currentColor', fill = 'none', onClick = () => {} }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={className}
fill={fill}
viewBox="0 -960 960 960"
stroke={stroke}
onClick={onClick}
>
<path
fill="currentColor"
d="M360-390q-21 0-35.5-14.5T310-440q0-21 14.5-35.5T360-490q21 0 35.5 14.5T410-440q0 21-14.5 35.5T360-390Zm240 0q-21 0-35.5-14.5T550-440q0-21 14.5-35.5T600-490q21 0 35.5 14.5T650-440q0 21-14.5 35.5T600-390ZM480-160q134 0 227-93t93-227q0-24-3-46.5T786-570q-21 5-42 7.5t-44 2.5q-91 0-172-39T390-708q-32 78-91.5 135.5T160-486v6q0 134 93 227t227 93Zm0 80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm-54-715q42 70 114 112.5T700-640q14 0 27-1.5t27-3.5q-42-70-114-112.5T480-800q-14 0-27 1.5t-27 3.5ZM177-581q51-29 89-75t57-103q-51 29-89 75t-57 103Zm249-214Zm-103 36Z"
/>
</svg>
);
}
export default memo(Zone);

View File

@ -0,0 +1,15 @@
import { h } from 'preact';
import { memo } from 'preact/compat';
export function StationaryObject({ className = '' }) {
return (
<svg className={`fill-current ${className}`} viewBox="0 -960 960 960">
<path
fill="currentColor"
d="M400-280h360v-240H400v240ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm0-80h640v-480H160v480Zm0 0v-480 480Z"
/>
</svg>
);
}
export default memo(StationaryObject);