mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Add support for stationary and active timeline entires (#7344)
* Add support for stationary and active timeline entires * Be sure to update stored event data copy with stationary
This commit is contained in:
parent
f57d21039e
commit
b7ff6735f6
@ -37,6 +37,14 @@ def should_update_db(prev_event: Event, current_event: Event) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def should_update_state(prev_event: Event, current_event: Event) -> bool:
|
||||
"""If current event should update state, but not necessarily update the db."""
|
||||
if prev_event["stationary"] != current_event["stationary"]:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class EventProcessor(threading.Thread):
|
||||
def __init__(
|
||||
self,
|
||||
@ -107,8 +115,11 @@ class EventProcessor(threading.Thread):
|
||||
event_data: Event,
|
||||
) -> None:
|
||||
"""handle tracked object event updates."""
|
||||
updated_db = False
|
||||
|
||||
# if this is the first message, just store it and continue, its not time to insert it in the db
|
||||
if should_update_db(self.events_in_process[event_data["id"]], event_data):
|
||||
updated_db = True
|
||||
camera_config = self.config.cameras[camera]
|
||||
event_config: EventsConfig = camera_config.record.events
|
||||
width = camera_config.detect.width
|
||||
@ -210,6 +221,10 @@ class EventProcessor(threading.Thread):
|
||||
.execute()
|
||||
)
|
||||
|
||||
# check if the stored event_data should be updated
|
||||
if updated_db or should_update_state(
|
||||
self.events_in_process[event_data["id"]], event_data
|
||||
):
|
||||
# update the stored copy for comparison on future update messages
|
||||
self.events_in_process[event_data["id"]] = event_data
|
||||
|
||||
|
@ -79,14 +79,20 @@ class TimelineProcessor(threading.Thread):
|
||||
if event_type == "start":
|
||||
timeline_entry[Timeline.class_type] = "visible"
|
||||
Timeline.insert(timeline_entry).execute()
|
||||
elif (
|
||||
event_type == "update"
|
||||
and prev_event_data["current_zones"] != event_data["current_zones"]
|
||||
elif event_type == "update":
|
||||
# zones have been updated
|
||||
if (
|
||||
prev_event_data["current_zones"] != event_data["current_zones"]
|
||||
and len(event_data["current_zones"]) > 0
|
||||
):
|
||||
timeline_entry[Timeline.class_type] = "entered_zone"
|
||||
timeline_entry[Timeline.data]["zones"] = event_data["current_zones"]
|
||||
Timeline.insert(timeline_entry).execute()
|
||||
elif prev_event_data["stationary"] != event_data["stationary"]:
|
||||
timeline_entry[Timeline.class_type] = (
|
||||
"stationary" if event_data["stationary"] else "active"
|
||||
)
|
||||
Timeline.insert(timeline_entry).execute()
|
||||
elif event_type == "end":
|
||||
timeline_entry[Timeline.class_type] = "gone"
|
||||
Timeline.insert(timeline_entry).execute()
|
||||
|
@ -3,8 +3,10 @@ import useSWR from 'swr';
|
||||
import ActivityIndicator from './ActivityIndicator';
|
||||
import { formatUnixTimestampToDateTime } from '../utils/dateUtil';
|
||||
import About from '../icons/About';
|
||||
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 { useMemo, useState } from 'preact/hooks';
|
||||
import Button from './Button';
|
||||
@ -77,8 +79,7 @@ export default function TimelineSummary({ event, onFrameSelected }) {
|
||||
<div className="flex flex-col">
|
||||
<div className="h-14 flex justify-center">
|
||||
<div className="sm:w-1 md:w-1/4 flex flex-row flex-nowrap justify-between overflow-auto">
|
||||
{eventTimeline.map((item, index) =>
|
||||
item.class_type == 'visible' || item.class_type == 'gone' ? (
|
||||
{eventTimeline.map((item, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
className="rounded-full"
|
||||
@ -87,21 +88,9 @@ export default function TimelineSummary({ event, onFrameSelected }) {
|
||||
aria-label={window.innerWidth > 640 ? getTimelineItemDescription(config, item, event) : ''}
|
||||
onClick={() => onSelectMoment(index)}
|
||||
>
|
||||
{item.class_type == 'visible' ? <PlayIcon className="w-8" /> : <ExitIcon className="w-8" />}
|
||||
{getTimelineIcon(item.class_type)}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
key={index}
|
||||
className="rounded-full"
|
||||
type="iconOnly"
|
||||
color={index == timeIndex ? 'blue' : 'gray'}
|
||||
aria-label={window.innerWidth > 640 ? getTimelineItemDescription(config, item, event) : ''}
|
||||
onClick={() => onSelectMoment(index)}
|
||||
>
|
||||
<Zone className="w-8" />
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{timeIndex >= 0 ? (
|
||||
@ -124,14 +113,30 @@ export default function TimelineSummary({ event, onFrameSelected }) {
|
||||
);
|
||||
}
|
||||
|
||||
function getTimelineIcon(classType) {
|
||||
switch (classType) {
|
||||
case 'visible':
|
||||
return <PlayIcon className="w-8" />;
|
||||
case 'gone':
|
||||
return <ExitIcon className="w-8" />;
|
||||
case 'active':
|
||||
return <ActiveObjectIcon className="w-8" />;
|
||||
case 'stationary':
|
||||
return <StationaryObjectIcon className="w-8" />;
|
||||
case 'entered_zone':
|
||||
return <Zone className="w-8" />;
|
||||
}
|
||||
}
|
||||
|
||||
function getTimelineItemDescription(config, timelineItem, event) {
|
||||
if (timelineItem.class_type == 'visible') {
|
||||
switch (timelineItem.class_type) {
|
||||
case 'visible':
|
||||
return `${event.label} detected at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
|
||||
date_style: 'short',
|
||||
time_style: 'medium',
|
||||
time_format: config.ui.time_format,
|
||||
})}`;
|
||||
} else if (timelineItem.class_type == 'entered_zone') {
|
||||
case 'entered_zone':
|
||||
return `${event.label.replaceAll('_', ' ')} entered ${timelineItem.data.zones
|
||||
.join(' and ')
|
||||
.replaceAll('_', ' ')} at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
|
||||
@ -139,11 +144,23 @@ function getTimelineItemDescription(config, timelineItem, event) {
|
||||
time_style: 'medium',
|
||||
time_format: config.ui.time_format,
|
||||
})}`;
|
||||
}
|
||||
|
||||
case 'active':
|
||||
return `${event.label} became active at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
|
||||
date_style: 'short',
|
||||
time_style: 'medium',
|
||||
time_format: config.ui.time_format,
|
||||
})}`;
|
||||
case 'stationary':
|
||||
return `${event.label} became stationary 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',
|
||||
time_style: 'medium',
|
||||
time_format: config.ui.time_format,
|
||||
})}`;
|
||||
}
|
||||
}
|
||||
|
15
web/src/icons/ActiveObject.jsx
Normal file
15
web/src/icons/ActiveObject.jsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { h } from 'preact';
|
||||
import { memo } from 'preact/compat';
|
||||
|
||||
export function ActiveObject({ className = '' }) {
|
||||
return (
|
||||
<svg className={`fill-current ${className}`} viewBox="0 -960 960 960">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M360-80q-58 0-109-22t-89-60q-38-38-60-89T80-360q0-81 42-148t110-102q20-39 49.5-68.5T350-728q33-68 101-110t149-42q58 0 109 22t89 60q38 38 60 89t22 109q0 85-42 150T728-350q-20 39-49.5 68.5T610-232q-35 68-102 110T360-80Zm0-80q33 0 63.5-10t56.5-30q-58 0-109-22t-89-60q-38-38-60-89t-22-109q-20 26-30 56.5T160-360q0 42 16 78t43 63q27 27 63 43t78 16Zm120-120q33 0 64.5-10t57.5-30q-59 0-110-22.5T403-403q-38-38-60.5-89T320-602q-20 26-30 57.5T280-480q0 42 15.5 78t43.5 63q27 28 63 43.5t78 15.5Zm120-120q18 0 34.5-3t33.5-9q22-60 6.5-115.5T621-621q-38-38-93.5-53.5T412-668q-6 17-9 33.5t-3 34.5q0 42 15.5 78t43.5 63q27 28 63 43.5t78 15.5Zm160-78q20-26 30-57.5t10-64.5q0-42-15.5-78T741-741q-27-28-63-43.5T600-800q-35 0-65.5 10T478-760q59 0 110 22.5t89 60.5q38 38 60.5 89T760-478Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(ActiveObject);
|
15
web/src/icons/StationaryObject.jsx
Normal file
15
web/src/icons/StationaryObject.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="M480-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-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(StationaryObject);
|
Loading…
Reference in New Issue
Block a user