mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Motion review changes (#10667)
* Add outlines in motion only mode * fix playground
This commit is contained in:
parent
258cd5b6d7
commit
51db63e42b
@ -4,7 +4,9 @@ import {
|
|||||||
useMotionActivity,
|
useMotionActivity,
|
||||||
} from "@/api/ws";
|
} from "@/api/ws";
|
||||||
import { CameraConfig } from "@/types/frigateConfig";
|
import { CameraConfig } from "@/types/frigateConfig";
|
||||||
|
import { MotionData, ReviewSegment } from "@/types/review";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { useTimelineUtils } from "./use-timeline-utils";
|
||||||
|
|
||||||
type useCameraActivityReturn = {
|
type useCameraActivityReturn = {
|
||||||
activeTracking: boolean;
|
activeTracking: boolean;
|
||||||
@ -66,3 +68,124 @@ export function useCameraActivity(
|
|||||||
: false,
|
: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useCameraMotionNextTimestamp(
|
||||||
|
timeRangeSegmentEnd: number,
|
||||||
|
segmentDuration: number,
|
||||||
|
motionOnly: boolean,
|
||||||
|
reviewItems: ReviewSegment[],
|
||||||
|
motionData: MotionData[],
|
||||||
|
currentTime: number,
|
||||||
|
) {
|
||||||
|
const { alignStartDateToTimeline } = useTimelineUtils({
|
||||||
|
segmentDuration,
|
||||||
|
});
|
||||||
|
|
||||||
|
const noMotionRanges = useMemo(() => {
|
||||||
|
if (!motionData || !reviewItems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!motionOnly) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ranges = [];
|
||||||
|
let currentSegmentStart = null;
|
||||||
|
let currentSegmentEnd = null;
|
||||||
|
|
||||||
|
// align motion start to timeline start
|
||||||
|
const offset =
|
||||||
|
(motionData[0].start_time -
|
||||||
|
alignStartDateToTimeline(timeRangeSegmentEnd)) %
|
||||||
|
segmentDuration;
|
||||||
|
|
||||||
|
const startIndex =
|
||||||
|
offset > 0 ? Math.floor(offset / (segmentDuration / 15)) : 0;
|
||||||
|
|
||||||
|
for (
|
||||||
|
let i = startIndex;
|
||||||
|
i < motionData.length;
|
||||||
|
i = i + segmentDuration / 15
|
||||||
|
) {
|
||||||
|
const motionStart = motionData[i].start_time;
|
||||||
|
const motionEnd = motionStart + segmentDuration;
|
||||||
|
|
||||||
|
const segmentMotion = motionData
|
||||||
|
.slice(i, i + segmentDuration / 15)
|
||||||
|
.some(({ motion }) => motion !== undefined && motion > 0);
|
||||||
|
const overlappingReviewItems = reviewItems.some(
|
||||||
|
(item) =>
|
||||||
|
(item.start_time >= motionStart && item.start_time < motionEnd) ||
|
||||||
|
(item.end_time > motionStart && item.end_time <= motionEnd) ||
|
||||||
|
(item.start_time <= motionStart && item.end_time >= motionEnd),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!segmentMotion || overlappingReviewItems) {
|
||||||
|
if (currentSegmentStart === null) {
|
||||||
|
currentSegmentStart = motionStart;
|
||||||
|
}
|
||||||
|
currentSegmentEnd = motionEnd;
|
||||||
|
} else {
|
||||||
|
if (currentSegmentStart !== null) {
|
||||||
|
ranges.push([currentSegmentStart, currentSegmentEnd]);
|
||||||
|
currentSegmentStart = null;
|
||||||
|
currentSegmentEnd = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentSegmentStart !== null) {
|
||||||
|
ranges.push([currentSegmentStart, currentSegmentEnd]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges;
|
||||||
|
}, [
|
||||||
|
motionData,
|
||||||
|
reviewItems,
|
||||||
|
motionOnly,
|
||||||
|
alignStartDateToTimeline,
|
||||||
|
segmentDuration,
|
||||||
|
timeRangeSegmentEnd,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const nextTimestamp = useMemo(() => {
|
||||||
|
if (!noMotionRanges) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!motionOnly) {
|
||||||
|
return currentTime + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentRange = 0;
|
||||||
|
let nextTimestamp = currentTime + 0.5;
|
||||||
|
|
||||||
|
while (currentRange < noMotionRanges.length) {
|
||||||
|
const [start, end] = noMotionRanges[currentRange];
|
||||||
|
|
||||||
|
if (start && end) {
|
||||||
|
// If the current time is before the start of the current range
|
||||||
|
if (currentTime < start) {
|
||||||
|
// The next timestamp is either the start of the current range or currentTime + 0.5, whichever is smaller
|
||||||
|
nextTimestamp = Math.min(start, nextTimestamp);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If the current time is within the current range
|
||||||
|
else if (currentTime >= start && currentTime < end) {
|
||||||
|
// The next timestamp is the end of the current range
|
||||||
|
nextTimestamp = end;
|
||||||
|
currentRange++;
|
||||||
|
}
|
||||||
|
// If the current time is past the end of the current range
|
||||||
|
else {
|
||||||
|
currentRange++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextTimestamp;
|
||||||
|
}, [currentTime, noMotionRanges, motionOnly]);
|
||||||
|
|
||||||
|
return nextTimestamp;
|
||||||
|
}
|
||||||
|
@ -235,7 +235,7 @@ function useDraggableElement({
|
|||||||
const elementEarliest = draggableElementEarliestTime
|
const elementEarliest = draggableElementEarliestTime
|
||||||
? timestampToPixels(draggableElementEarliestTime)
|
? timestampToPixels(draggableElementEarliestTime)
|
||||||
: segmentHeight * (timelineDuration / segmentDuration) -
|
: segmentHeight * (timelineDuration / segmentDuration) -
|
||||||
segmentHeight * 3;
|
segmentHeight * 3.5;
|
||||||
|
|
||||||
// top of timeline - default 2 segments added for draggableElement visibility
|
// top of timeline - default 2 segments added for draggableElement visibility
|
||||||
const elementLatest = draggableElementLatestTime
|
const elementLatest = draggableElementLatestTime
|
||||||
|
@ -77,10 +77,12 @@ function generateRandomMotionAudioData(): MotionData[] {
|
|||||||
) {
|
) {
|
||||||
const motion = Math.floor(Math.random() * 101); // Random number between 0 and 100
|
const motion = Math.floor(Math.random() * 101); // Random number between 0 and 100
|
||||||
const audio = Math.random() * -100; // Random negative value between -100 and 0
|
const audio = Math.random() * -100; // Random negative value between -100 and 0
|
||||||
|
const camera = "test_camera";
|
||||||
data.push({
|
data.push({
|
||||||
start_time: startTimestamp,
|
start_time: startTimestamp,
|
||||||
motion,
|
motion,
|
||||||
audio,
|
audio,
|
||||||
|
camera,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,4 +46,5 @@ export type MotionData = {
|
|||||||
start_time: number;
|
start_time: number;
|
||||||
motion?: number;
|
motion?: number;
|
||||||
audio?: number;
|
audio?: number;
|
||||||
|
camera: string;
|
||||||
};
|
};
|
||||||
|
@ -40,6 +40,7 @@ import SummaryTimeline from "@/components/timeline/SummaryTimeline";
|
|||||||
import { RecordingStartingPoint } from "@/types/record";
|
import { RecordingStartingPoint } from "@/types/record";
|
||||||
import VideoControls from "@/components/player/VideoControls";
|
import VideoControls from "@/components/player/VideoControls";
|
||||||
import { TimeRange } from "@/types/timeline";
|
import { TimeRange } from "@/types/timeline";
|
||||||
|
import { useCameraMotionNextTimestamp } from "@/hooks/use-camera-activity";
|
||||||
|
|
||||||
type EventViewProps = {
|
type EventViewProps = {
|
||||||
reviews?: ReviewSegment[];
|
reviews?: ReviewSegment[];
|
||||||
@ -720,86 +721,14 @@ function MotionReview({
|
|||||||
const [playbackRate, setPlaybackRate] = useState(8);
|
const [playbackRate, setPlaybackRate] = useState(8);
|
||||||
const [controlsOpen, setControlsOpen] = useState(false);
|
const [controlsOpen, setControlsOpen] = useState(false);
|
||||||
|
|
||||||
const noMotionRanges = useMemo(() => {
|
const nextTimestamp = useCameraMotionNextTimestamp(
|
||||||
if (!motionData || !reviewItems) {
|
timeRangeSegments.end,
|
||||||
return;
|
segmentDuration,
|
||||||
}
|
motionOnly,
|
||||||
|
reviewItems?.all ?? [],
|
||||||
if (!motionOnly) {
|
motionData ?? [],
|
||||||
return [];
|
currentTime,
|
||||||
}
|
);
|
||||||
|
|
||||||
const ranges = [];
|
|
||||||
let currentSegmentStart = null;
|
|
||||||
let currentSegmentEnd = null;
|
|
||||||
|
|
||||||
for (let i = 0; i < motionData.length; i = i + segmentDuration / 15) {
|
|
||||||
const motionStart = motionData[i].start_time;
|
|
||||||
const motionEnd = motionStart + segmentDuration;
|
|
||||||
|
|
||||||
const segmentMotion = motionData
|
|
||||||
.slice(i, i + segmentDuration / 15)
|
|
||||||
.some(({ motion }) => motion !== undefined && motion > 0);
|
|
||||||
const overlappingReviewItems = reviewItems.all.some(
|
|
||||||
(item) =>
|
|
||||||
(item.start_time >= motionStart && item.start_time < motionEnd) ||
|
|
||||||
(item.end_time > motionStart && item.end_time <= motionEnd) ||
|
|
||||||
(item.start_time <= motionStart && item.end_time >= motionEnd),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!segmentMotion || overlappingReviewItems) {
|
|
||||||
if (currentSegmentStart === null) {
|
|
||||||
currentSegmentStart = motionStart;
|
|
||||||
}
|
|
||||||
currentSegmentEnd = motionEnd;
|
|
||||||
} else {
|
|
||||||
if (currentSegmentStart !== null) {
|
|
||||||
ranges.push([currentSegmentStart, currentSegmentEnd]);
|
|
||||||
currentSegmentStart = null;
|
|
||||||
currentSegmentEnd = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentSegmentStart !== null) {
|
|
||||||
ranges.push([currentSegmentStart, currentSegmentEnd]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ranges;
|
|
||||||
}, [motionData, reviewItems, motionOnly]);
|
|
||||||
|
|
||||||
const nextTimestamp = useMemo(() => {
|
|
||||||
if (!noMotionRanges) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let currentRange = 0;
|
|
||||||
let nextTimestamp = currentTime + 0.5;
|
|
||||||
|
|
||||||
while (currentRange < noMotionRanges.length) {
|
|
||||||
const [start, end] = noMotionRanges[currentRange];
|
|
||||||
|
|
||||||
if (start && end) {
|
|
||||||
// If the current time is before the start of the current range
|
|
||||||
if (currentTime < start) {
|
|
||||||
// The next timestamp is either the start of the current range or currentTime + 0.5, whichever is smaller
|
|
||||||
nextTimestamp = Math.min(start, nextTimestamp);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// If the current time is within the current range
|
|
||||||
else if (currentTime >= start && currentTime < end) {
|
|
||||||
// The next timestamp is the end of the current range
|
|
||||||
nextTimestamp = end;
|
|
||||||
currentRange++;
|
|
||||||
}
|
|
||||||
// If the current time is past the end of the current range
|
|
||||||
else {
|
|
||||||
currentRange++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextTimestamp;
|
|
||||||
}, [currentTime, noMotionRanges]);
|
|
||||||
|
|
||||||
const timeoutIdRef = useRef<NodeJS.Timeout | null>(null);
|
const timeoutIdRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
@ -832,24 +761,42 @@ function MotionReview({
|
|||||||
const getDetectionType = useCallback(
|
const getDetectionType = useCallback(
|
||||||
(cameraName: string) => {
|
(cameraName: string) => {
|
||||||
if (motionOnly) {
|
if (motionOnly) {
|
||||||
return null;
|
const segmentStartTime = alignStartDateToTimeline(currentTime);
|
||||||
}
|
const segmentEndTime = segmentStartTime + segmentDuration;
|
||||||
const segmentStartTime = alignStartDateToTimeline(currentTime);
|
const matchingItem = motionData?.find((item) => {
|
||||||
const segmentEndTime = segmentStartTime + segmentDuration;
|
const cameras = item.camera.split(",").map((camera) => camera.trim());
|
||||||
const matchingItem = reviewItems?.all.find(
|
return (
|
||||||
(item) =>
|
item.start_time >= segmentStartTime &&
|
||||||
((item.start_time >= segmentStartTime &&
|
item.start_time < segmentEndTime &&
|
||||||
item.start_time < segmentEndTime) ||
|
cameras.includes(cameraName)
|
||||||
(item.end_time > segmentStartTime &&
|
);
|
||||||
item.end_time <= segmentEndTime) ||
|
});
|
||||||
(item.start_time <= segmentStartTime &&
|
|
||||||
item.end_time >= segmentEndTime)) &&
|
|
||||||
item.camera === cameraName,
|
|
||||||
);
|
|
||||||
|
|
||||||
return matchingItem ? matchingItem.severity : null;
|
return matchingItem ? "significant_motion" : null;
|
||||||
|
} else {
|
||||||
|
const segmentStartTime = alignStartDateToTimeline(currentTime);
|
||||||
|
const segmentEndTime = segmentStartTime + segmentDuration;
|
||||||
|
const matchingItem = reviewItems?.all.find(
|
||||||
|
(item) =>
|
||||||
|
((item.start_time >= segmentStartTime &&
|
||||||
|
item.start_time < segmentEndTime) ||
|
||||||
|
(item.end_time > segmentStartTime &&
|
||||||
|
item.end_time <= segmentEndTime) ||
|
||||||
|
(item.start_time <= segmentStartTime &&
|
||||||
|
item.end_time >= segmentEndTime)) &&
|
||||||
|
item.camera === cameraName,
|
||||||
|
);
|
||||||
|
|
||||||
|
return matchingItem ? matchingItem.severity : null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[reviewItems, currentTime, motionOnly, alignStartDateToTimeline],
|
[
|
||||||
|
reviewItems,
|
||||||
|
motionData,
|
||||||
|
currentTime,
|
||||||
|
motionOnly,
|
||||||
|
alignStartDateToTimeline,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!relevantPreviews) {
|
if (!relevantPreviews) {
|
||||||
|
Loading…
Reference in New Issue
Block a user