diff --git a/frigate/events/maintainer.py b/frigate/events/maintainer.py index f2989c065..40057958c 100644 --- a/frigate/events/maintainer.py +++ b/frigate/events/maintainer.py @@ -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 diff --git a/frigate/timeline.py b/frigate/timeline.py index 861dbe411..73c0a61b4 100644 --- a/frigate/timeline.py +++ b/frigate/timeline.py @@ -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"] - 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 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() diff --git a/web/src/components/TimelineSummary.jsx b/web/src/components/TimelineSummary.jsx index 8bf5bd1d7..92d79012e 100644 --- a/web/src/components/TimelineSummary.jsx +++ b/web/src/components/TimelineSummary.jsx @@ -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,31 +79,18 @@ export default function TimelineSummary({ event, onFrameSelected }) {
- {eventTimeline.map((item, index) => - item.class_type == 'visible' || item.class_type == 'gone' ? ( - - ) : ( - - ) - )} + {eventTimeline.map((item, index) => ( + + ))}
{timeIndex >= 0 ? ( @@ -124,26 +113,54 @@ export default function TimelineSummary({ event, onFrameSelected }) { ); } -function getTimelineItemDescription(config, timelineItem, event) { - if (timelineItem.class_type == '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') { - return `${event.label.replaceAll('_', ' ')} entered ${timelineItem.data.zones - .join(' and ') - .replaceAll('_', ' ')} at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { - date_style: 'short', - time_style: 'medium', - time_format: config.ui.time_format, - })}`; +function getTimelineIcon(classType) { + switch (classType) { + case 'visible': + return ; + case 'gone': + return ; + case 'active': + return ; + case 'stationary': + return ; + case 'entered_zone': + return ; + } +} + +function getTimelineItemDescription(config, timelineItem, event) { + 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, + })}`; + case 'entered_zone': + return `${event.label.replaceAll('_', ' ')} entered ${timelineItem.data.zones + .join(' and ') + .replaceAll('_', ' ')} at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { + date_style: 'short', + 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, + })}`; } - - return `${event.label} left at ${formatUnixTimestampToDateTime(timelineItem.timestamp, { - date_style: 'short', - time_style: 'medium', - time_format: config.ui.time_format, - })}`; } diff --git a/web/src/icons/ActiveObject.jsx b/web/src/icons/ActiveObject.jsx new file mode 100644 index 000000000..75f8da5e6 --- /dev/null +++ b/web/src/icons/ActiveObject.jsx @@ -0,0 +1,15 @@ +import { h } from 'preact'; +import { memo } from 'preact/compat'; + +export function ActiveObject({ className = '' }) { + return ( + + + + ); +} + +export default memo(ActiveObject); diff --git a/web/src/icons/StationaryObject.jsx b/web/src/icons/StationaryObject.jsx new file mode 100644 index 000000000..12ff746bc --- /dev/null +++ b/web/src/icons/StationaryObject.jsx @@ -0,0 +1,15 @@ +import { h } from 'preact'; +import { memo } from 'preact/compat'; + +export function StationaryObject({ className = '' }) { + return ( + + + + ); +} + +export default memo(StationaryObject);