diff --git a/frigate/http.py b/frigate/http.py index f1ecb7025..e24938d88 100644 --- a/frigate/http.py +++ b/frigate/http.py @@ -751,28 +751,6 @@ def hourly_timeline_activity(camera_name: str): } ) - # process data to make data counts relative - for hour, data in hours.items(): - motion_values = np.asarray(list(map(lambda m: m["count"], data))) - avg = motion_values.mean() - std = motion_values.std() - - for idx, motion in enumerate(motion_values): - if motion < (avg - (std * 2)): - value = 1 - elif motion < (avg - std): - value = 2 - elif motion < avg: - value = 3 - elif motion < (avg + std): - value = 4 - elif motion < (avg + (std * 2)): - value = 5 - else: - value = 6 - - hours[hour][idx]["count"] = value - return jsonify(hours) diff --git a/web/src/components/graph/TimelineGraph.tsx b/web/src/components/graph/TimelineGraph.tsx index 2525ab4a7..ddffe59ac 100644 --- a/web/src/components/graph/TimelineGraph.tsx +++ b/web/src/components/graph/TimelineGraph.tsx @@ -54,6 +54,8 @@ export default function TimelineGraph({ id, data }: TimelineGraphProps) { labels: { show: false, }, + logarithmic: true, + logBase: 10, }, }} series={data} diff --git a/web/src/pages/UIPlayground.tsx b/web/src/pages/UIPlayground.tsx index 1359f77bf..b1270de99 100644 --- a/web/src/pages/UIPlayground.tsx +++ b/web/src/pages/UIPlayground.tsx @@ -9,6 +9,8 @@ import { Event } from "@/types/event"; import ActivityIndicator from "@/components/ui/activity-indicator"; import { useApiHost } from "@/api"; import TimelineScrubber from "@/components/playground/TimelineScrubber"; +import TimelineGraph from "@/components/graph/TimelineGraph"; +import { GraphDataPoint } from "@/types/graph"; // Color data const colors = [ @@ -74,6 +76,36 @@ function UIPlayground() { "events", { limit: 10, after: recentTimestamp }, ]); + const { data: recordingSegments } = useSWR([ + "front_cam/recordings", + { before: 1704211200, after: 1704207600 }, + ]); + + const graphData = useMemo(() => { + if (!recordingSegments) { + return { motion: [], objects: [] }; + } + + const motion: GraphDataPoint[] = []; + const objects: GraphDataPoint[] = []; + + recordingSegments + .forEach((seg) => { + if (seg.objects > 0) { + objects.push({ + x: new Date((seg.start_time + 5) * 1000), + y: seg.motion, + }); + } else { + motion.push({ + x: new Date((seg.start_time + 5) * 1000), + y: seg.motion, + }); + } + }); + + return { motion, objects }; + }, [recordingSegments]); return ( <> @@ -91,12 +123,28 @@ function UIPlayground() { {config && (
{events && events.length > 0 && ( - <> +
- +
+ +
+
)}
)} diff --git a/web/src/types/history.ts b/web/src/types/history.ts index f54344871..e925de2e1 100644 --- a/web/src/types/history.ts +++ b/web/src/types/history.ts @@ -56,6 +56,12 @@ interface HistoryFilter extends FilterType { detailLevel: "normal" | "extra" | "full"; } +type HistoryTimeline = { + start: number; + end: number; + playbackItems: TimelinePlayback[]; +}; + type TimelinePlayback = { camera: string; range: { start: number; end: number }; diff --git a/web/src/types/record.ts b/web/src/types/record.ts index 733be1d04..614d223a8 100644 --- a/web/src/types/record.ts +++ b/web/src/types/record.ts @@ -18,3 +18,13 @@ type RecordingSegment = { objects: number; segment_size: number; }; + +type RecordingActivity = { + [hour: number]: RecordingSegmentActivity[]; +}; + +type RecordingSegmentActivity = { + date: number; + count: number; + type: "motion" | "objects"; +}; diff --git a/web/src/utils/historyUtil.ts b/web/src/utils/historyUtil.ts index 78bfe77cb..d9c6b88c0 100644 --- a/web/src/utils/historyUtil.ts +++ b/web/src/utils/historyUtil.ts @@ -107,13 +107,14 @@ export function getTimelineHoursForDay( cards: CardsData, allPreviews: Preview[], timestamp: number -): TimelinePlayback[] { +): HistoryTimeline { const now = new Date(); const data: TimelinePlayback[] = []; const startDay = new Date(timestamp * 1000); startDay.setHours(23, 59, 59, 999); const dayEnd = startDay.getTime() / 1000; startDay.setHours(0, 0, 0, 0); + const startTimestamp = startDay.getTime() / 1000; let start = startDay.getTime() / 1000; let end = 0; @@ -134,7 +135,7 @@ export function getTimelineHoursForDay( }); if (dayIdx == undefined) { - return []; + return { start: 0, end: 0, playbackItems: [] }; } const day = cards[dayIdx]; @@ -179,5 +180,5 @@ export function getTimelineHoursForDay( start = startDay.getTime() / 1000; } - return data.reverse(); + return { start: startTimestamp, end, playbackItems: data.reverse() }; } diff --git a/web/src/views/history/DesktopTimelineView.tsx b/web/src/views/history/DesktopTimelineView.tsx index 72fbd9b6d..7eed71d3c 100644 --- a/web/src/views/history/DesktopTimelineView.tsx +++ b/web/src/views/history/DesktopTimelineView.tsx @@ -10,6 +10,8 @@ import useSWR from "swr"; import Player from "video.js/dist/types/player"; import TimelineItemCard from "@/components/card/TimelineItemCard"; import { getTimelineHoursForDay } from "@/utils/historyUtil"; +import { GraphDataPoint } from "@/types/graph"; +import TimelineGraph from "@/components/graph/TimelineGraph"; type DesktopTimelineViewProps = { timelineData: CardsData; @@ -166,6 +168,50 @@ export default function DesktopTimelineView({ [] ); + const { data: activity } = useSWR( + [ + `${initialPlayback.camera}/recording/hourly/activity`, + { + after: timelineStack.start, + before: timelineStack.end, + timezone, + }, + ], + { revalidateOnFocus: false } + ); + const timelineGraphData = useMemo(() => { + if (!activity) { + return {}; + } + + const graphData: { + [hour: string]: { objects: GraphDataPoint[]; motion: GraphDataPoint[] }; + } = {}; + + Object.entries(activity).forEach(([hour, data]) => { + const objects: GraphDataPoint[] = []; + const motion: GraphDataPoint[] = []; + + data.forEach((seg) => { + if (seg.type == "objects") { + objects.push({ + x: new Date(seg.date * 1000), + y: seg.count, + }); + } else { + motion.push({ + x: new Date(seg.date * 1000), + y: seg.count, + }); + } + }); + + graphData[hour] = { objects, motion }; + }); + + return graphData; + }, [activity]); + if (!config) { return ; } @@ -271,14 +317,18 @@ export default function DesktopTimelineView({
- {timelineStack.map((timeline) => { + {timelineStack.playbackItems.map((timeline) => { const isSelected = timeline.range.start == selectedPlayback.range.start; + const graphData = timelineGraphData[timeline.range.start]; return (
setSelectedPlayback(timeline)} > { - setSelectedPlayback(timeline); - }} selectHandler={(data) => { if (data.items.length > 0) { const selected = data.items[0]; @@ -338,6 +385,20 @@ export default function DesktopTimelineView({ } }} /> + {graphData && ( +
+ +
+ )}
); })}