Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ python-debouncer==0.1.5
python-slugify==8.0.4
PyGObject==3.42.2 # Newer versions does not compile for the Jetson Nano
PyYAML==6.0.1
requests==2.31.0
requests==2.32.0
scikit-learn==1.2.2
scipy==1.13.0
supervision==0.21.0
Expand All @@ -34,5 +34,5 @@ setproctitle==1.3.3
sqlalchemy==2.0.30
watchdog==4.0.0
python-telegram-bot==21.4
onvif-zeep==0.2.12
onvif-python==0.1.9
ultralytics==8.3.146; platform_machine == "x86_64" or platform_machine == "aarch64"
4 changes: 2 additions & 2 deletions viseron/components/discord/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ async def _send_notifications(self, event_data: Event[EventRecorderData]) -> Non
# If file is smaller than the limit, send the complete video
if file_size <= max_size_bytes:
LOGGER.info(
f"Sending complete video file ({file_size/1024/1024:.1f}MB)."
f"Sending complete video file ({file_size / 1024 / 1024:.1f}MB)."
)
self._send_discord_file(
clip_path,
Expand All @@ -263,7 +263,7 @@ async def _send_notifications(self, event_data: Event[EventRecorderData]) -> Non
else:
# Video is too large, send the first max_size_bytes
LOGGER.info(
f"Video too large ({file_size/1024/1024:.1f}MB), "
f"Video too large ({file_size / 1024 / 1024:.1f}MB), "
f"sending first {max_size_mb}MB."
)
# Calculate approximate percentage of the video that is being sent
Expand Down
2 changes: 1 addition & 1 deletion viseron/components/gstreamer/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def decoder_element(self):
return [
"nvv4l2decoder",
"enable-max-performance=true",
f"drop-frame-interval={int(self._stream.fps/self._stream.output_fps)}",
f"drop-frame-interval={int(self._stream.fps / self._stream.output_fps)}",
"!",
]

Expand Down
87 changes: 34 additions & 53 deletions viseron/components/ptz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@

import asyncio
import logging
import os
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

import numpy as np
import voluptuous as vol
from onvif import ONVIFCamera, ONVIFError, ONVIFService
from onvif import ONVIFClient, ONVIFOperationException

from viseron.const import EVENT_DOMAIN_REGISTERED, VISERON_SIGNAL_STOPPING
from viseron.domains.camera import AbstractCamera
from viseron.domains.camera.const import DOMAIN as CAMERA_DOMAIN
from viseron.helpers import escape_string, find_file
from viseron.helpers import escape_string
from viseron.helpers.logs import SensitiveInformationFilter
from viseron.helpers.validators import CameraIdentifier
from viseron.watchdog.thread_watchdog import RestartableThread
Expand Down Expand Up @@ -123,14 +122,12 @@ def __init__(self, vis: Viseron, config) -> None:
escape_string(camera[CONFIG_CAMERA_PASSWORD])
)
self._cameras: dict[str, AbstractCamera] = {}
self._onvif_cameras: dict[str, ONVIFCamera] = {}
self._ptz_services: dict[str, ONVIFService] = {}
self._onvif_cameras: dict[str, ONVIFClient] = {}
self._ptz_services: dict[str, Any] = {}
self._ptz_tokens: dict[str, str] = {}
self._stop_patrol_events: dict[str, asyncio.Event] = {}
self._register_lock: asyncio.Lock = asyncio.Lock()
self._stop_event: asyncio.Event = asyncio.Event()
self._devicemgmt_path = self._get_wsdl_dir()
LOGGER.debug(f"Device management file path: ${self._devicemgmt_path}")
vis.data[COMPONENT] = self

def initialize(self):
Expand Down Expand Up @@ -160,29 +157,21 @@ def shutdown(self):
event.set()
self._stop_event.set()

def _get_wsdl_dir(self):
"""Find the wsdl dir."""
wsdl_file = find_file("devicemgmt.wsdl", ["/usr/local/lib", "/home"])
return os.path.dirname(wsdl_file)

def _camera_registered(self, event: Event[AbstractCamera]) -> None:
camera: AbstractCamera = event.data
if camera.identifier in self._config[CONFIG_CAMERAS]:
self._cameras.update({camera.identifier: camera})
config = self._config[CONFIG_CAMERAS][camera.identifier]

