* Catch onvif command error

* fix review item pre and post capture

* Include severity in query
This commit is contained in:
Nicolas Mowen 2024-12-19 09:46:14 -06:00 committed by GitHub
parent b149828c9f
commit 4af752028f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 33 deletions

View File

@ -4,6 +4,7 @@ from typing import Optional
from pydantic import Field from pydantic import Field
from frigate.const import MAX_PRE_CAPTURE from frigate.const import MAX_PRE_CAPTURE
from frigate.review.types import SeverityEnum
from ..base import FrigateBaseModel from ..base import FrigateBaseModel
@ -101,3 +102,15 @@ class RecordConfig(FrigateBaseModel):
self.alerts.pre_capture, self.alerts.pre_capture,
self.detections.pre_capture, self.detections.pre_capture,
) )
def get_review_pre_capture(self, severity: SeverityEnum) -> int:
if severity == SeverityEnum.alert:
return self.alerts.pre_capture
else:
return self.detections.pre_capture
def get_review_post_capture(self, severity: SeverityEnum) -> int:
if severity == SeverityEnum.alert:
return self.alerts.post_capture
else:
return self.detections.post_capture

View File

@ -558,6 +558,7 @@ class OnvifController:
if not self._init_onvif(camera_name): if not self._init_onvif(camera_name):
return return
try:
if command == OnvifCommandEnum.init: if command == OnvifCommandEnum.init:
# already init # already init
return return
@ -569,11 +570,14 @@ class OnvifController:
_, pan, tilt = param.split("_") _, pan, tilt = param.split("_")
self._move_relative(camera_name, float(pan), float(tilt), 0, 1) self._move_relative(camera_name, float(pan), float(tilt), 0, 1)
elif ( elif (
command == OnvifCommandEnum.zoom_in or command == OnvifCommandEnum.zoom_out command == OnvifCommandEnum.zoom_in
or command == OnvifCommandEnum.zoom_out
): ):
self._zoom(camera_name, command) self._zoom(camera_name, command)
else: else:
self._move(camera_name, command) self._move(camera_name, command)
except ONVIFError as e:
logger.error(f"Unable to handle onvif command: {e}")
def get_camera_info(self, camera_name: str) -> dict[str, any]: def get_camera_info(self, camera_name: str) -> dict[str, any]:
if camera_name not in self.cams.keys(): if camera_name not in self.cams.keys():

View File

@ -29,6 +29,7 @@ from frigate.const import (
RECORD_DIR, RECORD_DIR,
) )
from frigate.models import Recordings, ReviewSegment from frigate.models import Recordings, ReviewSegment
from frigate.review.types import SeverityEnum
from frigate.util.services import get_video_properties from frigate.util.services import get_video_properties
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -194,6 +195,7 @@ class RecordingMaintainer(threading.Thread):
ReviewSegment.select( ReviewSegment.select(
ReviewSegment.start_time, ReviewSegment.start_time,
ReviewSegment.end_time, ReviewSegment.end_time,
ReviewSegment.severity,
ReviewSegment.data, ReviewSegment.data,
) )
.where( .where(
@ -219,11 +221,15 @@ class RecordingMaintainer(threading.Thread):
[r for r in recordings_to_insert if r is not None], [r for r in recordings_to_insert if r is not None],
) )
def drop_segment(self, cache_path: str) -> None:
Path(cache_path).unlink(missing_ok=True)
self.end_time_cache.pop(cache_path, None)
async def validate_and_move_segment( async def validate_and_move_segment(
self, camera: str, reviews: list[ReviewSegment], recording: dict[str, any] self, camera: str, reviews: list[ReviewSegment], recording: dict[str, any]
) -> None: ) -> None:
cache_path = recording["cache_path"] cache_path: str = recording["cache_path"]
start_time = recording["start_time"] start_time: datetime.datetime = recording["start_time"]
record_config = self.config.cameras[camera].record record_config = self.config.cameras[camera].record
# Just delete files if recordings are turned off # Just delete files if recordings are turned off
@ -231,8 +237,7 @@ class RecordingMaintainer(threading.Thread):
camera not in self.config.cameras camera not in self.config.cameras
or not self.config.cameras[camera].record.enabled or not self.config.cameras[camera].record.enabled
): ):
Path(cache_path).unlink(missing_ok=True) self.drop_segment(cache_path)
self.end_time_cache.pop(cache_path, None)
return return
if cache_path in self.end_time_cache: if cache_path in self.end_time_cache:
@ -260,24 +265,34 @@ class RecordingMaintainer(threading.Thread):
return return
# if cached file's start_time is earlier than the retain days for the camera # if cached file's start_time is earlier than the retain days for the camera
# meaning continuous recording is not enabled
if start_time <= ( if start_time <= (
datetime.datetime.now().astimezone(datetime.timezone.utc) datetime.datetime.now().astimezone(datetime.timezone.utc)
- datetime.timedelta(days=self.config.cameras[camera].record.retain.days) - datetime.timedelta(days=self.config.cameras[camera].record.retain.days)
): ):
# if the cached segment overlaps with the events: # if the cached segment overlaps with the review items:
overlaps = False overlaps = False
for review in reviews: for review in reviews:
# if the event starts in the future, stop checking events severity = SeverityEnum[review.severity]
# if the review item starts in the future, stop checking review items
# and remove this segment # and remove this segment
if review.start_time > end_time.timestamp(): if (
review.start_time - record_config.get_review_pre_capture(severity)
) > end_time.timestamp():
overlaps = False overlaps = False
Path(cache_path).unlink(missing_ok=True)
self.end_time_cache.pop(cache_path, None)
break break
# if the event is in progress or ends after the recording starts, keep it # if the review item is in progress or ends after the recording starts, keep it
# and stop looking at events # and stop looking at review items
if review.end_time is None or review.end_time >= start_time.timestamp(): if (
review.end_time is None
or (
review.end_time
+ record_config.get_review_post_capture(severity)
)
>= start_time.timestamp()
):
overlaps = True overlaps = True
break break
@ -296,7 +311,7 @@ class RecordingMaintainer(threading.Thread):
cache_path, cache_path,
record_mode, record_mode,
) )
# if it doesn't overlap with an event, go ahead and drop the segment # if it doesn't overlap with an review item, 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:
camera_info = self.object_recordings_info[camera] camera_info = self.object_recordings_info[camera]
@ -307,9 +322,9 @@ class RecordingMaintainer(threading.Thread):
most_recently_processed_frame_time - record_config.event_pre_capture most_recently_processed_frame_time - record_config.event_pre_capture
).astimezone(datetime.timezone.utc) ).astimezone(datetime.timezone.utc)
if end_time < retain_cutoff: if end_time < retain_cutoff:
Path(cache_path).unlink(missing_ok=True) self.drop_segment(cache_path)
self.end_time_cache.pop(cache_path, None)
# else retain days includes this segment # else retain days includes this segment
# meaning continuous recording is enabled
else: else:
# assume that empty means the relevant recording info has not been received yet # assume that empty means the relevant recording info has not been received yet
camera_info = self.object_recordings_info[camera] camera_info = self.object_recordings_info[camera]
@ -390,8 +405,7 @@ class RecordingMaintainer(threading.Thread):
# check if the segment shouldn't be stored # check if the segment shouldn't be stored
if segment_info.should_discard_segment(store_mode): if segment_info.should_discard_segment(store_mode):
Path(cache_path).unlink(missing_ok=True) self.drop_segment(cache_path)
self.end_time_cache.pop(cache_path, None)
return return
# directory will be in utc due to start_time being in utc # directory will be in utc due to start_time being in utc