Migrate to deciding recording retention based on review items

This commit is contained in:
Nicolas Mowen 2024-08-26 07:10:21 -06:00
parent c06cc79bad
commit a19c1509e6

View File

@ -28,7 +28,7 @@ from frigate.const import (
MAX_SEGMENTS_IN_CACHE, MAX_SEGMENTS_IN_CACHE,
RECORD_DIR, RECORD_DIR,
) )
from frigate.models import Event, Recordings from frigate.models import Recordings, ReviewSegment
from frigate.util.services import get_video_properties from frigate.util.services import get_video_properties
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -159,25 +159,27 @@ class RecordingMaintainer(threading.Thread):
): ):
self.audio_recordings_info[camera].pop(0) 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 # or with end_time None
events: Event = ( reviews: ReviewSegment = (
Event.select( ReviewSegment.select(
Event.start_time, ReviewSegment.start_time,
Event.end_time, ReviewSegment.end_time,
Event.data, ReviewSegment.data,
) )
.where( .where(
Event.camera == camera, ReviewSegment.camera == camera,
(Event.end_time == None) (ReviewSegment.end_time == None)
| (Event.end_time >= recordings[0]["start_time"].timestamp()), | (
Event.has_clip, ReviewSegment.end_time
>= recordings[0]["start_time"].timestamp()
),
) )
.order_by(Event.start_time) .order_by(ReviewSegment.start_time)
) )
tasks.extend( 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) recordings_to_insert: list[Optional[Recordings]] = await asyncio.gather(*tasks)
@ -189,10 +191,11 @@ class RecordingMaintainer(threading.Thread):
) )
async def validate_and_move_segment( 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: ) -> None:
cache_path = recording["cache_path"] cache_path = recording["cache_path"]
start_time = recording["start_time"] start_time = recording["start_time"]
record_config = self.config.cameras[camera].record
# Just delete files if recordings are turned off # Just delete files if recordings are turned off
if ( if (
@ -232,10 +235,10 @@ class RecordingMaintainer(threading.Thread):
): ):
# if the cached segment overlaps with the events: # if the cached segment overlaps with the events:
overlaps = False overlaps = False
for event in events: for review in reviews:
# if the event starts in the future, stop checking events # if the event starts in the future, stop checking events
# and remove this segment # and remove this segment
if event.start_time > end_time.timestamp(): if review.start_time > end_time.timestamp():
overlaps = False overlaps = False
Path(cache_path).unlink(missing_ok=True) Path(cache_path).unlink(missing_ok=True)
self.end_time_cache.pop(cache_path, None) 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 # if the event is in progress or ends after the recording starts, keep it
# and stop looking at events # 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 overlaps = True
break break
if overlaps: 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 # move from cache to recordings immediately
return await self.move_segment( return await self.move_segment(
camera, camera,
@ -257,12 +264,11 @@ class RecordingMaintainer(threading.Thread):
duration, duration,
cache_path, cache_path,
record_mode, record_mode,
event.data["type"] == "api",
) )
# if it doesn't overlap with an event, go ahead and drop the segment # 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 # if it ends more than the configured pre_capture for the camera
else: 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] camera_info = self.object_recordings_info[camera]
most_recently_processed_frame_time = ( most_recently_processed_frame_time = (
camera_info[-1][0] if len(camera_info) > 0 else 0 camera_info[-1][0] if len(camera_info) > 0 else 0
@ -349,12 +355,11 @@ class RecordingMaintainer(threading.Thread):
duration: float, duration: float,
cache_path: str, cache_path: str,
store_mode: RetainModeEnum, store_mode: RetainModeEnum,
manual_event: bool = False, # if this segment is being moved due to a manual event
) -> Optional[Recordings]: ) -> Optional[Recordings]:
segment_info = self.segment_stats(camera, start_time, end_time) segment_info = self.segment_stats(camera, start_time, end_time)
# check if the segment shouldn't be stored # 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) Path(cache_path).unlink(missing_ok=True)
self.end_time_cache.pop(cache_path, None) self.end_time_cache.pop(cache_path, None)
return return
@ -427,8 +432,7 @@ class RecordingMaintainer(threading.Thread):
Recordings.duration.name: duration, Recordings.duration.name: duration,
Recordings.motion.name: segment_info.motion_count, Recordings.motion.name: segment_info.motion_count,
# TODO: update this to store list of active objects at some point # TODO: update this to store list of active objects at some point
Recordings.objects.name: segment_info.active_object_count Recordings.objects.name: segment_info.active_object_count,
+ (1 if manual_event else 0),
Recordings.regions.name: segment_info.region_count, Recordings.regions.name: segment_info.region_count,
Recordings.dBFS.name: segment_info.average_dBFS, Recordings.dBFS.name: segment_info.average_dBFS,
Recordings.segment_size.name: segment_size, Recordings.segment_size.name: segment_size,