onvif_camera = ONVIFCamera(
onvif_camera = ONVIFClient(
camera.config[CONFIG_HOST],
config[CONFIG_CAMERA_PORT],
config[CONFIG_CAMERA_USERNAME],
config[CONFIG_CAMERA_PASSWORD],
wsdl_dir=self._devicemgmt_path,
)
self._onvif_cameras.update({camera.identifier: onvif_camera})
self._ptz_services.update(
{camera.identifier: onvif_camera.create_ptz_service()}
)
media_service = onvif_camera.create_media_service()
self._ptz_services.update({camera.identifier: onvif_camera.ptz()})
media_service = onvif_camera.media()
self._ptz_tokens.update(
{camera.identifier: media_service.GetProfiles()[0].token}
)
Expand Down Expand Up @@ -262,7 +251,7 @@ async def _do_patrol(

# Get and store starting position
status = ptz_service.GetStatus(
{"ProfileToken": self._ptz_tokens.get(camera_identifier)}
ProfileToken=self._ptz_tokens.get(camera_identifier)
)
if status is None:
LOGGER.warning("Cannot determine starting position")
Expand Down Expand Up @@ -472,16 +461,14 @@ def relative_move(self, camera_identifier: str, pan: float, tilt: float) -> bool

try:
ptz_service.RelativeMove(
{
"ProfileToken": self._ptz_tokens.get(camera_identifier),
"Translation": {
"PanTilt": {"x": pan, "y": tilt},
"Zoom": {"x": 0.0},
},
}
ProfileToken=self._ptz_tokens.get(camera_identifier),
Translation={
"PanTilt": {"x": pan, "y": tilt},
"Zoom": {"x": 0.0},
},
)
return True
except ONVIFError as e:
except ONVIFOperationException as e:
LOGGER.warning(f"ONVIF error in RelativeMove (usually harmless): {e}")
return False

Expand All @@ -494,16 +481,14 @@ def zoom(self, camera_identifier: str, zoom: float = 0.1) -> bool:

try:
ptz_service.RelativeMove(
{
"ProfileToken": self._ptz_tokens.get(camera_identifier),
"Translation": {
"PanTilt": {"x": 0.0, "y": 0.0},
"Zoom": {"x": zoom},
},
}
ProfileToken=self._ptz_tokens.get(camera_identifier),
Translation={
"PanTilt": {"x": 0.0, "y": 0.0},
"Zoom": {"x": zoom},
},
)
return True
except ONVIFError as e:
except ONVIFOperationException as e:
# errors occur when the zoom exceeds the camera's limits?, silence them
# can't check, camera does not support zoom
LOGGER.warning(f"ONVIF error in Zoom (usually harmless): {e}")
Expand All @@ -517,15 +502,13 @@ def absolute_move(self, camera_identifier: str, pan: float, tilt: float) -> bool
return False
try:
ptz_service.AbsoluteMove(
{
"ProfileToken": self._ptz_tokens.get(camera_identifier),
"Position": {
"PanTilt": {"x": pan, "y": tilt},
},
}
ProfileToken=self._ptz_tokens.get(camera_identifier),
Position={
"PanTilt": {"x": pan, "y": tilt},
},
)
return True
except ONVIFError as e:
except ONVIFOperationException as e:
LOGGER.warning(f"ONVIF error in AbsoluteMove (usually harmless): {e}")
return False

Expand Down Expand Up @@ -565,17 +548,15 @@ async def continuous_move(
return False
try:
ptz_service.ContinuousMove(
{
"ProfileToken": self._ptz_tokens.get(camera_identifier),
"Velocity": {
"PanTilt": {"x": x_velocity, "y": y_velocity},
"Zoom": {"x": 0.0},
},
}
ProfileToken=self._ptz_tokens.get(camera_identifier),
Velocity={
"PanTilt": {"x": x_velocity, "y": y_velocity},
"Zoom": {"x": 0.0},
},
)
await asyncio.sleep(seconds)
ptz_service.Stop({"ProfileToken": self._ptz_tokens.get(camera_identifier)})
except ONVIFError as e:
except ONVIFOperationException as e:
LOGGER.warning(f"ONVIF error in ContinuousMove (usually harmless): {e}")

def pan_left(self, camera_identifier: str, step_size: float = 0.1) -> bool:
Expand Down Expand Up @@ -618,10 +599,10 @@ def get_position(self, camera_identifier: str) -> tuple[float, float]:
return 0.0, 0.0
try:
status = ptz_service.GetStatus(
{"ProfileToken": self._ptz_tokens.get(camera_identifier)}
ProfileToken=self._ptz_tokens.get(camera_identifier)
)
return status.Position.PanTilt.x, status.Position.PanTilt.y
except ONVIFError as e:
except ONVIFOperationException as e:
LOGGER.warning(f"ONVIF error in GetStatus (usually harmless): {e}")
return -255.0, -255.0

Expand Down
2 changes: 1 addition & 1 deletion viseron/components/webserver/stream_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ async def get(self, camera, mjpeg_stream) -> None:
self.active_streams[(nvr.camera.identifier, mjpeg_stream)] += 1
LOGGER.debug(
f"Stream {mjpeg_stream} already active, number of streams: "
f"{self.active_streams[(nvr.camera.identifier,mjpeg_stream)]}"
f"{self.active_streams[(nvr.camera.identifier, mjpeg_stream)]}"
)
else:
LOGGER.debug(f"Stream {mjpeg_stream} is not active, starting")
Expand Down
Loading