mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-14 07:05:24 +03:00
Migrate to deciding recording retention based on review items
This commit is contained in:
parent
c06cc79bad
commit
a19c1509e6
@ -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,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user