From 62d13024f6d792f88fa6bdb05ed296bb90f75259 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 9 Mar 2024 07:08:06 -0700 Subject: [PATCH] Adjustments and fixes (#10346) * Increase duration of alerts and detections * Add key * Fix cancel button * Fix motion review when switching days * Add reset buttons and make calendar apply immediately * Adjust apis for motion and audio activity * Write review thumbs as webp and reduce size --- frigate/api/review.py | 76 ++++++++++++++++--- frigate/review/maintainer.py | 13 +++- .../components/filter/CameraGroupSelector.tsx | 2 +- .../components/filter/ReviewFilterGroup.tsx | 59 +++++++------- web/src/pages/SubmitPlus.tsx | 2 +- web/src/types/review.ts | 4 +- web/src/views/events/EventView.tsx | 5 +- 7 files changed, 111 insertions(+), 50 deletions(-) diff --git a/frigate/api/review.py b/frigate/api/review.py index 5807f90ef..4170a1a91 100644 --- a/frigate/api/review.py +++ b/frigate/api/review.py @@ -353,8 +353,8 @@ def delete_reviews(ids: str): return make_response(jsonify({"success": True, "message": "Delete reviews"}), 200) -@ReviewBp.route("/review/activity") -def review_activity(): +@ReviewBp.route("/review/activity/motion") +def motion_activity(): """Get motion and audio activity.""" cameras = request.args.get("cameras", "all") before = request.args.get("before", type=float, default=datetime.now().timestamp()) @@ -374,6 +374,68 @@ def review_activity(): Recordings.duration, Recordings.objects, Recordings.motion, + ) + .where(reduce(operator.and_, clauses)) + .order_by(Recordings.start_time.asc()) + .iterator() + ) + + # format is: { timestamp: segment_start_ts, motion: [0-100], audio: [0 - -100] } + # periods where active objects / audio was detected will cause motion to be scaled down + data: list[dict[str, float]] = [] + + for rec in all_recordings: + data.append( + { + "start_time": rec.start_time, + "motion": rec.motion if rec.objects == 0 else 0, + } + ) + + # get scale in seconds + scale = request.args.get("scale", type=int, default=30) + + # resample data using pandas to get activity on scaled basis + df = pd.DataFrame(data, columns=["start_time", "motion"]) + + # set date as datetime index + df["start_time"] = pd.to_datetime(df["start_time"], unit="s") + df.set_index(["start_time"], inplace=True) + + # normalize data + df = df.resample(f"{scale}S").sum().fillna(0.0) + df["motion"] = ( + (df["motion"] - df["motion"].min()) + / (df["motion"].max() - df["motion"].min()) + * 100 + ) + + # change types for output + df.index = df.index.astype(int) // (10**9) + normalized = df.reset_index().to_dict("records") + return jsonify(normalized) + + +@ReviewBp.route("/review/activity/audio") +def audio_activity(): + """Get motion and audio activity.""" + cameras = request.args.get("cameras", "all") + before = request.args.get("before", type=float, default=datetime.now().timestamp()) + after = request.args.get( + "after", type=float, default=(datetime.now() - timedelta(hours=1)).timestamp() + ) + + clauses = [(Recordings.start_time > after) & (Recordings.end_time < before)] + + if cameras != "all": + camera_list = cameras.split(",") + clauses.append((Recordings.camera << camera_list)) + + all_recordings: list[Recordings] = ( + Recordings.select( + Recordings.start_time, + Recordings.duration, + Recordings.objects, Recordings.dBFS, ) .where(reduce(operator.and_, clauses)) @@ -382,14 +444,13 @@ def review_activity(): ) # format is: { timestamp: segment_start_ts, motion: [0-100], audio: [0 - -100] } - # periods where active objects / audio was detected will cause motion / audio to be scaled down + # periods where active objects / audio was detected will cause audio to be scaled down data: list[dict[str, float]] = [] for rec in all_recordings: data.append( { "start_time": rec.start_time, - "motion": rec.motion if rec.objects == 0 else 0, "audio": rec.dBFS if rec.objects == 0 else 0, } ) @@ -398,7 +459,7 @@ def review_activity(): scale = request.args.get("scale", type=int, default=30) # resample data using pandas to get activity on scaled basis - df = pd.DataFrame(data, columns=["start_time", "motion", "audio"]) + df = pd.DataFrame(data, columns=["start_time", "audio"]) # set date as datetime index df["start_time"] = pd.to_datetime(df["start_time"], unit="s") @@ -406,11 +467,6 @@ def review_activity(): # normalize data df = df.resample(f"{scale}S").mean().fillna(0.0) - df["motion"] = ( - (df["motion"] - df["motion"].min()) - / (df["motion"].max() - df["motion"].min()) - * 100 - ) df["audio"] = ( (df["audio"] - df["audio"].max()) / (df["audio"].min() - df["audio"].max()) diff --git a/frigate/review/maintainer.py b/frigate/review/maintainer.py index 9e0a38210..8da44708f 100644 --- a/frigate/review/maintainer.py +++ b/frigate/review/maintainer.py @@ -28,6 +28,10 @@ logger = logging.getLogger(__name__) THUMB_HEIGHT = 180 THUMB_WIDTH = 320 +THRESHOLD_ALERT_ACTIVITY = 120 +THRESHOLD_DETECTION_ACTIVITY = 30 +THRESHOLD_MOTION_ACTIVITY = 30 + class SeverityEnum(str, Enum): alert = "alert" @@ -100,7 +104,7 @@ class PendingReviewSegment: path = os.path.join(CLIPS_DIR, f"thumb-{self.camera}-{self.id}.jpg") if self.frame is not None: - cv2.imwrite(path, self.frame) + cv2.imwrite(path, self.frame, [int(cv2.IMWRITE_WEBP_QUALITY), 60]) return { ReviewSegment.id: self.id, @@ -195,15 +199,16 @@ class ReviewSegmentMaintainer(threading.Thread): if len(object["current_zones"]) > 0: segment.zones.update(object["current_zones"]) elif ( - segment.severity == SeverityEnum.signification_motion and len(motion) >= 20 + segment.severity == SeverityEnum.signification_motion + and len(motion) >= THRESHOLD_MOTION_ACTIVITY ): segment.last_update = frame_time else: if segment.severity == SeverityEnum.alert and frame_time > ( - segment.last_update + 60 + segment.last_update + THRESHOLD_ALERT_ACTIVITY ): self.end_segment(segment) - elif frame_time > (segment.last_update + 10): + elif frame_time > (segment.last_update + THRESHOLD_DETECTION_ACTIVITY): self.end_segment(segment) def check_if_new_segment( diff --git a/web/src/components/filter/CameraGroupSelector.tsx b/web/src/components/filter/CameraGroupSelector.tsx index 61414281a..b36406e4d 100644 --- a/web/src/components/filter/CameraGroupSelector.tsx +++ b/web/src/components/filter/CameraGroupSelector.tsx @@ -222,7 +222,7 @@ function NewGroupDialog({ open, setOpen, currentGroups }: NewGroupDialogProps) { Camera Groups {currentGroups.map((group) => ( -
+
{group[0]}
+
); @@ -271,8 +280,6 @@ function CalendarFilterButton({ day, updateSelectedDay, }: CalendarFilterButtonProps) { - const [open, setOpen] = useState(false); - const [selectedDay, setSelectedDay] = useState(day); const disabledDates = useMemo(() => { const tomorrow = new Date(); tomorrow.setHours(tomorrow.getHours() + 24, -1, 0, 0); @@ -298,22 +305,21 @@ function CalendarFilterButton({ { - setSelectedDay(day); + updateSelectedDay(day); }} />
@@ -321,16 +327,7 @@ function CalendarFilterButton({ if (isMobile) { return ( - { - if (!open) { - setSelectedDay(day); - } - - setOpen(open); - }} - > + {trigger} {content} @@ -338,16 +335,7 @@ function CalendarFilterButton({ } return ( - { - if (!open) { - setSelectedDay(day); - } - - setOpen(open); - }} - > + {trigger} {content} @@ -433,7 +421,7 @@ function GeneralFilterButton({ ))}
-
+
+
); diff --git a/web/src/pages/SubmitPlus.tsx b/web/src/pages/SubmitPlus.tsx index 1fc76597d..f0c206846 100644 --- a/web/src/pages/SubmitPlus.tsx +++ b/web/src/pages/SubmitPlus.tsx @@ -107,7 +107,7 @@ export default function SubmitPlus() { alt={`${upload?.label}`} /> - +