diff --git a/frigate/http.py b/frigate/http.py index cac6a3a93..f4388a881 100644 --- a/frigate/http.py +++ b/frigate/http.py @@ -391,6 +391,17 @@ def set_sub_label(id): new_sub_label = json.get("subLabel") new_score = json.get("subLabelScore") + if new_sub_label is None: + return make_response( + jsonify( + { + "success": False, + "message": "A sub label must be supplied", + } + ), + 400, + ) + if new_sub_label and len(new_sub_label) > 100: return make_response( jsonify( @@ -416,6 +427,7 @@ def set_sub_label(id): ) if not event.end_time: + # update tracked object tracked_obj: TrackedObject = ( current_app.detected_frames_processor.camera_states[ event.camera @@ -425,6 +437,11 @@ def set_sub_label(id): if tracked_obj: tracked_obj.obj_data["sub_label"] = (new_sub_label, new_score) + # update timeline items + Timeline.update( + data=Timeline.data.update({"sub_label": (new_sub_label, new_score)}) + ).where(Timeline.source_id == id).execute() + event.sub_label = new_sub_label if new_score: diff --git a/frigate/timeline.py b/frigate/timeline.py index 39cb6cf03..22a02763e 100644 --- a/frigate/timeline.py +++ b/frigate/timeline.py @@ -7,7 +7,6 @@ from multiprocessing import Queue from multiprocessing.synchronize import Event as MpEvent from frigate.config import FrigateConfig -from frigate.const import ALL_ATTRIBUTE_LABELS from frigate.events.maintainer import EventTypeEnum from frigate.models import Timeline from frigate.util.builtin import to_relative_box @@ -109,22 +108,6 @@ class TimelineProcessor(threading.Thread): }, } - # make sure all timeline items have a sub label - if prev_event_data != None and prev_event_data.get( - "sub_label" - ) != event_data.get("sub_label"): - # update existing timeline items to have sub label - sub_label = event_data["sub_label"] - - if sub_label[0] not in ALL_ATTRIBUTE_LABELS: - if event_id in self.pre_event_cache.keys(): - for e in self.pre_event_cache[event_id]: - e[Timeline.data]["sub_label"] = sub_label - else: - Timeline.update( - data=Timeline.data.update({"sub_label": sub_label}) - ).where(Timeline.source_id == event_id).execute() - if event_type == "start": timeline_entry[Timeline.class_type] = "visible" save = True diff --git a/web/src/components/bar/TimelineBar.tsx b/web/src/components/bar/TimelineBar.tsx new file mode 100644 index 000000000..9039c2f3c --- /dev/null +++ b/web/src/components/bar/TimelineBar.tsx @@ -0,0 +1,185 @@ +import { FrigateConfig } from "@/types/frigateConfig"; +import { GraphDataPoint } from "@/types/graph"; +import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; +import useSWR from "swr"; + +type TimelineBarProps = { + startTime: number; + graphData: + | { + objects: number[]; + motion: GraphDataPoint[]; + } + | undefined; + onClick?: () => void; +}; +export default function TimelineBar({ + startTime, + graphData, + onClick, +}: TimelineBarProps) { + const { data: config } = useSWR("config"); + + return ( +
+ {graphData != undefined && ( +
+ {getHourBlocks().map((idx) => { + return ( +
+ ); + })} +
+
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: + config?.ui.time_format == "24hour" ? "%H:00" : "%I:00%P", + time_style: "medium", + date_style: "medium", + })} +
+
+
+
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: + config?.ui.time_format == "24hour" ? "%H:05" : "%I:05%P", + time_style: "medium", + date_style: "medium", + })} +
+
+
+
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: + config?.ui.time_format == "24hour" ? "%H:10" : "%I:10%P", + time_style: "medium", + date_style: "medium", + })} +
+
+
+
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: + config?.ui.time_format == "24hour" ? "%H:15" : "%I:15%P", + time_style: "medium", + date_style: "medium", + })} +
+
+
+
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: + config?.ui.time_format == "24hour" ? "%H:20" : "%I:20%P", + time_style: "medium", + date_style: "medium", + })} +
+
+
+
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: + config?.ui.time_format == "24hour" ? "%H:25" : "%I:25%P", + time_style: "medium", + date_style: "medium", + })} +
+
+
+
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: + config?.ui.time_format == "24hour" ? "%H:30" : "%I:30%P", + time_style: "medium", + date_style: "medium", + })} +
+
+
+
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: + config?.ui.time_format == "24hour" ? "%H:35" : "%I:35%P", + time_style: "medium", + date_style: "medium", + })} +
+
+
+
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: + config?.ui.time_format == "24hour" ? "%H:40" : "%I:40%P", + time_style: "medium", + date_style: "medium", + })} +
+
+
+
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: + config?.ui.time_format == "24hour" ? "%H:45" : "%I:45%P", + time_style: "medium", + date_style: "medium", + })} +
+
+
+
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: + config?.ui.time_format == "24hour" ? "%H:50" : "%I:50%P", + time_style: "medium", + date_style: "medium", + })} +
+
+
+
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: + config?.ui.time_format == "24hour" ? "%H:55" : "%I:55%P", + time_style: "medium", + date_style: "medium", + })} +
+
+
+ )} +
+ {formatUnixTimestampToDateTime(startTime, { + strftime_fmt: "%a %d %B", + time_style: "medium", + date_style: "medium", + })} +
+
+ ); +} + +function getHourBlocks() { + const arr = []; + + for (let x = 0; x <= 59; x++) { + arr.push(x); + } + + return arr; +} diff --git a/web/src/views/history/DesktopTimelineView.tsx b/web/src/views/history/DesktopTimelineView.tsx index e210aef0f..e2ca1674a 100644 --- a/web/src/views/history/DesktopTimelineView.tsx +++ b/web/src/views/history/DesktopTimelineView.tsx @@ -12,6 +12,7 @@ import TimelineItemCard from "@/components/card/TimelineItemCard"; import { getTimelineHoursForDay } from "@/utils/historyUtil"; import { GraphDataPoint } from "@/types/graph"; import TimelineGraph from "@/components/graph/TimelineGraph"; +import TimelineBar from "@/components/bar/TimelineBar"; type DesktopTimelineViewProps = { timelineData: CardsData; @@ -361,76 +362,83 @@ export default function DesktopTimelineView({
- { - if (!timeline.relevantPreview) { - return; - } + {isSelected ? ( +
+ { + if (!timeline.relevantPreview) { + playerRef.current?.pause(); + return; + } - const seekTimestamp = data.time.getTime() / 1000; - const seekTime = - seekTimestamp - timeline.relevantPreview.start; - setPlayerTime(seekTimestamp - timeline.range.start); - setTimeToSeek(Math.round(seekTime)); - }} - timechangedHandler={(data) => { - setScrubbing(false); - const playbackTime = - data.time.getTime() / 1000 - timeline.range.start; - playerRef.current?.currentTime(playbackTime); - playerRef.current?.play(); - }} - selectHandler={(data) => { - if (data.items.length > 0) { - const selected = data.items[0]; - onSelectItem( - selectedPlayback.timelineItems.find( - (timeline) => timeline.timestamp == selected - ) - ); - } - }} - doubleClickHandler={() => { - setScrubbing(false); - setSelectedPlayback(timeline); - }} - /> - {isSelected && graphData && ( -
- { + setScrubbing(false); + const playbackTime = + data.time.getTime() / 1000 - timeline.range.start; + playerRef.current?.currentTime(playbackTime); + playerRef.current?.play(); + }} + selectHandler={(data) => { + if (data.items.length > 0) { + const selected = data.items[0]; + onSelectItem( + selectedPlayback.timelineItems.find( + (timeline) => timeline.timestamp == selected + ) + ); + } + }} /> + {isSelected && graphData && ( +
+ +
+ )}
+ ) : ( + { + setScrubbing(false); + setSelectedPlayback(timeline); + }} + /> )}
);