mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-23 08:38:22 +03:00
Skip time-based recording expiry in continuous_rollover mode
In rollover mode, RecordingCleanup no longer deletes recordings based on continuous.days / motion.days. Instead, StorageMaintainer handles overflow by deleting oldest recordings when disk fills up. Deleted cameras have their recordings removed immediately rather than waiting for time-based expiry. Review segment expiry still runs normally.
This commit is contained in:
parent
aed3793ce1
commit
c9b208a255
@ -10,7 +10,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from playhouse.sqlite_ext import SqliteExtDatabase
|
from playhouse.sqlite_ext import SqliteExtDatabase
|
||||||
|
|
||||||
from frigate.config import CameraConfig, FrigateConfig, RetainModeEnum
|
from frigate.config import CameraConfig, FrigateConfig, RetainModeEnum, RetainPolicyEnum
|
||||||
from frigate.const import CACHE_DIR, CLIPS_DIR, MAX_WAL_SIZE, RECORD_DIR
|
from frigate.const import CACHE_DIR, CLIPS_DIR, MAX_WAL_SIZE, RECORD_DIR
|
||||||
from frigate.models import Previews, Recordings, ReviewSegment, UserReviewStatus
|
from frigate.models import Previews, Recordings, ReviewSegment, UserReviewStatus
|
||||||
from frigate.util.builtin import clear_and_unlink
|
from frigate.util.builtin import clear_and_unlink
|
||||||
@ -281,27 +281,45 @@ class RecordingCleanup(threading.Thread):
|
|||||||
def expire_recordings(self) -> set[Path]:
|
def expire_recordings(self) -> set[Path]:
|
||||||
"""Delete recordings based on retention config."""
|
"""Delete recordings based on retention config."""
|
||||||
logger.debug("Start expire recordings.")
|
logger.debug("Start expire recordings.")
|
||||||
logger.debug("Start deleted cameras.")
|
|
||||||
|
is_rollover = (
|
||||||
|
self.config.record.retain_policy == RetainPolicyEnum.continuous_rollover
|
||||||
|
)
|
||||||
|
|
||||||
# Handle deleted cameras
|
# Handle deleted cameras
|
||||||
expire_days = max(
|
logger.debug("Start deleted cameras.")
|
||||||
self.config.record.continuous.days, self.config.record.motion.days
|
if is_rollover:
|
||||||
)
|
# In rollover mode, delete recordings from removed cameras immediately
|
||||||
expire_before = (
|
no_camera_recordings: Recordings = (
|
||||||
datetime.datetime.now() - datetime.timedelta(days=expire_days)
|
Recordings.select(
|
||||||
).timestamp()
|
Recordings.id,
|
||||||
no_camera_recordings: Recordings = (
|
Recordings.path,
|
||||||
Recordings.select(
|
)
|
||||||
Recordings.id,
|
.where(
|
||||||
Recordings.path,
|
Recordings.camera.not_in(list(self.config.cameras.keys())),
|
||||||
|
)
|
||||||
|
.namedtuples()
|
||||||
|
.iterator()
|
||||||
)
|
)
|
||||||
.where(
|
else:
|
||||||
Recordings.camera.not_in(list(self.config.cameras.keys())),
|
expire_days = max(
|
||||||
Recordings.end_time < expire_before,
|
self.config.record.continuous.days, self.config.record.motion.days
|
||||||
|
)
|
||||||
|
expire_before = (
|
||||||
|
datetime.datetime.now() - datetime.timedelta(days=expire_days)
|
||||||
|
).timestamp()
|
||||||
|
no_camera_recordings: Recordings = (
|
||||||
|
Recordings.select(
|
||||||
|
Recordings.id,
|
||||||
|
Recordings.path,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
Recordings.camera.not_in(list(self.config.cameras.keys())),
|
||||||
|
Recordings.end_time < expire_before,
|
||||||
|
)
|
||||||
|
.namedtuples()
|
||||||
|
.iterator()
|
||||||
)
|
)
|
||||||
.namedtuples()
|
|
||||||
.iterator()
|
|
||||||
)
|
|
||||||
|
|
||||||
maybe_empty_dirs = set()
|
maybe_empty_dirs = set()
|
||||||
|
|
||||||
@ -313,7 +331,6 @@ class RecordingCleanup(threading.Thread):
|
|||||||
maybe_empty_dirs.add(recording_path.parent)
|
maybe_empty_dirs.add(recording_path.parent)
|
||||||
|
|
||||||
logger.debug(f"Expiring {len(deleted_recordings)} recordings")
|
logger.debug(f"Expiring {len(deleted_recordings)} recordings")
|
||||||
# delete up to 100,000 at a time
|
|
||||||
max_deletes = 100000
|
max_deletes = 100000
|
||||||
deleted_recordings_list = list(deleted_recordings)
|
deleted_recordings_list = list(deleted_recordings)
|
||||||
for i in range(0, len(deleted_recordings_list), max_deletes):
|
for i in range(0, len(deleted_recordings_list), max_deletes):
|
||||||
@ -327,39 +344,41 @@ class RecordingCleanup(threading.Thread):
|
|||||||
logger.debug(f"Start camera: {camera}.")
|
logger.debug(f"Start camera: {camera}.")
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
|
|
||||||
|
# Always expire review segments (alerts/detections) regardless of policy
|
||||||
maybe_empty_dirs |= self.expire_review_segments(config, now)
|
maybe_empty_dirs |= self.expire_review_segments(config, now)
|
||||||
continuous_expire_date = (
|
|
||||||
now - datetime.timedelta(days=config.record.continuous.days)
|
|
||||||
).timestamp()
|
|
||||||
motion_expire_date = (
|
|
||||||
now
|
|
||||||
- datetime.timedelta(
|
|
||||||
days=max(
|
|
||||||
config.record.motion.days, config.record.continuous.days
|
|
||||||
) # can't keep motion for less than continuous
|
|
||||||
)
|
|
||||||
).timestamp()
|
|
||||||
|
|
||||||
# Get all the reviews to check against
|
# Skip continuous/motion time-based expiry in rollover mode
|
||||||
reviews: ReviewSegment = (
|
if not is_rollover:
|
||||||
ReviewSegment.select(
|
continuous_expire_date = (
|
||||||
ReviewSegment.start_time,
|
now - datetime.timedelta(days=config.record.continuous.days)
|
||||||
ReviewSegment.end_time,
|
).timestamp()
|
||||||
ReviewSegment.severity,
|
motion_expire_date = (
|
||||||
)
|
now
|
||||||
.where(
|
- datetime.timedelta(
|
||||||
ReviewSegment.camera == camera,
|
days=max(
|
||||||
# need to ensure segments for all reviews starting
|
config.record.motion.days, config.record.continuous.days
|
||||||
# before the expire date are included
|
)
|
||||||
ReviewSegment.start_time < motion_expire_date,
|
)
|
||||||
)
|
).timestamp()
|
||||||
.order_by(ReviewSegment.start_time)
|
|
||||||
.namedtuples()
|
reviews: ReviewSegment = (
|
||||||
)
|
ReviewSegment.select(
|
||||||
|
ReviewSegment.start_time,
|
||||||
|
ReviewSegment.end_time,
|
||||||
|
ReviewSegment.severity,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
ReviewSegment.camera == camera,
|
||||||
|
ReviewSegment.start_time < motion_expire_date,
|
||||||
|
)
|
||||||
|
.order_by(ReviewSegment.start_time)
|
||||||
|
.namedtuples()
|
||||||
|
)
|
||||||
|
|
||||||
|
maybe_empty_dirs |= self.expire_existing_camera_recordings(
|
||||||
|
continuous_expire_date, motion_expire_date, config, reviews
|
||||||
|
)
|
||||||
|
|
||||||
maybe_empty_dirs |= self.expire_existing_camera_recordings(
|
|
||||||
continuous_expire_date, motion_expire_date, config, reviews
|
|
||||||
)
|
|
||||||
logger.debug(f"End camera: {camera}.")
|
logger.debug(f"End camera: {camera}.")
|
||||||
|
|
||||||
logger.debug("End all cameras.")
|
logger.debug("End all cameras.")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user