diff --git a/frigate/record/cleanup.py b/frigate/record/cleanup.py index c2c7d32e7..86f1e63e1 100644 --- a/frigate/record/cleanup.py +++ b/frigate/record/cleanup.py @@ -9,7 +9,7 @@ from pathlib import Path from frigate.config import CameraConfig, FrigateConfig, RetainModeEnum from frigate.const import CACHE_DIR, RECORD_DIR -from frigate.models import Event, Previews, Recordings +from frigate.models import Event, Previews, Recordings, ReviewSegment from frigate.record.util import remove_empty_directories, sync_recordings from frigate.util.builtin import clear_and_unlink, get_tomorrow_at_time @@ -174,6 +174,65 @@ class RecordingCleanup(threading.Thread): Previews.id << deleted_previews_list[i : i + max_deletes] ).execute() + review_segments: list[ReviewSegment] = ( + ReviewSegment.select( + ReviewSegment.id, + ReviewSegment.start_time, + ReviewSegment.end_time, + ReviewSegment.thumb_path, + ) + .where( + ReviewSegment.camera == config.name, + ReviewSegment.end_time < expire_date, + ) + .order_by(ReviewSegment.start_time) + .namedtuples() + .iterator() + ) + + # expire review segments + recording_start = 0 + deleted_segments = set() + for segment in review_segments: + keep = False + # look for a reason to keep this segment + for idx in range(recording_start, len(kept_recordings)): + start_time, end_time = kept_recordings[idx] + + # if the recording starts in the future, stop checking recordings + # and let this segment expire + if start_time > segment.end_time: + keep = False + break + + # if the recording ends after the segment starts, keep it + # and stop looking at recordings + if end_time >= segment.start_time: + keep = True + break + + # if the recording ends before this segment starts, skip + # this recording and check the next recording for an overlap. + # since the kept recordings and segments are sorted, we can skip recordings + # that end before the current segment started + if end_time < segment.start_time: + recording_start = idx + + # Delete segments without any relevant recordings + if not keep: + Path(segment.thumb_path).unlink(missing_ok=True) + deleted_segments.add(segment.id) + + # expire segments + logger.debug(f"Expiring {len(deleted_segments)} segments") + # delete up to 100,000 at a time + max_deletes = 100000 + deleted_segments_list = list(deleted_segments) + for i in range(0, len(deleted_segments_list), max_deletes): + ReviewSegment.delete().where( + ReviewSegment.id << deleted_segments_list[i : i + max_deletes] + ).execute() + def expire_recordings(self) -> None: """Delete recordings based on retention config.""" logger.debug("Start expire recordings.") diff --git a/frigate/review/maintainer.py b/frigate/review/maintainer.py index 8d31a9d3b..88b41001e 100644 --- a/frigate/review/maintainer.py +++ b/frigate/review/maintainer.py @@ -185,7 +185,7 @@ class ReviewSegmentMaintainer(threading.Thread): camera_name = updated_topic.rpartition("/")[-1] self.config.cameras[camera_name].record = updated_record_config - (topic, data) = self.detection_subscriber.get_data() + (topic, data) = self.detection_subscriber.get_data(timeout=1) if not topic: continue