From 4af752028fbb83b88e0886a0a99b200b873bd44e Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Thu, 19 Dec 2024 09:46:14 -0600 Subject: [PATCH 1/2] Bug Fixes (#15598) * Catch onvif command error * fix review item pre and post capture * Include severity in query --- frigate/config/camera/record.py | 13 +++++++++ frigate/ptz/onvif.py | 36 ++++++++++++++----------- frigate/record/maintainer.py | 48 +++++++++++++++++++++------------ 3 files changed, 64 insertions(+), 33 deletions(-) diff --git a/frigate/config/camera/record.py b/frigate/config/camera/record.py index dec629b6b..52d11e2a5 100644 --- a/frigate/config/camera/record.py +++ b/frigate/config/camera/record.py @@ -4,6 +4,7 @@ from typing import Optional from pydantic import Field from frigate.const import MAX_PRE_CAPTURE +from frigate.review.types import SeverityEnum from ..base import FrigateBaseModel @@ -101,3 +102,15 @@ class RecordConfig(FrigateBaseModel): self.alerts.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 diff --git a/frigate/ptz/onvif.py b/frigate/ptz/onvif.py index fd3e3c396..f8c7a6bcb 100644 --- a/frigate/ptz/onvif.py +++ b/frigate/ptz/onvif.py @@ -558,22 +558,26 @@ class OnvifController: if not self._init_onvif(camera_name): return - if command == OnvifCommandEnum.init: - # already init - return - elif command == OnvifCommandEnum.stop: - self._stop(camera_name) - elif command == OnvifCommandEnum.preset: - self._move_to_preset(camera_name, param) - elif command == OnvifCommandEnum.move_relative: - _, pan, tilt = param.split("_") - self._move_relative(camera_name, float(pan), float(tilt), 0, 1) - elif ( - command == OnvifCommandEnum.zoom_in or command == OnvifCommandEnum.zoom_out - ): - self._zoom(camera_name, command) - else: - self._move(camera_name, command) + try: + if command == OnvifCommandEnum.init: + # already init + return + elif command == OnvifCommandEnum.stop: + self._stop(camera_name) + elif command == OnvifCommandEnum.preset: + self._move_to_preset(camera_name, param) + elif command == OnvifCommandEnum.move_relative: + _, pan, tilt = param.split("_") + self._move_relative(camera_name, float(pan), float(tilt), 0, 1) + elif ( + command == OnvifCommandEnum.zoom_in + or command == OnvifCommandEnum.zoom_out + ): + self._zoom(camera_name, command) + else: + 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]: if camera_name not in self.cams.keys(): diff --git a/frigate/record/maintainer.py b/frigate/record/maintainer.py index 4f976bbf6..269735670 100644 --- a/frigate/record/maintainer.py +++ b/frigate/record/maintainer.py @@ -29,6 +29,7 @@ from frigate.const import ( RECORD_DIR, ) from frigate.models import Recordings, ReviewSegment +from frigate.review.types import SeverityEnum from frigate.util.services import get_video_properties logger = logging.getLogger(__name__) @@ -194,6 +195,7 @@ class RecordingMaintainer(threading.Thread): ReviewSegment.select( ReviewSegment.start_time, ReviewSegment.end_time, + ReviewSegment.severity, ReviewSegment.data, ) .where( @@ -219,11 +221,15 @@ class RecordingMaintainer(threading.Thread): [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( self, camera: str, reviews: list[ReviewSegment], recording: dict[str, any] ) -> None: - cache_path = recording["cache_path"] - start_time = recording["start_time"] + cache_path: str = recording["cache_path"] + start_time: datetime.datetime = recording["start_time"] record_config = self.config.cameras[camera].record # Just delete files if recordings are turned off @@ -231,8 +237,7 @@ class RecordingMaintainer(threading.Thread): camera not in self.config.cameras or not self.config.cameras[camera].record.enabled ): - Path(cache_path).unlink(missing_ok=True) - self.end_time_cache.pop(cache_path, None) + self.drop_segment(cache_path) return if cache_path in self.end_time_cache: @@ -260,24 +265,34 @@ class RecordingMaintainer(threading.Thread): return # if cached file's start_time is earlier than the retain days for the camera + # meaning continuous recording is not enabled if start_time <= ( datetime.datetime.now().astimezone(datetime.timezone.utc) - 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 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 - if review.start_time > end_time.timestamp(): + if ( + review.start_time - record_config.get_review_pre_capture(severity) + ) > end_time.timestamp(): overlaps = False - Path(cache_path).unlink(missing_ok=True) - self.end_time_cache.pop(cache_path, None) break - # if the event is in progress or ends after the recording starts, keep it - # and stop looking at events - if review.end_time is None or review.end_time >= start_time.timestamp(): + # if the review item is in progress or ends after the recording starts, keep it + # and stop looking at review items + if ( + review.end_time is None + or ( + review.end_time + + record_config.get_review_post_capture(severity) + ) + >= start_time.timestamp() + ): overlaps = True break @@ -296,7 +311,7 @@ class RecordingMaintainer(threading.Thread): cache_path, 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 else: 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 ).astimezone(datetime.timezone.utc) if end_time < retain_cutoff: - Path(cache_path).unlink(missing_ok=True) - self.end_time_cache.pop(cache_path, None) + self.drop_segment(cache_path) # else retain days includes this segment + # meaning continuous recording is enabled else: # assume that empty means the relevant recording info has not been received yet camera_info = self.object_recordings_info[camera] @@ -390,8 +405,7 @@ class RecordingMaintainer(threading.Thread): # check if the segment shouldn't be stored if segment_info.should_discard_segment(store_mode): - Path(cache_path).unlink(missing_ok=True) - self.end_time_cache.pop(cache_path, None) + self.drop_segment(cache_path) return # directory will be in utc due to start_time being in utc From ddfe8f3921fb9b77710408f2a0dbd68db8b8e47b Mon Sep 17 00:00:00 2001 From: Gabriel de Biasi Date: Thu, 19 Dec 2024 16:54:33 -0300 Subject: [PATCH 2/2] Fix #7944: Adds tls_insecure to the onvif configuration (#15603) * Adds tls_insecure to the onvif configuration * reformat using ruff --- docs/docs/configuration/autotracking.md | 3 +++ docs/docs/configuration/reference.md | 3 +++ frigate/config/camera/onvif.py | 1 + frigate/ptz/onvif.py | 7 ++++++- web/src/types/frigateConfig.ts | 1 + 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/docs/configuration/autotracking.md b/docs/docs/configuration/autotracking.md index a8903441e..9545fa7d3 100644 --- a/docs/docs/configuration/autotracking.md +++ b/docs/docs/configuration/autotracking.md @@ -41,6 +41,7 @@ cameras: ... onvif: # Required: host of the camera being connected to. + # NOTE: HTTP is assumed by default; HTTPS is supported if you specify the scheme, ex: "https://0.0.0.0". host: 0.0.0.0 # Optional: ONVIF port for device (default: shown below). port: 8000 @@ -49,6 +50,8 @@ cameras: user: admin # Optional: password for login. password: admin + # Optional: Skip TLS verification from the ONVIF server (default: shown below) + tls_insecure: False # Optional: PTZ camera object autotracking. Keeps a moving object in # the center of the frame by automatically moving the PTZ camera. autotracking: diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index bb7ae49a3..b13b137d2 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -686,6 +686,7 @@ cameras: # to enable PTZ controls. onvif: # Required: host of the camera being connected to. + # NOTE: HTTP is assumed by default; HTTPS is supported if you specify the scheme, ex: "https://0.0.0.0". host: 0.0.0.0 # Optional: ONVIF port for device (default: shown below). port: 8000 @@ -694,6 +695,8 @@ cameras: user: admin # Optional: password for login. password: admin + # Optional: Skip TLS verification from the ONVIF server (default: shown below) + tls_insecure: False # Optional: Ignores time synchronization mismatches between the camera and the server during authentication. # Using NTP on both ends is recommended and this should only be set to True in a "safe" environment due to the security risk it represents. ignore_time_mismatch: False diff --git a/frigate/config/camera/onvif.py b/frigate/config/camera/onvif.py index b7ac23d4e..0c7985454 100644 --- a/frigate/config/camera/onvif.py +++ b/frigate/config/camera/onvif.py @@ -74,6 +74,7 @@ class OnvifConfig(FrigateBaseModel): port: int = Field(default=8000, title="Onvif Port") user: Optional[EnvString] = Field(default=None, title="Onvif Username") password: Optional[EnvString] = Field(default=None, title="Onvif Password") + tls_insecure: bool = Field(default=False, title="Onvif Disable TLS verification") autotracking: PtzAutotrackConfig = Field( default_factory=PtzAutotrackConfig, title="PTZ auto tracking config.", diff --git a/frigate/ptz/onvif.py b/frigate/ptz/onvif.py index f8c7a6bcb..21c973baa 100644 --- a/frigate/ptz/onvif.py +++ b/frigate/ptz/onvif.py @@ -6,6 +6,7 @@ from importlib.util import find_spec from pathlib import Path import numpy +import requests from onvif import ONVIFCamera, ONVIFError from zeep.exceptions import Fault, TransportError from zeep.transports import Transport @@ -48,7 +49,11 @@ class OnvifController: if cam.onvif.host: try: - transport = Transport(timeout=10, operation_timeout=10) + session = requests.Session() + session.verify = not cam.onvif.tls_insecure + transport = Transport( + timeout=10, operation_timeout=10, session=session + ) self.cams[cam_name] = { "onvif": ONVIFCamera( cam.onvif.host, diff --git a/web/src/types/frigateConfig.ts b/web/src/types/frigateConfig.ts index 5c5971fc0..8ed3119dc 100644 --- a/web/src/types/frigateConfig.ts +++ b/web/src/types/frigateConfig.ts @@ -142,6 +142,7 @@ export interface CameraConfig { password: string | null; port: number; user: string | null; + tls_insecure: boolean; }; record: { enabled: boolean;