mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
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:
parent
79fabbb6b0
commit
08ef69bac4
@ -42,6 +42,9 @@ def should_update_state(prev_event: Event, current_event: Event) -> bool:
|
|||||||
if prev_event["stationary"] != current_event["stationary"]:
|
if prev_event["stationary"] != current_event["stationary"]:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if prev_event["attributes"] != current_event["attributes"]:
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,6 +74,7 @@ class TimelineProcessor(threading.Thread):
|
|||||||
camera_config.detect.height,
|
camera_config.detect.height,
|
||||||
event_data["region"],
|
event_data["region"],
|
||||||
),
|
),
|
||||||
|
"attribute": "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if event_type == "start":
|
if event_type == "start":
|
||||||
@ -93,6 +94,12 @@ class TimelineProcessor(threading.Thread):
|
|||||||
"stationary" if event_data["stationary"] else "active"
|
"stationary" if event_data["stationary"] else "active"
|
||||||
)
|
)
|
||||||
Timeline.insert(timeline_entry).execute()
|
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":
|
elif event_type == "end":
|
||||||
timeline_entry[Timeline.class_type] = "gone"
|
timeline_entry[Timeline.class_type] = "gone"
|
||||||
Timeline.insert(timeline_entry).execute()
|
Timeline.insert(timeline_entry).execute()
|
||||||
|
@ -7,7 +7,10 @@ import ActiveObjectIcon from '../icons/ActiveObject';
|
|||||||
import PlayIcon from '../icons/Play';
|
import PlayIcon from '../icons/Play';
|
||||||
import ExitIcon from '../icons/Exit';
|
import ExitIcon from '../icons/Exit';
|
||||||
import StationaryObjectIcon from '../icons/StationaryObject';
|
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 { useMemo, useState } from 'preact/hooks';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
|
||||||
@ -88,7 +91,7 @@ export default function TimelineSummary({ event, onFrameSelected }) {
|
|||||||
aria-label={window.innerWidth > 640 ? getTimelineItemDescription(config, item, event) : ''}
|
aria-label={window.innerWidth > 640 ? getTimelineItemDescription(config, item, event) : ''}
|
||||||
onClick={() => onSelectMoment(index)}
|
onClick={() => onSelectMoment(index)}
|
||||||
>
|
>
|
||||||
{getTimelineIcon(item.class_type)}
|
{getTimelineIcon(item)}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -113,8 +116,8 @@ export default function TimelineSummary({ event, onFrameSelected }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTimelineIcon(classType) {
|
function getTimelineIcon(timelineItem) {
|
||||||
switch (classType) {
|
switch (timelineItem.class_type) {
|
||||||
case 'visible':
|
case 'visible':
|
||||||
return <PlayIcon className="w-8" />;
|
return <PlayIcon className="w-8" />;
|
||||||
case 'gone':
|
case 'gone':
|
||||||
@ -124,7 +127,16 @@ function getTimelineIcon(classType) {
|
|||||||
case 'stationary':
|
case 'stationary':
|
||||||
return <StationaryObjectIcon className="w-8" />;
|
return <StationaryObjectIcon className="w-8" />;
|
||||||
case 'entered_zone':
|
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_style: 'medium',
|
||||||
time_format: config.ui.time_format,
|
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':
|
case 'gone':
|
||||||
return `${event.label} left at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
|
return `${event.label} left at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
|
||||||
date_style: 'short',
|
date_style: 'short',
|
||||||
|
15
web/src/icons/DeliveryTruck.jsx
Normal file
15
web/src/icons/DeliveryTruck.jsx
Normal 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
22
web/src/icons/Face.jsx
Normal 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);
|
15
web/src/icons/LicensePlate.jsx
Normal file
15
web/src/icons/LicensePlate.jsx
Normal 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);
|
Loading…
Reference in New Issue
Block a user