mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-06-27 01:17:22 +02:00
Bug Fixes (#15598)
* Catch onvif command error * fix review item pre and post capture * Include severity in query
This commit is contained in:
parent
b149828c9f
commit
4af752028f
@ -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
|
||||||
|
@ -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():
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user