From a19c1509e62f83676b2726fe95f7527ed7203694 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 26 Aug 2024 07:10:21 -0600 Subject: [PATCH] Migrate to deciding recording retention based on review items --- frigate/record/maintainer.py | 52 +++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/frigate/record/maintainer.py b/frigate/record/maintainer.py index 2d12e2c32..cf5dccc93 100644 --- a/frigate/record/maintainer.py +++ b/frigate/record/maintainer.py @@ -28,7 +28,7 @@ from frigate.const import ( MAX_SEGMENTS_IN_CACHE, RECORD_DIR, ) -from frigate.models import Event, Recordings +from frigate.models import Recordings, ReviewSegment from frigate.util.services import get_video_properties logger = logging.getLogger(__name__) @@ -159,25 +159,27 @@ class RecordingMaintainer(threading.Thread): ): self.audio_recordings_info[camera].pop(0) - # get all events with the end time after the start of the oldest cache file + # get all reviews with the end time after the start of the oldest cache file # or with end_time None - events: Event = ( - Event.select( - Event.start_time, - Event.end_time, - Event.data, + reviews: ReviewSegment = ( + ReviewSegment.select( + ReviewSegment.start_time, + ReviewSegment.end_time, + ReviewSegment.data, ) .where( - Event.camera == camera, - (Event.end_time == None) - | (Event.end_time >= recordings[0]["start_time"].timestamp()), - Event.has_clip, + ReviewSegment.camera == camera, + (ReviewSegment.end_time == None) + | ( + ReviewSegment.end_time + >= recordings[0]["start_time"].timestamp() + ), ) - .order_by(Event.start_time) + .order_by(ReviewSegment.start_time) ) tasks.extend( - [self.validate_and_move_segment(camera, events, r) for r in recordings] + [self.validate_and_move_segment(camera, reviews, r) for r in recordings] ) recordings_to_insert: list[Optional[Recordings]] = await asyncio.gather(*tasks) @@ -189,10 +191,11 @@ class RecordingMaintainer(threading.Thread): ) async def validate_and_move_segment( - self, camera: str, events: list[Event], recording: dict[str, any] + self, camera: str, reviews: list[ReviewSegment], recording: dict[str, any] ) -> None: cache_path = recording["cache_path"] start_time = recording["start_time"] + record_config = self.config.cameras[camera].record # Just delete files if recordings are turned off if ( @@ -232,10 +235,10 @@ class RecordingMaintainer(threading.Thread): ): # if the cached segment overlaps with the events: overlaps = False - for event in events: + for review in reviews: # if the event starts in the future, stop checking events # and remove this segment - if event.start_time > end_time.timestamp(): + if review.start_time > end_time.timestamp(): overlaps = False Path(cache_path).unlink(missing_ok=True) self.end_time_cache.pop(cache_path, None) @@ -243,12 +246,16 @@ class RecordingMaintainer(threading.Thread): # if the event is in progress or ends after the recording starts, keep it # and stop looking at events - if event.end_time is None or event.end_time >= start_time.timestamp(): + if review.end_time is None or review.end_time >= start_time.timestamp(): overlaps = True break if overlaps: - record_mode = self.config.cameras[camera].record.events.retain.mode + record_mode = ( + record_config.alerts.retain.mode + if review.severity == "alert" + else record_config.detections.retain.mode + ) # move from cache to recordings immediately return await self.move_segment( camera, @@ -257,12 +264,11 @@ class RecordingMaintainer(threading.Thread): duration, cache_path, record_mode, - event.data["type"] == "api", ) # if it doesn't overlap with an event, go ahead and drop the segment # if it ends more than the configured pre_capture for the camera else: - pre_capture = self.config.cameras[camera].record.events.pre_capture + pre_capture = max(record_config.alerts.pre_capture, record_config.detections.pre_capture) camera_info = self.object_recordings_info[camera] most_recently_processed_frame_time = ( camera_info[-1][0] if len(camera_info) > 0 else 0 @@ -349,12 +355,11 @@ class RecordingMaintainer(threading.Thread): duration: float, cache_path: str, store_mode: RetainModeEnum, - manual_event: bool = False, # if this segment is being moved due to a manual event ) -> Optional[Recordings]: segment_info = self.segment_stats(camera, start_time, end_time) # check if the segment shouldn't be stored - if not manual_event and segment_info.should_discard_segment(store_mode): + if segment_info.should_discard_segment(store_mode): Path(cache_path).unlink(missing_ok=True) self.end_time_cache.pop(cache_path, None) return @@ -427,8 +432,7 @@ class RecordingMaintainer(threading.Thread): Recordings.duration.name: duration, Recordings.motion.name: segment_info.motion_count, # TODO: update this to store list of active objects at some point - Recordings.objects.name: segment_info.active_object_count - + (1 if manual_event else 0), + Recordings.objects.name: segment_info.active_object_count, Recordings.regions.name: segment_info.region_count, Recordings.dBFS.name: segment_info.average_dBFS, Recordings.segment_size.name: segment_size,