From 2f2cdda1ead3e924aeece61c41908d33e6c8cdd5 Mon Sep 17 00:00:00 2001 From: Stephen Rhodes Date: Tue, 27 Aug 2024 12:12:19 -0400 Subject: [PATCH] integrate liblivemedia --- .gitmodules | 3 + README.md | 81 +- assets/images/alarm.png | Bin 0 -> 59349 bytes assets/images/discover.png | Bin 0 -> 82655 bytes assets/images/general.png | Bin 0 -> 101507 bytes assets/images/proxy.png | Bin 0 -> 68536 bytes assets/images/storage.png | Bin 0 -> 67828 bytes assets/scripts/build_pkgs | 3 + assets/scripts/build_pkgs.bat | 5 + assets/scripts/clean | 50 +- assets/scripts/clean.bat | 63 +- assets/scripts/compile | 11 + assets/scripts/compile.bat | 58 +- libavio | 2 +- liblivemedia | 1 + libonvif/CMakeLists.txt | 2 +- libonvif/README.md | 26 + libonvif/include/onvif_data.h | 1616 ++++++++++--------- libonvif/include/session.h | 272 ++-- libonvif/pyproject.toml | 2 +- libonvif/setup.py | 2 +- libonvif/src/onvif.c | 8 +- libonvif/src/onvif.cpp | 4 +- libonvif/test/onvif-test.cpp | 304 ++-- onvif-gui/gui/__init__.py | 4 +- onvif-gui/gui/components/target.py | 476 +++--- onvif-gui/gui/components/thresholdslider.py | 2 +- onvif-gui/gui/components/warningbar.py | 168 +- onvif-gui/gui/enums.py | 17 + onvif-gui/gui/glwidget.py | 31 +- onvif-gui/gui/main.py | 381 ++--- onvif-gui/gui/manager.py | 562 +++---- onvif-gui/gui/onvif/datastructures.py | 567 +++---- onvif-gui/gui/onvif/systemtab.py | 612 +++---- onvif-gui/gui/panels/__init__.py | 2 +- onvif-gui/gui/panels/audiopanel.py | 2 +- onvif-gui/gui/panels/camerapanel.py | 63 +- onvif-gui/gui/panels/options/__init__.py | 5 + onvif-gui/gui/panels/options/alarm.py | 94 ++ onvif-gui/gui/panels/options/discover.py | 171 ++ onvif-gui/gui/panels/options/general.py | 321 ++++ onvif-gui/gui/panels/options/proxy.py | 129 ++ onvif-gui/gui/panels/options/storage.py | 124 ++ onvif-gui/gui/panels/settingspanel.py | 602 +------ onvif-gui/gui/panels/videopanel.py | 2 +- onvif-gui/gui/player.py | 231 +++ onvif-gui/modules/audio/sample.py | 2 +- onvif-gui/modules/video/motion.py | 2 +- onvif-gui/modules/video/yolox.py | 27 +- onvif-gui/pyproject.toml | 9 +- onvif-gui/setup.py | 2 +- 51 files changed, 3855 insertions(+), 3266 deletions(-) create mode 100644 assets/images/alarm.png create mode 100644 assets/images/discover.png create mode 100644 assets/images/general.png create mode 100644 assets/images/proxy.png create mode 100644 assets/images/storage.png create mode 160000 liblivemedia create mode 100644 onvif-gui/gui/enums.py create mode 100644 onvif-gui/gui/panels/options/__init__.py create mode 100644 onvif-gui/gui/panels/options/alarm.py create mode 100644 onvif-gui/gui/panels/options/discover.py create mode 100644 onvif-gui/gui/panels/options/general.py create mode 100644 onvif-gui/gui/panels/options/proxy.py create mode 100644 onvif-gui/gui/panels/options/storage.py create mode 100644 onvif-gui/gui/player.py diff --git a/.gitmodules b/.gitmodules index b8be6d5..64b19c5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "libavio"] path = libavio url = https://github.com/sr99622/libavio +[submodule "liblivemedia"] + path = liblivemedia + url = https://github.com/sr99622/liblivemedia diff --git a/README.md b/README.md index e95b35b..d816f58 100644 --- a/README.md +++ b/README.md @@ -754,7 +754,9 @@ Right clicking over the file will bring up a context menu that can be used to pe --- - +## General Settings + + ### Common Username and Password @@ -768,37 +770,34 @@ A hardware decoder may be selected for the application. Mulitcore CPUs with more Selecting this check box will cause the application to start in full screen mode. The full screen mode can be cancelled with the Escape key. The F12 key will also toggle full screen mode. -### Auto Discovery - -When selected, this option will cause the application to discover cameras automatically when it starts. This holds true whether the application is using Broadcast Discovery or Cached Addresses. Note that if this option is selected and the Broadcast Discovery Option is also selected, the application will poll the network once per minute to find missing or new cameras. +### Auto TIme Sync -### Auto Start +This selection will send a time sync message to each of the cameras once an hour. The camera time is set to the host computer time without regard for time zone. -When selected in combination with the Auto Discovery check box, cameras shown in the list will start automatically when the application starts. This feature will work with either Discovery Broadcast or Cached Adresses. +### Display Refresh Interval -### Auto TIme Sync +Performance on some lower powered systems may be improved by increasing the display refresh interval. -This selection will send a time sync message to each of the cameras once an hour. The camera time is set to the host computer time without regard for time zone. +### Maximum Input Stream Cache Size -### Pre-Alarm Buffer Size +Adjust the maximum number of frames held in the cache before frames are dropped. This is the same cache referred to by the Video Tab of the Camera Panel. -When a camera is recording, this length of media is prepended to the file so that the moments prior to the alarm are preserved. If always recording, or the file length is limited by the system to 15 minutes, this feature will insure that there is a small overlap between adjacent files. +### Start All Cameras / Close All Streams -### Post-Alarm Lag Time +This button will change appearance depending on whether there are streams playing or not. It can be used to batch control cameras to start or stop as a group. It will start all cameras on the Camera List. It will stop all streams, including files if playing. -In the case where a camera is configured to record during alarms, this length of time must pass after the cessation of the alarm before the file recording is turned off. This helps to prevent excessive file creation. +### Show Logs -### Alarm Sounds +This button will show the logs of the application. Many events and errors encountered will be documented here. The log rolls over at 1 MB. The older logs can be managed using the Archive button on the logs display dialog. Note that on Linux, the archive file selection dialog may be slow to open or may require some mouse movement to visualize. -A few default alarm sounds for selection. A system wide volume setting for the alarm volume can be made with the slider. +### Help -### Display Refresh Interval +Shows this file. -Performance on some lower powered systems may be improved by increasing the display refresh interval. -### Maximum Input Stream Cache Size +## Discover Settings -Adjust the maximum number of frames held in the cache before frames are dropped. This is the same cache referred to by the Video Tab of the Camera Panel. + ### Discovery Options @@ -814,6 +813,18 @@ Adjust the maximum number of frames held in the cache before frames are dropped. It is possible to add a camera manually to the address cache by using the Add Camera button. The IP address and ONVIF port are required to connect. The ONVIF port by default is 80. If successful, the camera will be added silently to the camera list. +### Auto Discovery + +When selected, this option will cause the application to discover cameras automatically when it starts. This holds true whether the application is using Broadcast Discovery or Cached Addresses. Note that if this option is selected and the Broadcast Discovery Option is also selected, the application will poll the network once per minute to find missing or new cameras. + +### Auto Start + +When selected in combination with the Auto Discovery check box, cameras shown in the list will start automatically when the application starts. This feature will work with either Discovery Broadcast or Cached Adresses. + +## Storage Settings + + + ### Disk Usage The application has the ability to manage the disk space used by the recorded media files. This setting is recommended as the files can overwhelm the computer and cause the application to crash. Allocating a directory for the camera recordings is done by assigning a directory using the Archive Dir selection widget. The default setting for the Archive Dir is the user's Video directory. It is advised to change this setting if the host computer employs the user's Video directory for other applications. @@ -830,17 +841,39 @@ The application has the ability to manage the disk space used by the recorded me The spin box can be used to limit the application disk usage in GB. Note that the application is conservative in it's estimate of required file size and the actual space occupied by the media files will be a few GB less than the allocated space. -### Start All Cameras / Close All Streams +## Proxy Settings -This button will change appearance depending on whether there are streams playing or not. It can be used to batch control cameras to start or stop as a group. It will start all cameras on the Camera List. It will stop all streams, including files if playing. + -### Show Logs +## Proxy Type -This button will show the logs of the application. Many events and errors encountered will be documented here. The log rolls over at 1 MB. The older logs can be managed using the Archive button on the logs display dialog. Note that on Linux, the archive file selection dialog may be slow to open or may require some mouse movement to visualize. +* Stand Alone -### Help + Default setting, implements a single instance of the progran that connects to the cameras directly. -Shows this file. +* Server + + The application will host a proxy server and allow other instances of the application configured as clients to connect over the local network to the cameras proxied by the server. The connection string required for the clients will be displayed. + +* Client + + The application will act as a client to the proxy server. Use the connection string displayed by the server in the url box. + +## Alarm Settings + + + +### Pre-Alarm Buffer Size + +When a camera is recording, this length of media is prepended to the file so that the moments prior to the alarm are preserved. If always recording, or the file length is limited by the system to 15 minutes, this feature will insure that there is a small overlap between adjacent files. + +### Post-Alarm Lag Time + +In the case where a camera is configured to record during alarms, this length of time must pass after the cessation of the alarm before the file recording is turned off. This helps to prevent excessive file creation. + +### Alarm Sounds + +A few default alarm sounds for selection. A system wide volume setting for the alarm volume can be made with the slider. ---   diff --git a/assets/images/alarm.png b/assets/images/alarm.png new file mode 100644 index 0000000000000000000000000000000000000000..29ffea588c6c0194fa01867b0371661e54027dfc GIT binary patch literal 59349 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 OesWSul4at?{{IJBSTq6v literal 0 HcmV?d00001 diff --git a/assets/images/discover.png b/assets/images/discover.png new file mode 100644 index 0000000000000000000000000000000000000000..cf7516c8f0be26362822bf98158456c5749428f0 GIT binary patch literal 82655 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 CulSiW*Qnvg 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 literal 0 HcmV?d00001 diff --git a/assets/images/proxy.png b/assets/images/proxy.png new file mode 100644 index 0000000000000000000000000000000000000000..c6cbbbd8dbdbbfa4fd290f1ce86de5886bed0c54 GIT binary patch literal 68536 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 literal 0 HcmV?d00001 diff --git a/assets/images/storage.png b/assets/images/storage.png new file mode 100644 index 0000000000000000000000000000000000000000..3bac8f189bb615bc1f41621cb41e685a42b9b6e4 GIT binary patch literal 67828 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 literal 0 HcmV?d00001 diff --git a/assets/scripts/build_pkgs b/assets/scripts/build_pkgs index fef78b2..a3f5cb4 100755 --- a/assets/scripts/build_pkgs +++ b/assets/scripts/build_pkgs @@ -4,9 +4,12 @@ cd libonvif python -m build cd ../libavio python -m build +cd ../liblivemedia +python -m build cd ../onvif-gui python -m build cd .. for FILE in libonvif/dist/*.whl; do pip install $FILE; done for FILE in libavio/dist/*.whl; do pip install $FILE; done +for FILE in liblivemedia/dist/*.whl; do pip install $FILE; done for FILE in onvif-gui/dist/*.whl; do pip install $FILE; done diff --git a/assets/scripts/build_pkgs.bat b/assets/scripts/build_pkgs.bat index ee77da7..cc2b959 100755 --- a/assets/scripts/build_pkgs.bat +++ b/assets/scripts/build_pkgs.bat @@ -9,9 +9,14 @@ set FFMPEG_INSTALL_DIR=%CD%/ffmpeg set SDL2_INSTALL_DIR=%CD%/sdl python -m build cd .. +cd liblivemedia +set CMAKE_CURRENT_SOURCE_DIR=%CD% +python -m build +cd .. cd onvif-gui python -m build cd .. for /R libonvif\dist %%F in (*.whl) do pip install "%%F" for /R libavio\dist %%F in (*.whl) do pip install "%%F" +for /R liblivemedia\dist %%F in (*.whl) do pip install "%%F" for /R onvif-gui\dist %%F in (*.whl) do pip install "%%F" diff --git a/assets/scripts/clean b/assets/scripts/clean index 4ddead0..a02a879 100755 --- a/assets/scripts/clean +++ b/assets/scripts/clean @@ -2,32 +2,58 @@ find . -type f -name '._*' -delete cd libonvif -FILE=build -if [ -d "$FILE" ]; then +DIR=build +if [ -d "$DIR" ]; then rm -R build fi -FILE=libonvif.egg-info -if [ -d "$FILE" ]; then +DIR=dist +if [ -d "$DIR" ]; then + rm -R dist +fi +DIR=libonvif.egg-info +if [ -d "$DIR" ]; then rm -R libonvif.egg-info fi cd ../libavio -FILE=build -if [ -d "$FILE" ]; then +DIR=build +if [ -d "$DIR" ]; then rm -R build fi -FILE=avio.egg-info -if [ -d "$FILE" ]; then +DIR=dist +if [ -d "$DIR" ]; then + rm -R dist +fi +DIR=avio.egg-info +if [ -d "$DIR" ]; then rm -R avio.egg-info fi +cd ../liblivemedia +DIR=build +if [ -d "$DIR" ]; then + rm -R build +fi +DIR=dist +if [ -d "$DIR" ]; then + rm -R dist +fi +DIR=liblivemedia.egg-info +if [ -d "$DIR" ]; then + rm -R liblivemedia.egg-info +fi + cd ../onvif-gui -FILE=build -if [ -d "$FILE" ]; then +DIR=build +if [ -d "$DIR" ]; then rm -R build fi -FILE=onvif_gui.egg-info -if [ -d "$FILE" ]; then +DIR=dist +if [ -d "$DIR" ]; then + rm -R dist +fi +DIR=onvif_gui.egg-info +if [ -d "$DIR" ]; then rm -R onvif_gui.egg-info fi diff --git a/assets/scripts/clean.bat b/assets/scripts/clean.bat index e0a2576..7d4d5ea 100755 --- a/assets/scripts/clean.bat +++ b/assets/scripts/clean.bat @@ -1,22 +1,41 @@ -cd libonvif -if exist build\ ( - rmdir /s /q build -) -if exist libonvif.egg-info\ ( - rmdir /s /q libonvif.egg-info -) -cd ../libavio -if exist build\ ( - rmdir /s /q build -) -if exist avio.egg-info\ ( - rmdir /s /q avio.egg-info -) -cd ../onvif-gui -if exist build\ ( - rmdir /s /q build -) -if exist onvif_gui.egg-info\ ( - rmdir /s /q onvif_gui.egg-info -) -cd .. +cd libonvif +if exist build\ ( + rmdir /s /q build +) +if exist dist\ ( + rmdir /s /q dist +) +if exist libonvif.egg-info\ ( + rmdir /s /q libonvif.egg-info +) +cd ../libavio +if exist build\ ( + rmdir /s /q build +) +if exist dist\ ( + rmdir /s /q dist +) +if exist avio.egg-info\ ( + rmdir /s /q avio.egg-info +) +cd ../liblivemedia +if exist build\ ( + rmdir /s /q build +) +if exist dist\ ( + rmdir /s /q dist +) +if exist liblivemedia.egg-info\ ( + rmdir /s /q liblivemedia.egg-info +) +cd ../onvif-gui +if exist build\ ( + rmdir /s /q build +) +if exist dist\ ( + rmdir /s /q dist +) +if exist onvif_gui.egg-info\ ( + rmdir /s /q onvif_gui.egg-info +) +cd .. diff --git a/assets/scripts/compile b/assets/scripts/compile index 9678c9d..c89dac8 100755 --- a/assets/scripts/compile +++ b/assets/scripts/compile @@ -23,6 +23,17 @@ if [ -d "$FILE" ]; then fi pip install -v . +cd ../liblivemedia +FILE=build +if [ -d "$FILE" ]; then + rm -R build +fi +FILE=liblivemedia.egg-info +if [ -d "$FILE" ]; then + rm -R liblivemedia.egg-info +fi +pip install -v . + cd ../onvif-gui FILE=build if [ -d "$FILE" ]; then diff --git a/assets/scripts/compile.bat b/assets/scripts/compile.bat index 1e1c0e4..55cc6aa 100755 --- a/assets/scripts/compile.bat +++ b/assets/scripts/compile.bat @@ -1,25 +1,33 @@ -cd libonvif -if exist build\ ( - rmdir /s /q build -) -if exist libonvif.egg-info\ ( - rmdir /s /q libonvif.egg-info -) -pip install -v . -cd ../libavio -if exist build\ ( - rmdir /s /q build -) -if exist avio.egg-info\ ( - rmdir /s /q avio.egg-info -) -pip install -v . -cd ../onvif-gui -if exist build\ ( - rmdir /s /q build -) -if exist onvif_gui.egg-info\ ( - rmdir /s /q onvif_gui.egg-info -) -pip install . -cd .. +cd libonvif +if exist build\ ( + rmdir /s /q build +) +if exist libonvif.egg-info\ ( + rmdir /s /q libonvif.egg-info +) +pip install -v . +cd ../libavio +if exist build\ ( + rmdir /s /q build +) +if exist avio.egg-info\ ( + rmdir /s /q avio.egg-info +) +pip install -v . +cd ../liblivemedia +if exist build\ ( + rmdir /s /q build +) +if exist liblivemedia.egg-info\ ( + rmdir /s /q liblivemedia.egg-info +) +pip install -v . +cd ../onvif-gui +if exist build\ ( + rmdir /s /q build +) +if exist onvif_gui.egg-info\ ( + rmdir /s /q onvif_gui.egg-info +) +pip install . +cd .. diff --git a/libavio b/libavio index dd249be..4aa8f67 160000 --- a/libavio +++ b/libavio @@ -1 +1 @@ -Subproject commit dd249be9bf2600d384ca58b3120eff1145250256 +Subproject commit 4aa8f6705e8911d21e84997666abe080c01ec316 diff --git a/liblivemedia b/liblivemedia new file mode 160000 index 0000000..3bd3a6c --- /dev/null +++ b/liblivemedia @@ -0,0 +1 @@ +Subproject commit 3bd3a6c5e4422b3bdfdc787d1e16d82953b75bed diff --git a/libonvif/CMakeLists.txt b/libonvif/CMakeLists.txt index 7a2a840..0549f0d 100644 --- a/libonvif/CMakeLists.txt +++ b/libonvif/CMakeLists.txt @@ -21,7 +21,7 @@ cmake_minimum_required(VERSION 3.17) -project(libonvif VERSION 3.1.1) +project(libonvif VERSION 3.2.1) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) diff --git a/libonvif/README.md b/libonvif/README.md index 60d8843..9212437 100644 --- a/libonvif/README.md +++ b/libonvif/README.md @@ -18,3 +18,29 @@ Copyright (c) 2020, 2023, 2024 Stephen Rhodes You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +# libxml2 + +Except where otherwise noted in the source code (e.g. the files dict.c and +list.c, which are covered by a similar licence but with different Copyright +notices) all the files are: + + Copyright (C) 1998-2012 Daniel Veillard. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is fur- +nished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT- +NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/libonvif/include/onvif_data.h b/libonvif/include/onvif_data.h index 57c79ea..1670f71 100644 --- a/libonvif/include/onvif_data.h +++ b/libonvif/include/onvif_data.h @@ -1,799 +1,819 @@ -/******************************************************************************* -* onvif_data.h -* -* copyright 2023 Stephen Rhodes -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; either -* version 2.1 of the License, or (at your option) any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -*******************************************************************************/ - -#ifndef ONVIF_DATA_H -#define ONVIF_DATA_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include "onvif.h" - -namespace libonvif -{ - -class Data -{ -public: - std::function filled = nullptr; - std::function getData = nullptr; - std::function discovered = nullptr; - std::function getCredential = nullptr; - std::function setSetting = nullptr; - std::function getSetting = nullptr; - - OnvifData* data; - bool cancelled = false; - std::string alias; - int preset; - int stop_type; - float x, y, z; - bool synchronizeTime = false; - int displayProfile = 0; - - std::vector profiles; - - Data() - { - data = (OnvifData*)calloc(sizeof(OnvifData), 1); - } - - Data(OnvifData* onvif_data) - { - data = onvif_data; - } - - Data(const Data& other) - { - data = (OnvifData*)calloc(sizeof(OnvifData), 1); - copyData(data, other.data); - cancelled = other.cancelled; - alias = other.alias; - preset = other.preset; - stop_type = other.stop_type; - synchronizeTime = other.synchronizeTime; - profiles = other.profiles; - x = other.x; - y = other.y; - z = other.z; - } - - Data(Data&& other) noexcept - { - data = other.data; - other.data = nullptr; - cancelled = other.cancelled; - alias = other.alias; - preset = other.preset; - stop_type = other.stop_type; - synchronizeTime = other.synchronizeTime; - profiles = other.profiles; - x = other.x; - y = other.y; - z = other.z; - } - - Data& operator=(const Data& other) - { - if (!data) data = (OnvifData*)calloc(sizeof(OnvifData), 1); - copyData(data, other.data); - cancelled = other.cancelled; - alias = other.alias; - preset = other.preset; - stop_type = other.stop_type; - synchronizeTime = other.synchronizeTime; - profiles = other.profiles; - x = other.x; - y = other.y; - z = other.z; - return *this; - } - - Data& operator=(Data&& other) noexcept - { - if (data) free(data); - data = other.data; - cancelled = other.cancelled; - alias = other.alias; - other.data = nullptr; - preset = other.preset; - stop_type = other.stop_type; - synchronizeTime = other.synchronizeTime; - profiles = other.profiles; - x = other.x; - y = other.y; - z = other.z; - return *this; - } - - bool operator==(const Data& rhs) - { - if (strcmp(data->xaddrs, rhs.data->xaddrs)) { - return false; - } - else { - return true; - } - } - - bool operator!=(const Data& rhs) - { - if (strcmp(data->xaddrs, rhs.data->xaddrs)) { - return true; - } - else { - return false; - } - } - - bool friend operator==(const Data& lhs, const Data& rhs) - { - if (strcmp(lhs.data->xaddrs, rhs.data->xaddrs)) { - return false; - } - else { - return true; - } - } - - bool friend operator!=(const Data& lhs, const Data& rhs) - { - if (strcmp(lhs.data->xaddrs, rhs.data->xaddrs)) { - return true; - } - else { - return false; - } - } - - ~Data() - { - free(data); - } - - operator OnvifData* () - { - return data; - } - - OnvifData* operator ->() - { - return data; - } - - void startUpdateTime() - { - std::thread thread([&]() { updateTime(); }); - thread.detach(); - } - - void updateTime() - { - std::stringstream str; - if (setSystemDateAndTime(data)) str << data->last_error << " - "; - if (getTimeOffset(data)) str << data->last_error << " - "; - - memset(data->last_error, 0, 1024); - int length = std::min(std::max((int)(str.str().length() - 2), 0), 1024); - strncpy(data->last_error, str.str().c_str(), length); - - for (int i = 0; i < profiles.size(); i++) { - profiles[i].data->dst = data->dst; - profiles[i].data->datetimetype = data->datetimetype; - profiles[i].data->time_offset = data->time_offset; - profiles[i].data->ntp_dhcp = data->ntp_dhcp; - strcpy(profiles[i].data->timezone, data->timezone); - strcpy(profiles[i].data->ntp_type, data->ntp_type); - strcpy(profiles[i].data->ntp_addr, data->ntp_addr); - } - } - - void startStop() - { - std::thread thread([&]() { stop(); }); - thread.detach(); - } - - void stop() - { - moveStop(stop_type, data); - } - - void startMove() - { - std::thread thread([&]() { move(); }); - thread.detach(); - } - - void move() - { - continuousMove(x, y, z, data); - } - - void startSet() - { - std::thread thread([&]() { set(); }); - thread.detach(); - } - - void set() - { - char pos[128] = {0}; - sprintf(pos, "%d", preset); - gotoPreset(pos, data); - } - - void startSetGotoPreset() - { - std::thread thread([&]() { setGotoPreset(); }); - thread.detach(); - } - - void setGotoPreset() - { - char pos[128] = {0}; - sprintf(pos, "%d", preset); - setPreset(pos, data); - if (filled) filled(*this); - } - - void startUpdateVideo() - { - std::thread thread([&]() { updateVideo(); }); - thread.detach(); - } - - void updateVideo() - { - std::stringstream str; - if (setVideoEncoderConfiguration(data)) str << data->last_error << " - "; - if (getVideoEncoderConfigurationOptions(data)) str << data->last_error << " - "; - if (getVideoEncoderConfiguration(data)) str << data->last_error << " - "; - - memset(data->last_error, 0, 1024); - int length = std::min(std::max((int)(str.str().length() - 2), 0), 1024); - strncpy(data->last_error, str.str().c_str(), length); - - syncProfile(indexForProfile(data->profileToken)); - - if (filled) filled(*this); - } - - void startUpdateAudio() - { - std::thread thread([&] () { updateAudio(); }); - thread.detach(); - } - - void updateAudio() - { - std::stringstream str; - if (setAudioEncoderConfiguration(data)) str << data->last_error << " - "; - if (getAudioEncoderConfigurationOptions(data)) str << data->last_error << " - "; - if (getAudioEncoderConfiguration(data)) str << data->last_error << " - "; - - memset(data->last_error, 0, 1024); - int length = std::min(std::max((int)(str.str().length() - 2), 0), 1024); - strncpy(data->last_error, str.str().c_str(), length); - - syncProfile(indexForProfile(data->profileToken)); - - if (filled) filled(*this); - } - - void startUpdateImage() - { - std::thread thread([&]() { updateImage(); }); - thread.detach(); - } - - void updateImage() - { - std::stringstream str; - if (setImagingSettings(data)) str << data->last_error << " - "; - if (getOptions(data)) str << data->last_error << " - "; - if (getImagingSettings(data)) str << data->last_error << " - "; - - memset(data->last_error, 0, 1024); - int length = std::min(std::max((int)(str.str().length() - 2), 0), 1024); - strncpy(data->last_error, str.str().c_str(), length); - - for (int i = 0; i < profiles.size(); i++) { - profiles[i].data->brightness = data->brightness; - profiles[i].data->saturation = data->saturation; - profiles[i].data->contrast = data->contrast; - profiles[i].data->sharpness = data->sharpness; - } - - if (filled) filled(*this); - } - - void startUpdateNetwork() - { - std::thread thread([&]() { updateNetwork(); }); - thread.detach(); - } - - void updateNetwork() - { - std::stringstream str; - if (setNetworkInterfaces(data)) str << data->last_error << " - "; - if (setDNS(data)) str << data->last_error << " - "; - if (setNetworkDefaultGateway(data)) str << data->last_error << " - "; - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - if (getNetworkInterfaces(data)) str << data->last_error << " - "; - if (getNetworkDefaultGateway(data)) str << data->last_error << " - "; - if (getDNS(data)) str << data->last_error << " - "; - - memset(data->last_error, 0, 1024); - int length = std::min(std::max((int)(str.str().length() - 2), 0), 1024); - strncpy(data->last_error, str.str().c_str(), length); - - syncProfile(indexForProfile(data->profileToken)); - - for (int i = 0; i < profiles.size(); i++) { - if (strcmp(profiles[i].data->profileToken, data->profileToken)) { - strcpy(profiles[i].data->networkInterfaceToken, data->networkInterfaceToken); - strcpy(profiles[i].data->networkInterfaceName, data->networkInterfaceName); - strcpy(profiles[i].data->ip_address_buf, data->ip_address_buf); - strcpy(profiles[i].data->default_gateway_buf, data->default_gateway_buf); - strcpy(profiles[i].data->dns_buf, data->dns_buf); - strcpy(profiles[i].data->mask_buf, data->mask_buf); - profiles[i].data->dhcp_enabled = data->dhcp_enabled; - profiles[i].data->prefix_length = data->prefix_length; - } - } - - if (filled) filled(*this); - } - - void startReboot() - { - std::thread thread([&]() { reboot(); }); - thread.detach(); - } - - void reboot() - { - rebootCamera(data); - } - - void startReset() - { - std::thread thread([&]() { reset(); }); - thread.detach(); - } - - void reset() - { - hardReset(data); - } - - void startSetUser() - { - std::thread thread([&]() { setOnvifUser(); }); - thread.detach(); - } - - void setOnvifUser() - { - /* - if (setUser((char*)new_password.c_str(), onvif_data) == 0) - onvif_data.setPassword(new_password.c_str()); - filled(onvif_data); - */ - } - - void startFill(bool arg) - { - synchronizeTime = arg; - std::thread thread([&]() { fill(); }); - thread.detach(); - } - - void fill() - { - for (int i = 0; i < profiles.size(); i++) { - std::stringstream str; - if (synchronizeTime) { - if (setSystemDateAndTime(profiles[i])) str << profiles[i]->last_error << " - "; - if (getTimeOffset(profiles[i])) str << profiles[i]->last_error << " - "; - } - if (getProfile(profiles[i])) str << profiles[i]->last_error << " - "; - if (getNetworkInterfaces(profiles[i])) str << profiles[i]->last_error << " - "; - if (getNetworkDefaultGateway(profiles[i])) str << profiles[i]->last_error << " - "; - if (getDNS(profiles[i])) str << profiles[i]->last_error << " - "; - if (getVideoEncoderConfiguration(profiles[i])) str << profiles[i]->last_error << " - "; - if (getVideoEncoderConfigurationOptions(profiles[i])) str << profiles[i]->last_error << " - "; - if (getAudioEncoderConfiguration(profiles[i])) str << profiles[i]->last_error << " - "; - if (getAudioEncoderConfigurationOptions(profiles[i])) str << profiles[i]->last_error << " - "; - if (getOptions(profiles[i])) str << profiles[i]->last_error << " - "; - if (getImagingSettings(profiles[i])) str << profiles[i]->last_error << " - "; - - memset(profiles[i]->last_error, 0, 1024); - int length = std::min(std::max((int)(str.str().length() - 2), 0), 1024); - strncpy(profiles[i]->last_error, str.str().c_str(), length); - } - - setProfile(displayProfile); - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - if (filled) filled(*this); - } - - void startManualFill() - { - std::thread thread([&]() { manual_fill(); }); - thread.detach(); - } - - void manual_fill() - { - bool first_pass = true; - int count = 0; - while (true) { - *this = getCredential(*this); - if (!cancelled) { - - getCapabilities(data); - if (!getTimeOffset(data)) { - time_t rawtime; - struct tm timeinfo; - time(&rawtime); - #ifdef _WIN32 - localtime_s(&timeinfo, &rawtime); - #else - localtime_r(&rawtime, &timeinfo); - #endif - if (timeinfo.tm_isdst) - setTimeOffset(time_offset() - 3600); - } - - if (getDeviceInformation(data) == 0) { - int index = 0; - while (true) { - Data profile(*this); - getProfileToken(profile, index); - if (profile.profile().length() == 0) - - break; - getStreamUri(profile.data); - profiles.push_back(profile); - index++; - } - setProfile(displayProfile); - getData(*this); - break; - } - } - else { - break; - } - } - } - - std::string uri() { - std::stringstream str; - std::string arg(data->stream_uri); - if (arg.length() > 7) - str << arg.substr(0, 7) << data->username << ":" << data->password << "@" << arg.substr(7); - return str.str(); - } - - std::string username() { return data->username; } const - void setUsername(const std::string& arg) { - memset(data->username, 0, 128); - strncpy(data->username, arg.c_str(), arg.length()); - } - - std::string password() { return data->password; } const - void setPassword(const std::string& arg) { - memset(data->password, 0, 128); - strncpy(data->password, arg.c_str(), arg.length()); - } - - bool isValid() const { return data ? true : false; } - std::string xaddrs() { return data->xaddrs; } const - void setXAddrs(const std::string& arg) { strcpy(data->xaddrs, arg.c_str()); } - std::string device_service() { return data->device_service; } const - void setDeviceService(const std::string& arg) { strcpy(data->device_service, arg.c_str()); } - std::string event_service() { return data->event_service; } const - std::string stream_uri() { return data->stream_uri; } const - std::string serial_number() { return data->serial_number; } const - std::string camera_name() { return data->camera_name; } const - void setCameraName(const std::string& arg) { strcpy(data->camera_name, arg.c_str()); } - void setHost(const std::string& arg) { strcpy(data->host, arg.c_str()); } - std::string last_error() { return data->last_error; } const - std::string profile() { return data->profileToken; } const - void clearLastError() { memset(data->last_error, 0, 1024); } - void setLastError(const std::string& arg) { strcpy(data->last_error, arg.c_str()); } - time_t time_offset() { return data->time_offset; } const - void setTimeOffset(time_t arg) { data->time_offset = arg; } - std::string timezone() { return data->timezone; } const - bool dst() { return data->dst; } - - std::string host() { - extractHost(data->xaddrs, data->host); - return data->host; - } const - - void syncProfile(int index) { - if (index < profiles.size()) - copyData(profiles[index].data, data); - } - - void setProfile(int index) { - if (index < profiles.size()) { - copyData(data, profiles[index].data); - displayProfile = index; - } - } - - void clear(int bug) { - clearData(data); - alias = ""; - } - - int indexForProfile(const std::string& profileToken) { - int result = 0; - for (int i = 0; i < profiles.size(); i++) { - if (profileToken == profiles[i].data->profileToken) { - result = i; - break; - } - } - return result; - } - - //VIDEO - std::string resolutions_buf(int arg) { return data->resolutions_buf[arg]; } - int width() { return data->width; } - void setWidth(int arg) { data->width = arg; } - int height() { return data->height; } - void setHeight(int arg) { data->height = arg; } - int frame_rate_max() { return data->frame_rate_max; } - int frame_rate_min() { return data->frame_rate_min; } - int frame_rate() { return data->frame_rate; } - void setFrameRate(int arg) { data->frame_rate = arg; } - int gov_length_max() { return data->gov_length_max; } - int gov_length_min() { return data->gov_length_min; } - int gov_length() { return data->gov_length; } - void setGovLength(int arg) { data->gov_length = arg; } - int bitrate_max() { return data->bitrate_max; } - int bitrate_min() { return data->bitrate_min; } - int bitrate() { return data->bitrate; } - void setBitrate(int arg) { data->bitrate = arg; } - std::string encoding() { return data->encoding; } - - //IMAGE - int brightness_max() { return data->brightness_max; } - int brightness_min() { return data->brightness_min; } - int brightness() { return data->brightness; } - void setBrightness(int arg) { data->brightness = arg; } - int saturation_max() { return data->saturation_max; } - int saturation_min() { return data->saturation_min; } - int saturation() { return data->saturation; } - void setSaturation(int arg) { data->saturation = arg; } - int contrast_max() { return data->contrast_max; } - int contrast_min() { return data->contrast_min; } - int contrast() { return data->contrast; } - void setContrast(int arg) { data->contrast = arg; } - int sharpness_max() { return data->sharpness_max; } - int sharpness_min() { return data->sharpness_min; } - int sharpness() { return data->sharpness; } - void setSharpness(int arg) { data->sharpness = arg; } - - //NETWORK - bool dhcp_enabled() { return data->dhcp_enabled; } - void setDHCPEnabled(bool arg) { data->dhcp_enabled = arg; } - std::string ip_address_buf() { return data->ip_address_buf; } const - void setIPAddressBuf(const std::string& arg) { - memset(data->ip_address_buf, 0, 128); - strncpy(data->ip_address_buf, arg.c_str(), arg.length()); - } - std::string default_gateway_buf() { return data->default_gateway_buf; } const - void setDefaultGatewayBuf(const std::string& arg) { - memset(data->default_gateway_buf, 0, 128); - strncpy(data->default_gateway_buf, arg.c_str(), arg.length()); - } - std::string dns_buf() { return data->dns_buf; } const - void setDNSBuf(const std::string& arg) { - memset(data->dns_buf, 0, 128); - strncpy(data->dns_buf, arg.c_str(), arg.length()); - } - int prefix_length() { return data->prefix_length; } - void setPrefixLength(int arg) { data->prefix_length = arg; } - std::string mask_buf() { - memset(data->mask_buf, 0, 128); - prefix2mask(data->prefix_length, data->mask_buf); - return data->mask_buf; - } const - void setMaskBuf(const std::string& arg) { - data->prefix_length = mask2prefix((char*)arg.c_str()); - } - - //AUDIO - std::vector audio_encoders() { - std::vector result; - for (int i=0; i<3; i++) { - if (strlen(data->audio_encoders[i])) - result.push_back(data->audio_encoders[i]); - } - return result; - } const - std::vector audio_bitrates(int arg) { - std::vector result; - for (int i=0; i<8; i++) { - if (data->audio_bitrates[arg][i]) - result.push_back(data->audio_bitrates[arg][i]); - } - return result; - } const - std::vector audio_sample_rates(int arg) { - std::vector result; - for (int i=0; i<8; i++) { - if (data->audio_sample_rates[arg][i]) - result.push_back(data->audio_sample_rates[arg][i]); - } - return result; - } const - std::string audio_encoding() { return data->audio_encoding; } const - void setAudioEncoding(const std::string& arg) { - memset(data->audio_encoding, 0, sizeof(data->audio_encoding)); - strncpy(data->audio_encoding, arg.c_str(), arg.length()); - } - std::string audio_name() { return data->audio_name; } const - int audio_bitrate() { return data->audio_bitrate; } - void setAudioBitrate(int arg) { data->audio_bitrate = arg; } - int audio_sample_rate() { return data->audio_sample_rate; } - void setAudioSampleRate(int arg) { data->audio_sample_rate = arg; } - std::string audio_session_timeout() { return data->audio_session_timeout; } const - std::string audio_multicast_type() { return data->audio_multicast_type; } const - std::string audio_multicast_address() { return data->audio_multicast_address; } const - int audio_use_count() { return data->audio_use_count; } - int audio_multicast_port() { return data->audio_multicast_port; } - int audio_multicast_TTL() { return data->audio_multicast_TTL; } - bool audio_multicast_auto_start() { return data->audio_multicast_auto_start; } - - //GUI INTERFACE - - /* - Please note that this class is intended to be self contained within the C++ domain. It will not - behave as expected if the calling python program attempts to extend the functionality of the - class by adding member variables in the python domain. This was done so that the profile could - be copied or filled with data by the C++ class exclusively, removing the need for additional - synchronization code in the python domain. - - The effect of this decision is that GUI persistence for profiles must be implemented in this - C++ class directly. The member variables are added to the OnvifData structure in onvif.h and - the copyData and clearData functions in onvif.c. GUI persistence is handled by passing setSetting - and getSetting from the calling python program for writing variable states to disk. - */ - - - bool getDisableVideo() { - std::stringstream str; - str << serial_number() << "/" << profile() << "/DisableVideo"; - return getSetting(str.str(), "0") == "1"; - } - void setDisableVideo(bool arg) { - data->disable_video = arg; - std::stringstream str; - str << serial_number() << "/" << profile() << "/DisableVideo"; - setSetting(str.str(), arg ? "1" : "0"); - } - bool getAnalyzeVideo() { - std::stringstream str; - str << serial_number() << "/" << profile() << "/AnalyzeVideo"; - return getSetting(str.str(), "0") == "1"; - } - void setAnalyzeVideo(bool arg) { - data->analyze_video = arg; - std::stringstream str; - str << serial_number() << "/" << profile() << "/AnalyzeVideo"; - setSetting(str.str(), arg ? "1" : "0"); - } - bool getDisableAudio() { - std::stringstream str; - str << serial_number() << "/" << profile() << "/DisableAudio"; - return getSetting(str.str(), "0") == "1"; - } - void setDisableAudio(bool arg) { - data->disable_audio = arg; - std::stringstream str; - str << serial_number() << "/" << profile() << "/DisableAudio"; - setSetting(str.str(), arg ? "1" : "0"); - } - bool getAnalyzeAudio() { - std::stringstream str; - str << serial_number() << "/" << profile() << "/AnalyzeAudio"; - return getSetting(str.str(), "0") == "1"; - } - void setAnalyzeAudio(bool arg) { - data->analyze_audio = arg; - std::stringstream str; - str << serial_number() << "/" << profile() << "/AnalyzeAudio"; - setSetting(str.str(), arg ? "1" : "0"); - } - bool getSyncAudio() { - std::stringstream str; - str << serial_number() << "/" << profile() << "/SyncAudio"; - return getSetting(str.str(), "0") == "1"; - } - void setSyncAudio(bool arg) { - data->sync_audio = arg; - std::stringstream str; - str << serial_number() << "/" << profile() << "/SyncAudio"; - setSetting(str.str(), arg ? "1" : "0"); - } - bool getHidden() { - std::stringstream str; - str << serial_number() << "/" << profile() << "/Hidden"; - return getSetting(str.str(), "0") == "1"; - } - void setHidden(bool arg) { - data->hidden = arg; - std::stringstream str; - str << serial_number() << "/" << profile() << "/Hidden"; - setSetting(str.str(), arg ? "1" : "0"); - } - int getDesiredAspect() { - std::stringstream str_key, str_val, ratio; - str_key << serial_number() << "/" << profile() << "/DesiredAspect"; - ratio << ((height() == 0) ? 0 : (int)(100 * width() / height())); - str_val << getSetting(str_key.str(), ratio.str()); - int desired_aspect = 0; - str_val >> desired_aspect; - return desired_aspect; - } - void setDesiredAspect(int arg) { - data->desired_aspect = arg; - std::stringstream str_key, str_val; - str_key << serial_number() << "/" << profile() << "/DesiredAspect"; - str_val << arg; - setSetting(str_key.str(), str_val.str()); - } - int getCacheMax() { - std::stringstream str_key, str_val; - str_key << serial_number() << "/" << profile() << "/CacheMax"; - str_val << getSetting(str_key.str(), "100"); - int result = 100; - str_val >> result; - return result; - } - void setCacheMax(int arg) { - data->cache_max = arg; - std::stringstream str_key, str_val; - str_key << serial_number() << "/" << profile() << "/CacheMax"; - str_val << arg; - setSetting(str_key.str(), str_val.str()); - } - -}; - -} - - +/******************************************************************************* +* onvif_data.h +* +* copyright 2023 Stephen Rhodes +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +*******************************************************************************/ + +#ifndef ONVIF_DATA_H +#define ONVIF_DATA_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "onvif.h" + +namespace libonvif +{ + +class Data +{ +public: + std::function filled = nullptr; + std::function getData = nullptr; + std::function discovered = nullptr; + std::function getCredential = nullptr; + std::function setSetting = nullptr; + std::function getSetting = nullptr; + std::function getProxyURI; + + OnvifData* data; + bool cancelled = false; + std::string alias; + int preset; + int stop_type; + float x, y, z; + bool synchronizeTime = false; + int displayProfile = 0; + + std::vector profiles; + + Data() + { + data = (OnvifData*)calloc(sizeof(OnvifData), 1); + } + + Data(OnvifData* onvif_data) + { + data = onvif_data; + } + + Data(const Data& other) + { + data = (OnvifData*)calloc(sizeof(OnvifData), 1); + copyData(data, other.data); + cancelled = other.cancelled; + alias = other.alias; + preset = other.preset; + stop_type = other.stop_type; + synchronizeTime = other.synchronizeTime; + profiles = other.profiles; + x = other.x; + y = other.y; + z = other.z; + } + + Data(Data&& other) noexcept + { + data = other.data; + other.data = nullptr; + cancelled = other.cancelled; + alias = other.alias; + preset = other.preset; + stop_type = other.stop_type; + synchronizeTime = other.synchronizeTime; + profiles = other.profiles; + x = other.x; + y = other.y; + z = other.z; + } + + Data& operator=(const Data& other) + { + if (!data) data = (OnvifData*)calloc(sizeof(OnvifData), 1); + copyData(data, other.data); + cancelled = other.cancelled; + alias = other.alias; + preset = other.preset; + stop_type = other.stop_type; + synchronizeTime = other.synchronizeTime; + profiles = other.profiles; + x = other.x; + y = other.y; + z = other.z; + return *this; + } + + Data& operator=(Data&& other) noexcept + { + if (data) free(data); + data = other.data; + cancelled = other.cancelled; + alias = other.alias; + other.data = nullptr; + preset = other.preset; + stop_type = other.stop_type; + synchronizeTime = other.synchronizeTime; + profiles = other.profiles; + x = other.x; + y = other.y; + z = other.z; + return *this; + } + + bool operator==(const Data& rhs) + { + if (strcmp(data->xaddrs, rhs.data->xaddrs)) { + return false; + } + else { + return true; + } + } + + bool operator!=(const Data& rhs) + { + if (strcmp(data->xaddrs, rhs.data->xaddrs)) { + return true; + } + else { + return false; + } + } + + bool friend operator==(const Data& lhs, const Data& rhs) + { + if (strcmp(lhs.data->xaddrs, rhs.data->xaddrs)) { + return false; + } + else { + return true; + } + } + + bool friend operator!=(const Data& lhs, const Data& rhs) + { + if (strcmp(lhs.data->xaddrs, rhs.data->xaddrs)) { + return true; + } + else { + return false; + } + } + + ~Data() + { + free(data); + } + + operator OnvifData* () + { + return data; + } + + OnvifData* operator ->() + { + return data; + } + + void startUpdateTime() + { + std::thread thread([&]() { updateTime(); }); + thread.detach(); + } + + void updateTime() + { + std::stringstream str; + if (setSystemDateAndTime(data)) str << data->last_error << " - "; + if (getTimeOffset(data)) str << data->last_error << " - "; + + memset(data->last_error, 0, 1024); + int length = std::min(std::max((int)(str.str().length() - 2), 0), 1024); + strncpy(data->last_error, str.str().c_str(), length); + + for (int i = 0; i < profiles.size(); i++) { + profiles[i].data->dst = data->dst; + profiles[i].data->datetimetype = data->datetimetype; + profiles[i].data->time_offset = data->time_offset; + profiles[i].data->ntp_dhcp = data->ntp_dhcp; + strcpy(profiles[i].data->timezone, data->timezone); + strcpy(profiles[i].data->ntp_type, data->ntp_type); + strcpy(profiles[i].data->ntp_addr, data->ntp_addr); + } + } + + void startStop() + { + std::thread thread([&]() { stop(); }); + thread.detach(); + } + + void stop() + { + moveStop(stop_type, data); + } + + void startMove() + { + std::thread thread([&]() { move(); }); + thread.detach(); + } + + void move() + { + continuousMove(x, y, z, data); + } + + void startSet() + { + std::thread thread([&]() { set(); }); + thread.detach(); + } + + void set() + { + char pos[128] = {0}; + sprintf(pos, "%d", preset); + gotoPreset(pos, data); + } + + void startSetGotoPreset() + { + std::thread thread([&]() { setGotoPreset(); }); + thread.detach(); + } + + void setGotoPreset() + { + char pos[128] = {0}; + sprintf(pos, "%d", preset); + setPreset(pos, data); + if (filled) filled(*this); + } + + void startUpdateVideo() + { + std::thread thread([&]() { updateVideo(); }); + thread.detach(); + } + + void updateVideo() + { + std::stringstream str; + if (setVideoEncoderConfiguration(data)) str << data->last_error << " - "; + if (getVideoEncoderConfigurationOptions(data)) str << data->last_error << " - "; + if (getVideoEncoderConfiguration(data)) str << data->last_error << " - "; + + memset(data->last_error, 0, 1024); + int length = std::min(std::max((int)(str.str().length() - 2), 0), 1024); + strncpy(data->last_error, str.str().c_str(), length); + + syncProfile(indexForProfile(data->profileToken)); + + if (filled) filled(*this); + } + + void startUpdateAudio() + { + std::thread thread([&] () { updateAudio(); }); + thread.detach(); + } + + void updateAudio() + { + std::stringstream str; + if (setAudioEncoderConfiguration(data)) str << data->last_error << " - "; + if (getAudioEncoderConfigurationOptions(data)) str << data->last_error << " - "; + if (getAudioEncoderConfiguration(data)) str << data->last_error << " - "; + + memset(data->last_error, 0, 1024); + int length = std::min(std::max((int)(str.str().length() - 2), 0), 1024); + strncpy(data->last_error, str.str().c_str(), length); + + syncProfile(indexForProfile(data->profileToken)); + + if (filled) filled(*this); + } + + void startUpdateImage() + { + std::thread thread([&]() { updateImage(); }); + thread.detach(); + } + + void updateImage() + { + std::stringstream str; + if (setImagingSettings(data)) str << data->last_error << " - "; + if (getOptions(data)) str << data->last_error << " - "; + if (getImagingSettings(data)) str << data->last_error << " - "; + + memset(data->last_error, 0, 1024); + int length = std::min(std::max((int)(str.str().length() - 2), 0), 1024); + strncpy(data->last_error, str.str().c_str(), length); + + for (int i = 0; i < profiles.size(); i++) { + profiles[i].data->brightness = data->brightness; + profiles[i].data->saturation = data->saturation; + profiles[i].data->contrast = data->contrast; + profiles[i].data->sharpness = data->sharpness; + } + + if (filled) filled(*this); + } + + void startUpdateNetwork() + { + std::thread thread([&]() { updateNetwork(); }); + thread.detach(); + } + + void updateNetwork() + { + std::stringstream str; + if (setNetworkInterfaces(data)) str << data->last_error << " - "; + if (setDNS(data)) str << data->last_error << " - "; + if (setNetworkDefaultGateway(data)) str << data->last_error << " - "; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + if (getNetworkInterfaces(data)) str << data->last_error << " - "; + if (getNetworkDefaultGateway(data)) str << data->last_error << " - "; + if (getDNS(data)) str << data->last_error << " - "; + + memset(data->last_error, 0, 1024); + int length = std::min(std::max((int)(str.str().length() - 2), 0), 1024); + strncpy(data->last_error, str.str().c_str(), length); + + syncProfile(indexForProfile(data->profileToken)); + + for (int i = 0; i < profiles.size(); i++) { + if (strcmp(profiles[i].data->profileToken, data->profileToken)) { + strcpy(profiles[i].data->networkInterfaceToken, data->networkInterfaceToken); + strcpy(profiles[i].data->networkInterfaceName, data->networkInterfaceName); + strcpy(profiles[i].data->ip_address_buf, data->ip_address_buf); + strcpy(profiles[i].data->default_gateway_buf, data->default_gateway_buf); + strcpy(profiles[i].data->dns_buf, data->dns_buf); + strcpy(profiles[i].data->mask_buf, data->mask_buf); + profiles[i].data->dhcp_enabled = data->dhcp_enabled; + profiles[i].data->prefix_length = data->prefix_length; + } + } + + if (filled) filled(*this); + } + + void startReboot() + { + std::thread thread([&]() { reboot(); }); + thread.detach(); + } + + void reboot() + { + rebootCamera(data); + } + + void startReset() + { + std::thread thread([&]() { reset(); }); + thread.detach(); + } + + void reset() + { + hardReset(data); + } + + void startSetUser() + { + std::thread thread([&]() { setOnvifUser(); }); + thread.detach(); + } + + void setOnvifUser() + { + /* + if (setUser((char*)new_password.c_str(), onvif_data) == 0) + onvif_data.setPassword(new_password.c_str()); + filled(onvif_data); + */ + } + + void startFill(bool arg) + { + synchronizeTime = arg; + std::thread thread([&]() { fill(); }); + thread.detach(); + } + + void fill() + { + for (int i = 0; i < profiles.size(); i++) { + std::stringstream str; + if (synchronizeTime) { + if (setSystemDateAndTime(profiles[i])) str << profiles[i]->last_error << " - "; + if (getTimeOffset(profiles[i])) str << profiles[i]->last_error << " - "; + } + if (getProfile(profiles[i])) str << profiles[i]->last_error << " - "; + if (getNetworkInterfaces(profiles[i])) str << profiles[i]->last_error << " - "; + if (getNetworkDefaultGateway(profiles[i])) str << profiles[i]->last_error << " - "; + if (getDNS(profiles[i])) str << profiles[i]->last_error << " - "; + if (getVideoEncoderConfiguration(profiles[i])) str << profiles[i]->last_error << " - "; + if (getVideoEncoderConfigurationOptions(profiles[i])) str << profiles[i]->last_error << " - "; + if (getAudioEncoderConfiguration(profiles[i])) str << profiles[i]->last_error << " - "; + if (getAudioEncoderConfigurationOptions(profiles[i])) str << profiles[i]->last_error << " - "; + if (getOptions(profiles[i])) str << profiles[i]->last_error << " - "; + if (getImagingSettings(profiles[i])) str << profiles[i]->last_error << " - "; + + memset(profiles[i]->last_error, 0, 1024); + int length = std::min(std::max((int)(str.str().length() - 2), 0), 1024); + strncpy(profiles[i]->last_error, str.str().c_str(), length); + } + + setProfile(displayProfile); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + if (filled) filled(*this); + } + + void startManualFill() + { + std::thread thread([&]() { manual_fill(); }); + thread.detach(); + } + + void manual_fill() + { + bool first_pass = true; + int count = 0; + while (true) { + *this = getCredential(*this); + if (!cancelled) { + + getCapabilities(data); + if (!getTimeOffset(data)) { + time_t rawtime; + struct tm timeinfo; + time(&rawtime); + #ifdef _WIN32 + localtime_s(&timeinfo, &rawtime); + #else + localtime_r(&rawtime, &timeinfo); + #endif + if (timeinfo.tm_isdst) + setTimeOffset(time_offset() - 3600); + } + + if (getDeviceInformation(data) == 0) { + int index = 0; + while (true) { + Data profile(*this); + getProfileToken(profile, index); + if (profile.profile().length() == 0) + + break; + getStreamUri(profile.data); + profiles.push_back(profile); + index++; + } + setProfile(displayProfile); + getData(*this); + break; + } + } + else { + break; + } + } + } + + std::string toString() { + std::stringstream str; + str << "camera_name=" << data->camera_name << "\n" + << "stream_uri=" << data->stream_uri << "\n"; + return str.str(); + } + + std::string uri() { + std::stringstream str; + try { + std::string arg(data->stream_uri); + + if (getProxyURI) { + str << getProxyURI(arg); + } + else { + if (arg.length() > 7) + str << arg.substr(0, 7) << data->username << ":" << data->password << "@" << arg.substr(7); + } + } + catch (const std::exception& ex) { + std::cout << "onvif data uri() exception: " << ex.what() << std::endl; + } + + return str.str(); + } + + std::string username() { return data->username; } const + void setUsername(const std::string& arg) { + memset(data->username, 0, 128); + strncpy(data->username, arg.c_str(), arg.length()); + } + + std::string password() { return data->password; } const + void setPassword(const std::string& arg) { + memset(data->password, 0, 128); + strncpy(data->password, arg.c_str(), arg.length()); + } + + bool isValid() const { return data ? true : false; } + std::string xaddrs() { return data->xaddrs; } const + void setXAddrs(const std::string& arg) { strcpy(data->xaddrs, arg.c_str()); } + std::string device_service() { return data->device_service; } const + void setDeviceService(const std::string& arg) { strcpy(data->device_service, arg.c_str()); } + std::string event_service() { return data->event_service; } const + std::string stream_uri() { return data->stream_uri; } const + std::string serial_number() { return data->serial_number; } const + std::string camera_name() { return data->camera_name; } const + void setCameraName(const std::string& arg) { strcpy(data->camera_name, arg.c_str()); } + void setHost(const std::string& arg) { strcpy(data->host, arg.c_str()); } + std::string last_error() { return data->last_error; } const + std::string profile() { return data->profileToken; } const + void clearLastError() { memset(data->last_error, 0, 1024); } + void setLastError(const std::string& arg) { strcpy(data->last_error, arg.c_str()); } + time_t time_offset() { return data->time_offset; } const + void setTimeOffset(time_t arg) { data->time_offset = arg; } + std::string timezone() { return data->timezone; } const + bool dst() { return data->dst; } + + std::string host() { + extractHost(data->xaddrs, data->host); + return data->host; + } const + + void syncProfile(int index) { + if (index < profiles.size()) + copyData(profiles[index].data, data); + } + + void setProfile(int index) { + if (index < profiles.size()) { + copyData(data, profiles[index].data); + displayProfile = index; + } + } + + void clear(int bug) { + clearData(data); + alias = ""; + } + + int indexForProfile(const std::string& profileToken) { + int result = 0; + for (int i = 0; i < profiles.size(); i++) { + if (profileToken == profiles[i].data->profileToken) { + result = i; + break; + } + } + return result; + } + + //VIDEO + std::string resolutions_buf(int arg) { return data->resolutions_buf[arg]; } + int width() { return data->width; } + void setWidth(int arg) { data->width = arg; } + int height() { return data->height; } + void setHeight(int arg) { data->height = arg; } + int frame_rate_max() { return data->frame_rate_max; } + int frame_rate_min() { return data->frame_rate_min; } + int frame_rate() { return data->frame_rate; } + void setFrameRate(int arg) { data->frame_rate = arg; } + int gov_length_max() { return data->gov_length_max; } + int gov_length_min() { return data->gov_length_min; } + int gov_length() { return data->gov_length; } + void setGovLength(int arg) { data->gov_length = arg; } + int bitrate_max() { return data->bitrate_max; } + int bitrate_min() { return data->bitrate_min; } + int bitrate() { return data->bitrate; } + void setBitrate(int arg) { data->bitrate = arg; } + std::string encoding() { return data->encoding; } + + //IMAGE + int brightness_max() { return data->brightness_max; } + int brightness_min() { return data->brightness_min; } + int brightness() { return data->brightness; } + void setBrightness(int arg) { data->brightness = arg; } + int saturation_max() { return data->saturation_max; } + int saturation_min() { return data->saturation_min; } + int saturation() { return data->saturation; } + void setSaturation(int arg) { data->saturation = arg; } + int contrast_max() { return data->contrast_max; } + int contrast_min() { return data->contrast_min; } + int contrast() { return data->contrast; } + void setContrast(int arg) { data->contrast = arg; } + int sharpness_max() { return data->sharpness_max; } + int sharpness_min() { return data->sharpness_min; } + int sharpness() { return data->sharpness; } + void setSharpness(int arg) { data->sharpness = arg; } + + //NETWORK + bool dhcp_enabled() { return data->dhcp_enabled; } + void setDHCPEnabled(bool arg) { data->dhcp_enabled = arg; } + std::string ip_address_buf() { return data->ip_address_buf; } const + void setIPAddressBuf(const std::string& arg) { + memset(data->ip_address_buf, 0, 128); + strncpy(data->ip_address_buf, arg.c_str(), arg.length()); + } + std::string default_gateway_buf() { return data->default_gateway_buf; } const + void setDefaultGatewayBuf(const std::string& arg) { + memset(data->default_gateway_buf, 0, 128); + strncpy(data->default_gateway_buf, arg.c_str(), arg.length()); + } + std::string dns_buf() { return data->dns_buf; } const + void setDNSBuf(const std::string& arg) { + memset(data->dns_buf, 0, 128); + strncpy(data->dns_buf, arg.c_str(), arg.length()); + } + int prefix_length() { return data->prefix_length; } + void setPrefixLength(int arg) { data->prefix_length = arg; } + std::string mask_buf() { + memset(data->mask_buf, 0, 128); + prefix2mask(data->prefix_length, data->mask_buf); + return data->mask_buf; + } const + void setMaskBuf(const std::string& arg) { + data->prefix_length = mask2prefix((char*)arg.c_str()); + } + + //AUDIO + std::vector audio_encoders() { + std::vector result; + for (int i=0; i<3; i++) { + if (strlen(data->audio_encoders[i])) + result.push_back(data->audio_encoders[i]); + } + return result; + } const + std::vector audio_bitrates(int arg) { + std::vector result; + for (int i=0; i<8; i++) { + if (data->audio_bitrates[arg][i]) + result.push_back(data->audio_bitrates[arg][i]); + } + return result; + } const + std::vector audio_sample_rates(int arg) { + std::vector result; + for (int i=0; i<8; i++) { + if (data->audio_sample_rates[arg][i]) + result.push_back(data->audio_sample_rates[arg][i]); + } + return result; + } const + std::string audio_encoding() { return data->audio_encoding; } const + void setAudioEncoding(const std::string& arg) { + memset(data->audio_encoding, 0, sizeof(data->audio_encoding)); + strncpy(data->audio_encoding, arg.c_str(), arg.length()); + } + std::string audio_name() { return data->audio_name; } const + int audio_bitrate() { return data->audio_bitrate; } + void setAudioBitrate(int arg) { data->audio_bitrate = arg; } + int audio_sample_rate() { return data->audio_sample_rate; } + void setAudioSampleRate(int arg) { data->audio_sample_rate = arg; } + std::string audio_session_timeout() { return data->audio_session_timeout; } const + std::string audio_multicast_type() { return data->audio_multicast_type; } const + std::string audio_multicast_address() { return data->audio_multicast_address; } const + int audio_use_count() { return data->audio_use_count; } + int audio_multicast_port() { return data->audio_multicast_port; } + int audio_multicast_TTL() { return data->audio_multicast_TTL; } + bool audio_multicast_auto_start() { return data->audio_multicast_auto_start; } + + //GUI INTERFACE + + /* + Please note that this class is intended to be self contained within the C++ domain. It will not + behave as expected if the calling python program attempts to extend the functionality of the + class by adding member variables in the python domain. This was done so that the profile could + be copied or filled with data by the C++ class exclusively, removing the need for additional + synchronization code in the python domain. + + The effect of this decision is that GUI persistence for profiles must be implemented in this + C++ class directly. The member variables are added to the OnvifData structure in onvif.h and + the copyData and clearData functions in onvif.c. GUI persistence is handled by passing setSetting + and getSetting from the calling python program for writing variable states to disk. + */ + + + bool getDisableVideo() { + std::stringstream str; + str << serial_number() << "/" << profile() << "/DisableVideo"; + return getSetting(str.str(), "0") == "1"; + } + void setDisableVideo(bool arg) { + data->disable_video = arg; + std::stringstream str; + str << serial_number() << "/" << profile() << "/DisableVideo"; + setSetting(str.str(), arg ? "1" : "0"); + } + bool getAnalyzeVideo() { + std::stringstream str; + str << serial_number() << "/" << profile() << "/AnalyzeVideo"; + return getSetting(str.str(), "0") == "1"; + } + void setAnalyzeVideo(bool arg) { + data->analyze_video = arg; + std::stringstream str; + str << serial_number() << "/" << profile() << "/AnalyzeVideo"; + setSetting(str.str(), arg ? "1" : "0"); + } + bool getDisableAudio() { + std::stringstream str; + str << serial_number() << "/" << profile() << "/DisableAudio"; + return getSetting(str.str(), "0") == "1"; + } + void setDisableAudio(bool arg) { + data->disable_audio = arg; + std::stringstream str; + str << serial_number() << "/" << profile() << "/DisableAudio"; + setSetting(str.str(), arg ? "1" : "0"); + } + bool getAnalyzeAudio() { + std::stringstream str; + str << serial_number() << "/" << profile() << "/AnalyzeAudio"; + return getSetting(str.str(), "0") == "1"; + } + void setAnalyzeAudio(bool arg) { + data->analyze_audio = arg; + std::stringstream str; + str << serial_number() << "/" << profile() << "/AnalyzeAudio"; + setSetting(str.str(), arg ? "1" : "0"); + } + bool getSyncAudio() { + std::stringstream str; + str << serial_number() << "/" << profile() << "/SyncAudio"; + return getSetting(str.str(), "0") == "1"; + } + void setSyncAudio(bool arg) { + data->sync_audio = arg; + std::stringstream str; + str << serial_number() << "/" << profile() << "/SyncAudio"; + setSetting(str.str(), arg ? "1" : "0"); + } + bool getHidden() { + std::stringstream str; + str << serial_number() << "/" << profile() << "/Hidden"; + return getSetting(str.str(), "0") == "1"; + } + void setHidden(bool arg) { + data->hidden = arg; + std::stringstream str; + str << serial_number() << "/" << profile() << "/Hidden"; + setSetting(str.str(), arg ? "1" : "0"); + } + int getDesiredAspect() { + std::stringstream str_key, str_val, ratio; + str_key << serial_number() << "/" << profile() << "/DesiredAspect"; + ratio << ((height() == 0) ? 0 : (int)(100 * width() / height())); + str_val << getSetting(str_key.str(), ratio.str()); + int desired_aspect = 0; + str_val >> desired_aspect; + return desired_aspect; + } + void setDesiredAspect(int arg) { + data->desired_aspect = arg; + std::stringstream str_key, str_val; + str_key << serial_number() << "/" << profile() << "/DesiredAspect"; + str_val << arg; + setSetting(str_key.str(), str_val.str()); + } + int getCacheMax() { + std::stringstream str_key, str_val; + str_key << serial_number() << "/" << profile() << "/CacheMax"; + str_val << getSetting(str_key.str(), "100"); + int result = 100; + str_val >> result; + return result; + } + void setCacheMax(int arg) { + data->cache_max = arg; + std::stringstream str_key, str_val; + str_key << serial_number() << "/" << profile() << "/CacheMax"; + str_val << arg; + setSetting(str_key.str(), str_val.str()); + } + +}; + +} + + #endif // ONVIF_DATA_H \ No newline at end of file diff --git a/libonvif/include/session.h b/libonvif/include/session.h index 0348379..aad4361 100644 --- a/libonvif/include/session.h +++ b/libonvif/include/session.h @@ -1,136 +1,136 @@ -/******************************************************************************* -* session.h -* -* copyright 2023 Stephen Rhodes -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; either -* version 2.1 of the License, or (at your option) any later version. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -*******************************************************************************/ - -#ifndef SESSION_H -#define SESSION_H - -#include -#include -#include -#include -#include "onvif.h" - -namespace libonvif -{ - -class Session -{ -public: - Session() - { - session = (OnvifSession*)calloc(sizeof(OnvifSession), 1); - initializeSession(session); - } - - ~Session() - { - closeSession(session); - free(session); - } - - operator OnvifSession* () - { - return session; - } - - void startDiscover() - { - std::thread thread([&]() { discover(); }); - thread.detach(); - } - - void discover() - { - if (interface.length() > 0) { - memset(session->preferred_network_address, 0, 16); - strcpy(session->preferred_network_address, interface.c_str()); - } - int number_of_devices = broadcast(session); - - std::vector devices; - for (int i = 0; i < number_of_devices; i++) { - if (abort) break; - Data data; - if (prepareOnvifData(i, session, data)) { - if (std::find(devices.begin(), devices.end(), data) == devices.end()) { - devices.push_back(data); - while (true) { - data = getCredential(data); - if (!data.cancelled) { - - getCapabilities(data); - if (!getTimeOffset(data)) { - time_t rawtime; - struct tm timeinfo; - time(&rawtime); - #ifdef _WIN32 - localtime_s(&timeinfo, &rawtime); - #else - localtime_r(&rawtime, &timeinfo); - #endif - if (timeinfo.tm_isdst && !data.dst()) - data.setTimeOffset(data.time_offset() - 3600); - } - - if (getDeviceInformation(data) == 0) { - int index = 0; - while (true) { - Data profile(data); - getProfileToken(profile, index); - if (strlen(profile->profileToken) == 0) - break; - getStreamUri(profile); - data.profiles.push_back(profile); - index++; - } - getData(data); - break; - } - } - else { - break; - } - } - } - } - } - - discovered(); - } - - void getActiveInterfaces() { - getActiveNetworkInterfaces(session); - } - - std::string active_interface(int arg) { return session->active_network_interfaces[arg]; } - - std::function discovered = nullptr; - std::function getCredential = nullptr; - std::function getData = nullptr; - - std::string interface; - OnvifSession* session; - bool abort = false; -}; - -} - -#endif // SESSION_H +/******************************************************************************* +* session.h +* +* copyright 2023 Stephen Rhodes +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +*******************************************************************************/ + +#ifndef SESSION_H +#define SESSION_H + +#include +#include +#include +#include +#include "onvif.h" + +namespace libonvif +{ + +class Session +{ +public: + Session() + { + session = (OnvifSession*)calloc(sizeof(OnvifSession), 1); + initializeSession(session); + } + + ~Session() + { + closeSession(session); + free(session); + } + + operator OnvifSession* () + { + return session; + } + + void startDiscover() + { + std::thread thread([&]() { discover(); }); + thread.detach(); + } + + void discover() + { + if (interface.length() > 0) { + memset(session->preferred_network_address, 0, 16); + strcpy(session->preferred_network_address, interface.c_str()); + } + int number_of_devices = broadcast(session); + + std::vector devices; + for (int i = 0; i < number_of_devices; i++) { + if (abort) break; + Data data; + if (prepareOnvifData(i, session, data)) { + if (std::find(devices.begin(), devices.end(), data) == devices.end()) { + devices.push_back(data); + while (true) { + data = getCredential(data); + if (!data.cancelled) { + + getCapabilities(data); + if (!getTimeOffset(data)) { + time_t rawtime; + struct tm timeinfo; + time(&rawtime); + #ifdef _WIN32 + localtime_s(&timeinfo, &rawtime); + #else + localtime_r(&rawtime, &timeinfo); + #endif + if (timeinfo.tm_isdst && !data.dst()) + data.setTimeOffset(data.time_offset() - 3600); + } + + if (getDeviceInformation(data) == 0) { + int index = 0; + while (true) { + Data profile(data); + getProfileToken(profile, index); + if (strlen(profile->profileToken) == 0) + break; + getStreamUri(profile); + data.profiles.push_back(profile); + index++; + } + getData(data); + break; + } + } + else { + break; + } + } + } + } + } + + discovered(); + } + + void getActiveInterfaces() { + getActiveNetworkInterfaces(session); + } + + std::string active_interface(int arg) { return session->active_network_interfaces[arg]; } + + std::function discovered = nullptr; + std::function getCredential = nullptr; + std::function getData = nullptr; + + std::string interface; + OnvifSession* session; + bool abort = false; +}; + +} + +#endif // SESSION_H diff --git a/libonvif/pyproject.toml b/libonvif/pyproject.toml index de520f0..bf0eb76 100644 --- a/libonvif/pyproject.toml +++ b/libonvif/pyproject.toml @@ -25,7 +25,7 @@ build-backend = "setuptools.build_meta" [project] name = "libonvif" -version = "3.2.0" +version = "3.2.1" authors = [ { name="Stephen Rhodes", email="sr99622@gmail.com" }, ] diff --git a/libonvif/setup.py b/libonvif/setup.py index 97b7d04..893aa8e 100644 --- a/libonvif/setup.py +++ b/libonvif/setup.py @@ -28,7 +28,7 @@ from setuptools.command.build_ext import build_ext PKG_NAME = "libonvif" -VERSION = "3.2.0" +VERSION = "3.2.1" class CMakeExtension(Extension): def __init__(self, name, sourcedir=""): diff --git a/libonvif/src/onvif.c b/libonvif/src/onvif.c index 1740d80..cd12b54 100644 --- a/libonvif/src/onvif.c +++ b/libonvif/src/onvif.c @@ -822,7 +822,13 @@ int setVideoEncoderConfiguration(struct OnvifData *onvif_data) { sprintf(use_count_buf, "%d", onvif_data->use_count); sprintf(width_buf, "%d", onvif_data->width); sprintf(height_buf, "%d", onvif_data->height); + sprintf(quality_buf, "%f", onvif_data->quality); + for (int i = 0; i < strlen(quality_buf); i++) { + if (quality_buf[i] == ',') + quality_buf[i] = '.'; + } + sprintf(multicast_port_buf, "%d", onvif_data->multicast_port); sprintf(multicast_ttl_buf, "%d", onvif_data->multicast_ttl); if (onvif_data->autostart) @@ -2713,7 +2719,7 @@ void getActiveNetworkInterfaces(struct OnvifSession* onvif_session) continue; } - if (strcmp(ifa->ifa_name, "lo")) { + if (strcmp(host, "127.0.0.1")) { strcpy(onvif_session->active_network_interfaces[count], host); strcat(onvif_session->active_network_interfaces[count], " - "); strcat(onvif_session->active_network_interfaces[count], ifa->ifa_name); diff --git a/libonvif/src/onvif.cpp b/libonvif/src/onvif.cpp index b719144..1c90475 100644 --- a/libonvif/src/onvif.cpp +++ b/libonvif/src/onvif.cpp @@ -166,6 +166,7 @@ PYBIND11_MODULE(libonvif, m) .def("setHidden", &Data::setHidden) .def("getCacheMax", &Data::getCacheMax) .def("setCacheMax", &Data::setCacheMax) + .def("toString", &Data::toString) .def(py::self == py::self) .def_readwrite("profiles", &Data::profiles) .def_readwrite("displayProfile", &Data::displayProfile) @@ -175,6 +176,7 @@ PYBIND11_MODULE(libonvif, m) .def_readwrite("discovered", &Data::discovered) .def_readwrite("getSetting", &Data::getSetting) .def_readwrite("setSetting", &Data::setSetting) + .def_readwrite("getProxyURI", &Data::getProxyURI) .def_readwrite("preset", &Data::preset) .def_readwrite("x", &Data::x) .def_readwrite("y", &Data::y) @@ -183,7 +185,7 @@ PYBIND11_MODULE(libonvif, m) .def_readwrite("alias", &Data::alias) .def_readwrite("cancelled", &Data::cancelled); - m.attr("__version__") = "2.0.10"; + m.attr("__version__") = "3.2.1"; } diff --git a/libonvif/test/onvif-test.cpp b/libonvif/test/onvif-test.cpp index 8716f09..fa82dd6 100644 --- a/libonvif/test/onvif-test.cpp +++ b/libonvif/test/onvif-test.cpp @@ -1,152 +1,152 @@ -#include -#include -#include -#include -#include -#include -#include -#include "onvif.h" -#ifdef _WIN32 -#include "getopt-win.h" -#else -#include -#endif - -int main(int argc, char **argv) -{ - std::cout << "Looking for cameras on the network..." << std::endl; - - struct OnvifSession *onvif_session = (struct OnvifSession*)calloc(sizeof(struct OnvifSession), 1); - - getActiveNetworkInterfaces(onvif_session); - for (int i = 0; i < 16; i++) { - std::cout << "interface: " << onvif_session->active_network_interfaces[i] << std::endl; - } - - std::string delimiter = " - "; - std::string thingy = onvif_session->active_network_interfaces[0]; - std::string token = thingy.substr(0, thingy.find(delimiter)); - std::cout << "---" << token << "---" << std::endl; - strcpy(onvif_session->preferred_network_address, "10.1.1.1"); - - struct OnvifData *tmp_onvif_data = (struct OnvifData*)calloc(sizeof(struct OnvifData), 1); - struct OnvifData *onvif_data = (struct OnvifData*)calloc(sizeof(struct OnvifData), 1); - - initializeSession(onvif_session); - int n = broadcast(onvif_session); - std::cout << "Found " << n << " cameras" << std::endl; - for (int i = 0; i < n; i++) { - if (prepareOnvifData(i, onvif_session, tmp_onvif_data)) { - char host[128]; - extractHost(tmp_onvif_data->xaddrs, host); - getHostname(tmp_onvif_data); - printf("%s %s(%s)\n",host, - tmp_onvif_data->host_name, - tmp_onvif_data->camera_name); - - if (!strcmp(host, "10.1.1.67")) { - std::cout << "FOUND HOST" << tmp_onvif_data->camera_name << std::endl; - copyData(onvif_data, tmp_onvif_data); - } - } - else { - std::cout << "found invalid xaddrs in device repsonse" << std::endl; - } - } - - closeSession(onvif_session); - free(onvif_session); - free(tmp_onvif_data); - - std::cout << "subject camera - " << onvif_data->camera_name << std::endl; - - strcpy(onvif_data->username, "admin"); - strcpy(onvif_data->password, "admin123"); - if (getDeviceInformation(onvif_data)) - std::cout << "getDeviceInformation failure " << onvif_data->last_error << std::endl; - - if (getCapabilities(onvif_data)) - std::cout << "getCapabilities failure " << onvif_data->last_error << std::endl; - - if (getProfileToken(onvif_data, 0)) - std::cout << "getProfileToken failure " << onvif_data->last_error << std::endl; - - if (getProfile(onvif_data)) - std::cout << "getProfile failure " << onvif_data->last_error << std::endl; - - if (setSystemDateAndTime(onvif_data)) - std::cout << "setSystemDateAndTime failure " << onvif_data->last_error << std::endl; - - if (getStreamUri(onvif_data)) - std::cout << "getStreamUri failure " << onvif_data->last_error << std::endl; - - std::cout << onvif_data->stream_uri << std::endl; - - if(getVideoEncoderConfiguration(onvif_data)) - std::cout << "getVideoEncoderConfiguration failure " << onvif_data->last_error << std::endl; - - std::cout << " Width: " << onvif_data->width << "\n"; - std::cout << " Height: " << onvif_data->height << "\n"; - std::cout << " Frame Rate: " << onvif_data->frame_rate << "\n"; - std::cout << " Gov Length: " << onvif_data->gov_length << "\n"; - std::cout << " Bitrate: " << onvif_data->bitrate << "\n" << std::endl; - - if (getOptions(onvif_data)) - std::cout << "getOptions failure " << onvif_data->last_error << std::endl; - - std::cout << " Min Brightness: " << onvif_data->brightness_min << "\n"; - std::cout << " Max Brightness: " << onvif_data->brightness_max << "\n"; - std::cout << " Min ColorSaturation: " << onvif_data->saturation_min << "\n"; - std::cout << " Max ColorSaturation: " << onvif_data->saturation_max << "\n"; - std::cout << " Min Contrast: " << onvif_data->contrast_min << "\n"; - std::cout << " Max Contrast: " << onvif_data->contrast_max << "\n"; - std::cout << " Min Sharpness: " << onvif_data->sharpness_min << "\n"; - std::cout << " Max Sharpness: " << onvif_data->sharpness_max << "\n" << std::endl; - - if (getImagingSettings(onvif_data)) - std::cout << "getImagingSettings failure" << onvif_data->last_error << std::endl; - - std::cout << " Brightness: " << onvif_data->brightness << "\n"; - std::cout << " Contrast: " << onvif_data->contrast << "\n"; - std::cout << " Saturation: " << onvif_data->saturation << "\n"; - std::cout << " Sharpness: " << onvif_data->sharpness << "\n" << std::endl; - - if (getTimeOffset(onvif_data)) - std::cout << "getTimeOffset failure " << onvif_data->last_error << std::endl; - - std::cout << " Time Offset: " << onvif_data->time_offset << " seconds" << "\n"; - std::cout << " Timezone: " << onvif_data->timezone << "\n"; - std::cout << " DST: " << (onvif_data->dst ? "Yes" : "No") << "\n"; - std::cout << " Time Set By: " << ((onvif_data->datetimetype == 'M') ? "Manual" : "NTP") << "\n"; - - if (getNetworkInterfaces(onvif_data)) - std::cout << "getNetworkInterfaces failure " << onvif_data->last_error << std::endl; - if (getNetworkDefaultGateway(onvif_data)) - std::cout << "getNetworkDefaultGateway failure " << onvif_data->last_error << std::endl; - if (getDNS(onvif_data)) - std::cout << "getDNS failure " << onvif_data->last_error << std::endl; - - std::cout << " IP Address: " << onvif_data->ip_address_buf << "\n"; - std::cout << " Gateway: " << onvif_data->default_gateway_buf << "\n"; - std::cout << " DNS: " << onvif_data->dns_buf << "\n"; - std::cout << " DHCP: " << (onvif_data->dhcp_enabled ? "YES" : "NO") << "\n" << std::endl; - - if (getVideoEncoderConfigurationOptions(onvif_data)) - std::cout << "getVideoEncoderConfigurationOptions failure " << onvif_data->last_error << std::endl; - - std::cout << " Available Resolutions" << std::endl; - for (int i=0; i<16; i++) { - if (strlen(onvif_data->resolutions_buf[i])) - std::cout << " " << onvif_data->resolutions_buf[i] << std::endl; - } - - std::cout << " Min Gov Length: " << onvif_data->gov_length_min << "\n"; - std::cout << " Max Gov Length: " << onvif_data->gov_length_max << "\n"; - std::cout << " Min Frame Rate: " << onvif_data->frame_rate_min << "\n"; - std::cout << " Max Frame Rate: " << onvif_data->frame_rate_max << "\n"; - std::cout << " Min Bit Rate: " << onvif_data->bitrate_min << "\n"; - std::cout << " Max Bit Rate: " << onvif_data->bitrate_max << "\n" << std::endl; - - - free(onvif_data); -} +#include +#include +#include +#include +#include +#include +#include +#include "onvif.h" +#ifdef _WIN32 +#include "getopt-win.h" +#else +#include +#endif + +int main(int argc, char **argv) +{ + std::cout << "Looking for cameras on the network..." << std::endl; + + struct OnvifSession *onvif_session = (struct OnvifSession*)calloc(sizeof(struct OnvifSession), 1); + + getActiveNetworkInterfaces(onvif_session); + for (int i = 0; i < 16; i++) { + std::cout << "interface: " << onvif_session->active_network_interfaces[i] << std::endl; + } + + std::string delimiter = " - "; + std::string thingy = onvif_session->active_network_interfaces[0]; + std::string token = thingy.substr(0, thingy.find(delimiter)); + std::cout << "---" << token << "---" << std::endl; + strcpy(onvif_session->preferred_network_address, "10.1.1.1"); + + struct OnvifData *tmp_onvif_data = (struct OnvifData*)calloc(sizeof(struct OnvifData), 1); + struct OnvifData *onvif_data = (struct OnvifData*)calloc(sizeof(struct OnvifData), 1); + + initializeSession(onvif_session); + int n = broadcast(onvif_session); + std::cout << "Found " << n << " cameras" << std::endl; + for (int i = 0; i < n; i++) { + if (prepareOnvifData(i, onvif_session, tmp_onvif_data)) { + char host[128]; + extractHost(tmp_onvif_data->xaddrs, host); + getHostname(tmp_onvif_data); + printf("%s %s(%s)\n",host, + tmp_onvif_data->host_name, + tmp_onvif_data->camera_name); + + if (!strcmp(host, "10.1.1.67")) { + std::cout << "FOUND HOST" << tmp_onvif_data->camera_name << std::endl; + copyData(onvif_data, tmp_onvif_data); + } + } + else { + std::cout << "found invalid xaddrs in device repsonse" << std::endl; + } + } + + closeSession(onvif_session); + free(onvif_session); + free(tmp_onvif_data); + + std::cout << "subject camera - " << onvif_data->camera_name << std::endl; + + strcpy(onvif_data->username, "admin"); + strcpy(onvif_data->password, "admin123"); + if (getDeviceInformation(onvif_data)) + std::cout << "getDeviceInformation failure " << onvif_data->last_error << std::endl; + + if (getCapabilities(onvif_data)) + std::cout << "getCapabilities failure " << onvif_data->last_error << std::endl; + + if (getProfileToken(onvif_data, 0)) + std::cout << "getProfileToken failure " << onvif_data->last_error << std::endl; + + if (getProfile(onvif_data)) + std::cout << "getProfile failure " << onvif_data->last_error << std::endl; + + if (setSystemDateAndTime(onvif_data)) + std::cout << "setSystemDateAndTime failure " << onvif_data->last_error << std::endl; + + if (getStreamUri(onvif_data)) + std::cout << "getStreamUri failure " << onvif_data->last_error << std::endl; + + std::cout << onvif_data->stream_uri << std::endl; + + if(getVideoEncoderConfiguration(onvif_data)) + std::cout << "getVideoEncoderConfiguration failure " << onvif_data->last_error << std::endl; + + std::cout << " Width: " << onvif_data->width << "\n"; + std::cout << " Height: " << onvif_data->height << "\n"; + std::cout << " Frame Rate: " << onvif_data->frame_rate << "\n"; + std::cout << " Gov Length: " << onvif_data->gov_length << "\n"; + std::cout << " Bitrate: " << onvif_data->bitrate << "\n" << std::endl; + + if (getOptions(onvif_data)) + std::cout << "getOptions failure " << onvif_data->last_error << std::endl; + + std::cout << " Min Brightness: " << onvif_data->brightness_min << "\n"; + std::cout << " Max Brightness: " << onvif_data->brightness_max << "\n"; + std::cout << " Min ColorSaturation: " << onvif_data->saturation_min << "\n"; + std::cout << " Max ColorSaturation: " << onvif_data->saturation_max << "\n"; + std::cout << " Min Contrast: " << onvif_data->contrast_min << "\n"; + std::cout << " Max Contrast: " << onvif_data->contrast_max << "\n"; + std::cout << " Min Sharpness: " << onvif_data->sharpness_min << "\n"; + std::cout << " Max Sharpness: " << onvif_data->sharpness_max << "\n" << std::endl; + + if (getImagingSettings(onvif_data)) + std::cout << "getImagingSettings failure" << onvif_data->last_error << std::endl; + + std::cout << " Brightness: " << onvif_data->brightness << "\n"; + std::cout << " Contrast: " << onvif_data->contrast << "\n"; + std::cout << " Saturation: " << onvif_data->saturation << "\n"; + std::cout << " Sharpness: " << onvif_data->sharpness << "\n" << std::endl; + + if (getTimeOffset(onvif_data)) + std::cout << "getTimeOffset failure " << onvif_data->last_error << std::endl; + + std::cout << " Time Offset: " << onvif_data->time_offset << " seconds" << "\n"; + std::cout << " Timezone: " << onvif_data->timezone << "\n"; + std::cout << " DST: " << (onvif_data->dst ? "Yes" : "No") << "\n"; + std::cout << " Time Set By: " << ((onvif_data->datetimetype == 'M') ? "Manual" : "NTP") << "\n"; + + if (getNetworkInterfaces(onvif_data)) + std::cout << "getNetworkInterfaces failure " << onvif_data->last_error << std::endl; + if (getNetworkDefaultGateway(onvif_data)) + std::cout << "getNetworkDefaultGateway failure " << onvif_data->last_error << std::endl; + if (getDNS(onvif_data)) + std::cout << "getDNS failure " << onvif_data->last_error << std::endl; + + std::cout << " IP Address: " << onvif_data->ip_address_buf << "\n"; + std::cout << " Gateway: " << onvif_data->default_gateway_buf << "\n"; + std::cout << " DNS: " << onvif_data->dns_buf << "\n"; + std::cout << " DHCP: " << (onvif_data->dhcp_enabled ? "YES" : "NO") << "\n" << std::endl; + + if (getVideoEncoderConfigurationOptions(onvif_data)) + std::cout << "getVideoEncoderConfigurationOptions failure " << onvif_data->last_error << std::endl; + + std::cout << " Available Resolutions" << std::endl; + for (int i=0; i<16; i++) { + if (strlen(onvif_data->resolutions_buf[i])) + std::cout << " " << onvif_data->resolutions_buf[i] << std::endl; + } + + std::cout << " Min Gov Length: " << onvif_data->gov_length_min << "\n"; + std::cout << " Max Gov Length: " << onvif_data->gov_length_max << "\n"; + std::cout << " Min Frame Rate: " << onvif_data->frame_rate_min << "\n"; + std::cout << " Max Frame Rate: " << onvif_data->frame_rate_max << "\n"; + std::cout << " Min Bit Rate: " << onvif_data->bitrate_min << "\n"; + std::cout << " Max Bit Rate: " << onvif_data->bitrate_max << "\n" << std::endl; + + + free(onvif_data); +} diff --git a/onvif-gui/gui/__init__.py b/onvif-gui/gui/__init__.py index 15ca685..91d18a9 100644 --- a/onvif-gui/gui/__init__.py +++ b/onvif-gui/gui/__init__.py @@ -1 +1,3 @@ -from .main import MainWindow \ No newline at end of file +from .main import MainWindow +from .player import Player +from .enums import ProxyType, MediaSource, StreamState \ No newline at end of file diff --git a/onvif-gui/gui/components/target.py b/onvif-gui/gui/components/target.py index a2ef7b0..d410ef3 100644 --- a/onvif-gui/gui/components/target.py +++ b/onvif-gui/gui/components/target.py @@ -1,238 +1,238 @@ -#******************************************************************** -# libonvif/onvif-gui/gui/components/target.py -# -# Copyright (c) 2024 Stephen Rhodes -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#*********************************************************************/ - -from PyQt6.QtWidgets import QDialog, QGridLayout, QListWidget, QListWidgetItem, \ - QDialogButtonBox, QWidget, QLabel, QPushButton, QMessageBox, QSlider, QCheckBox -from PyQt6.QtCore import Qt, pyqtSignal, QObject -from .warningbar import WarningBar, Indicator -from gui.onvif.datastructures import MediaSource -from loguru import logger - -class Target(QListWidgetItem): - def __init__(self, name, id): - super().__init__(name) - self.id = id - -class TargetDialog(QDialog): - def __init__(self, mw): - super().__init__(mw) - self.mw = mw - self.source = MediaSource.CAMERA - - self.targets = { - 0: "person", - 1: "bicycle", - 2: "car", - 3: "motorcycle", - 4: "airplane", - 5: "bus", - 6: "train", - 7: "truck", - 8: "boat", - 14: "bird", - 15: "cat", - 16: "dog", - 17: "horse", - 18: "sheep", - 19: "cow", - 21: "bear" - } - - self.setModal(True) - self.setWindowTitle("Add Target") - self.list = QListWidget() - for key in self.targets: - self.list.addItem(Target(self.targets[key], key)) - - self.buttonBox = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Close) - self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setFocusPolicy(Qt.FocusPolicy.NoFocus) - self.buttonBox.rejected.connect(self.reject) - - lytMain = QGridLayout(self) - lytMain.addWidget(self.list, 0, 0, 1, 2) - lytMain.addWidget(self.buttonBox, 1, 0, 1, 2) - - self.list.setCurrentRow(0) - - def reject(self): - self.hide() - -class TargetListSignals(QObject): - delete = pyqtSignal() - -class TargetList(QListWidget): - def __init__(self, mw): - super().__init__() - self.mw = mw - self.signals = TargetListSignals() - - def keyPressEvent(self, event): - if event.key() == Qt.Key.Key_Delete: - self.signals.delete.emit() - return super().keyPressEvent(event) - - def toString(self): - output = "" - length = self.count() - for i in range(length): - output += str(self.item(i).id) - if i < length - 1: - output += ":" - return output - -class TargetSelector(QWidget): - def __init__(self, mw, module): - super().__init__() - self.mw = mw - self.module = module - - self.lstTargets = TargetList(self.mw) - self.lstTargets.signals.delete.connect(self.btnDeleteTargetClicked) - self.lblTargets = QLabel("Targets") - self.btnAddTarget = QPushButton("+") - self.btnAddTarget.clicked.connect(self.btnAddTargetClicked) - self.btnDeleteTarget = QPushButton("-") - self.btnDeleteTarget.clicked.connect(self.btnDeleteTargetClicked) - self.dlgTarget = TargetDialog(self.mw) - self.dlgTarget.list.itemDoubleClicked.connect(self.onAddItemDoubleClicked) - self.dlgTarget.buttonBox.accepted.connect(self.dlgListAccepted) - - # gui works better if these are on the same panel - self.barLevel = WarningBar() - self.indAlarm = Indicator(self.mw) - self.sldGain = QSlider(Qt.Orientation.Vertical) - self.sldGain.setMinimum(0) - self.sldGain.setMaximum(100) - self.sldGain.setValue(0) - self.sldGain.valueChanged.connect(self.sldGainValueChanged) - self.lblGain = QLabel("0") - - pnlTargets = QWidget() - pnlTargets.setMaximumWidth(200) - lytTargets = QGridLayout(pnlTargets) - - self.chkShowBoxes = QCheckBox("Show Boxes") - self.chkShowBoxes.stateChanged.connect(self.chkShowBoxesStateChanged) - - lytTargets.addWidget(self.lblTargets, 0, 0, 1, 1) - lytTargets.addWidget(self.btnDeleteTarget, 0, 1, 1, 1) - lytTargets.addWidget(self.btnAddTarget, 0, 2, 1, 1) - lytTargets.addWidget(self.lstTargets, 1, 0, 2, 3) - - lytMain = QGridLayout(self) - lytMain.addWidget(pnlTargets, 1, 0, 2, 1) - lytMain.addWidget(self.lblGain, 1, 1, 1, 1, Qt.AlignmentFlag.AlignHCenter) - lytMain.addWidget(self.sldGain, 2, 1, 1, 1, Qt.AlignmentFlag.AlignHCenter) - lytMain.addWidget(QLabel("Limit"), 3, 1, 1, 1, Qt.AlignmentFlag.AlignHCenter) - lytMain.addWidget(self.indAlarm, 1, 2, 1, 1) - lytMain.addWidget(self.barLevel, 2, 2, 1, 1) - lytMain.addWidget(QLabel(), 2, 3, 1, 1) - lytMain.addWidget(self.chkShowBoxes, 3, 0, 1, 1, Qt.AlignmentFlag.AlignCenter) - lytMain.setContentsMargins(0, 0, 0, 0) - - def btnAddTargetClicked(self): - self.dlgTarget.show() - - def btnDeleteTargetClicked(self): - try: - if item := self.lstTargets.currentItem(): - ret = QMessageBox.warning(self, "Delete Target: " + item.text(), "You are about to delete target\n" - "Are you sure you want to continue?", - QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel) - if ret != QMessageBox.StandardButton.Ok: - return - self.lstTargets.takeItem(self.lstTargets.row(item)) - - match self.mw.videoConfigure.source: - case MediaSource.CAMERA: - if camera := self.mw.cameraPanel.getCurrentCamera(): - if camera.videoModelSettings: - camera.videoModelSettings.setTargets(self.lstTargets.toString()) - case MediaSource.FILE: - if self.mw.filePanel.videoModelSettings: - self.mw.filePanel.videoModelSettings.setTargets(self.lstTargets.toString()) - except Exception as ex: - logger.error(ex) - - def dlgListAccepted(self): - item = self.dlgTarget.list.item(self.dlgTarget.list.currentRow()) - self.onAddItemDoubleClicked(item) - - def onAddItemDoubleClicked(self, item): - try: - target = Target(item.text(), item.id) - found = False - for i in range(self.lstTargets.count()): - if target.text() == self.lstTargets.item(i).text(): - found = True - break - if not found: - self.lstTargets.addItem(target) - - match self.mw.videoConfigure.source: - case MediaSource.CAMERA: - if camera := self.mw.cameraPanel.getCurrentCamera(): - if camera.videoModelSettings: - camera.videoModelSettings.setTargets(self.lstTargets.toString()) - case MediaSource.FILE: - if self.mw.filePanel.videoModelSettings: - self.mw.filePanel.videoModelSettings.setTargets(self.lstTargets.toString()) - except Exception as ex: - logger.error(ex) - - def setTargets(self, targets): - while self.lstTargets.count() > 0: - self.lstTargets.takeItem(0) - - for t in targets: - self.lstTargets.addItem(Target(self.dlgTarget.targets[t], t)) - - def getTargets(self): - output = [] - for i in range(self.lstTargets.count()): - target = self.lstTargets.item(i) - output.append(target.id) - return output - - def sldGainValueChanged(self, value): - try: - self.lblGain.setText(f'{value}') - match self.mw.videoConfigure.source: - case MediaSource.CAMERA: - if camera := self.mw.cameraPanel.getCurrentCamera(): - if camera.videoModelSettings: - camera.videoModelSettings.setModelOutputLimit(value) - case MediaSource.FILE: - if self.mw.filePanel.videoModelSettings: - self.mw.filePanel.videoModelSettings.setModelOutputLimit(value) - except Exception as ex: - logger.error(ex) - - def chkShowBoxesStateChanged(self, state): - try: - match self.mw.videoConfigure.source: - case MediaSource.CAMERA: - if camera := self.mw.cameraPanel.getCurrentCamera(): - if camera.videoModelSettings: - camera.videoModelSettings.setModelShowBoxes(bool(state)) - case MediaSource.FILE: - if self.mw.filePanel.videoModelSettings: - self.mw.filePanel.videoModelSettings.setModelShowBoxes(bool(state)) - except Exception as ex: - logger.error(ex) +#******************************************************************** +# libonvif/onvif-gui/gui/components/target.py +# +# Copyright (c) 2024 Stephen Rhodes +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#*********************************************************************/ + +from PyQt6.QtWidgets import QDialog, QGridLayout, QListWidget, QListWidgetItem, \ + QDialogButtonBox, QWidget, QLabel, QPushButton, QMessageBox, QSlider, QCheckBox +from PyQt6.QtCore import Qt, pyqtSignal, QObject +from .warningbar import WarningBar, Indicator +from gui.enums import MediaSource +from loguru import logger + +class Target(QListWidgetItem): + def __init__(self, name, id): + super().__init__(name) + self.id = id + +class TargetDialog(QDialog): + def __init__(self, mw): + super().__init__(mw) + self.mw = mw + self.source = MediaSource.CAMERA + + self.targets = { + 0: "person", + 1: "bicycle", + 2: "car", + 3: "motorcycle", + 4: "airplane", + 5: "bus", + 6: "train", + 7: "truck", + 8: "boat", + 14: "bird", + 15: "cat", + 16: "dog", + 17: "horse", + 18: "sheep", + 19: "cow", + 21: "bear" + } + + self.setModal(True) + self.setWindowTitle("Add Target") + self.list = QListWidget() + for key in self.targets: + self.list.addItem(Target(self.targets[key], key)) + + self.buttonBox = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Close) + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.buttonBox.rejected.connect(self.reject) + + lytMain = QGridLayout(self) + lytMain.addWidget(self.list, 0, 0, 1, 2) + lytMain.addWidget(self.buttonBox, 1, 0, 1, 2) + + self.list.setCurrentRow(0) + + def reject(self): + self.hide() + +class TargetListSignals(QObject): + delete = pyqtSignal() + +class TargetList(QListWidget): + def __init__(self, mw): + super().__init__() + self.mw = mw + self.signals = TargetListSignals() + + def keyPressEvent(self, event): + if event.key() == Qt.Key.Key_Delete: + self.signals.delete.emit() + return super().keyPressEvent(event) + + def toString(self): + output = "" + length = self.count() + for i in range(length): + output += str(self.item(i).id) + if i < length - 1: + output += ":" + return output + +class TargetSelector(QWidget): + def __init__(self, mw, module): + super().__init__() + self.mw = mw + self.module = module + + self.lstTargets = TargetList(self.mw) + self.lstTargets.signals.delete.connect(self.btnDeleteTargetClicked) + self.lblTargets = QLabel("Targets") + self.btnAddTarget = QPushButton("+") + self.btnAddTarget.clicked.connect(self.btnAddTargetClicked) + self.btnDeleteTarget = QPushButton("-") + self.btnDeleteTarget.clicked.connect(self.btnDeleteTargetClicked) + self.dlgTarget = TargetDialog(self.mw) + self.dlgTarget.list.itemDoubleClicked.connect(self.onAddItemDoubleClicked) + self.dlgTarget.buttonBox.accepted.connect(self.dlgListAccepted) + + # gui works better if these are on the same panel + self.barLevel = WarningBar() + self.indAlarm = Indicator(self.mw) + self.sldGain = QSlider(Qt.Orientation.Vertical) + self.sldGain.setMinimum(0) + self.sldGain.setMaximum(100) + self.sldGain.setValue(0) + self.sldGain.valueChanged.connect(self.sldGainValueChanged) + self.lblGain = QLabel("0") + + pnlTargets = QWidget() + pnlTargets.setMaximumWidth(200) + lytTargets = QGridLayout(pnlTargets) + + self.chkShowBoxes = QCheckBox("Show Boxes") + self.chkShowBoxes.stateChanged.connect(self.chkShowBoxesStateChanged) + + lytTargets.addWidget(self.lblTargets, 0, 0, 1, 1) + lytTargets.addWidget(self.btnDeleteTarget, 0, 1, 1, 1) + lytTargets.addWidget(self.btnAddTarget, 0, 2, 1, 1) + lytTargets.addWidget(self.lstTargets, 1, 0, 2, 3) + + lytMain = QGridLayout(self) + lytMain.addWidget(pnlTargets, 1, 0, 2, 1) + lytMain.addWidget(self.lblGain, 1, 1, 1, 1, Qt.AlignmentFlag.AlignHCenter) + lytMain.addWidget(self.sldGain, 2, 1, 1, 1, Qt.AlignmentFlag.AlignHCenter) + lytMain.addWidget(QLabel("Limit"), 3, 1, 1, 1, Qt.AlignmentFlag.AlignHCenter) + lytMain.addWidget(self.indAlarm, 1, 2, 1, 1) + lytMain.addWidget(self.barLevel, 2, 2, 1, 1) + lytMain.addWidget(QLabel(), 2, 3, 1, 1) + lytMain.addWidget(self.chkShowBoxes, 3, 0, 1, 1, Qt.AlignmentFlag.AlignCenter) + lytMain.setContentsMargins(0, 0, 0, 0) + + def btnAddTargetClicked(self): + self.dlgTarget.show() + + def btnDeleteTargetClicked(self): + try: + if item := self.lstTargets.currentItem(): + ret = QMessageBox.warning(self, "Delete Target: " + item.text(), "You are about to delete target\n" + "Are you sure you want to continue?", + QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel) + if ret != QMessageBox.StandardButton.Ok: + return + self.lstTargets.takeItem(self.lstTargets.row(item)) + + match self.mw.videoConfigure.source: + case MediaSource.CAMERA: + if camera := self.mw.cameraPanel.getCurrentCamera(): + if camera.videoModelSettings: + camera.videoModelSettings.setTargets(self.lstTargets.toString()) + case MediaSource.FILE: + if self.mw.filePanel.videoModelSettings: + self.mw.filePanel.videoModelSettings.setTargets(self.lstTargets.toString()) + except Exception as ex: + logger.error(ex) + + def dlgListAccepted(self): + item = self.dlgTarget.list.item(self.dlgTarget.list.currentRow()) + self.onAddItemDoubleClicked(item) + + def onAddItemDoubleClicked(self, item): + try: + target = Target(item.text(), item.id) + found = False + for i in range(self.lstTargets.count()): + if target.text() == self.lstTargets.item(i).text(): + found = True + break + if not found: + self.lstTargets.addItem(target) + + match self.mw.videoConfigure.source: + case MediaSource.CAMERA: + if camera := self.mw.cameraPanel.getCurrentCamera(): + if camera.videoModelSettings: + camera.videoModelSettings.setTargets(self.lstTargets.toString()) + case MediaSource.FILE: + if self.mw.filePanel.videoModelSettings: + self.mw.filePanel.videoModelSettings.setTargets(self.lstTargets.toString()) + except Exception as ex: + logger.error(ex) + + def setTargets(self, targets): + while self.lstTargets.count() > 0: + self.lstTargets.takeItem(0) + + for t in targets: + self.lstTargets.addItem(Target(self.dlgTarget.targets[t], t)) + + def getTargets(self): + output = [] + for i in range(self.lstTargets.count()): + target = self.lstTargets.item(i) + output.append(target.id) + return output + + def sldGainValueChanged(self, value): + try: + self.lblGain.setText(f'{value}') + match self.mw.videoConfigure.source: + case MediaSource.CAMERA: + if camera := self.mw.cameraPanel.getCurrentCamera(): + if camera.videoModelSettings: + camera.videoModelSettings.setModelOutputLimit(value) + case MediaSource.FILE: + if self.mw.filePanel.videoModelSettings: + self.mw.filePanel.videoModelSettings.setModelOutputLimit(value) + except Exception as ex: + logger.error(ex) + + def chkShowBoxesStateChanged(self, state): + try: + match self.mw.videoConfigure.source: + case MediaSource.CAMERA: + if camera := self.mw.cameraPanel.getCurrentCamera(): + if camera.videoModelSettings: + camera.videoModelSettings.setModelShowBoxes(bool(state)) + case MediaSource.FILE: + if self.mw.filePanel.videoModelSettings: + self.mw.filePanel.videoModelSettings.setModelShowBoxes(bool(state)) + except Exception as ex: + logger.error(ex) diff --git a/onvif-gui/gui/components/thresholdslider.py b/onvif-gui/gui/components/thresholdslider.py index 432b93b..1a6e7ae 100644 --- a/onvif-gui/gui/components/thresholdslider.py +++ b/onvif-gui/gui/components/thresholdslider.py @@ -19,7 +19,7 @@ from PyQt6.QtWidgets import QWidget, QSlider, QLabel, QGridLayout from PyQt6.QtCore import Qt -from gui.onvif.datastructures import MediaSource +from gui.enums import MediaSource from loguru import logger class ThresholdSlider(QWidget): diff --git a/onvif-gui/gui/components/warningbar.py b/onvif-gui/gui/components/warningbar.py index 0aaca50..f847f06 100644 --- a/onvif-gui/gui/components/warningbar.py +++ b/onvif-gui/gui/components/warningbar.py @@ -1,84 +1,84 @@ -#******************************************************************** -# libonvif/onvif-gui/gui/components/warningbar.py -# -# Copyright (c) 2024 Stephen Rhodes -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#*********************************************************************/ - -from PyQt6.QtWidgets import QLabel -from PyQt6.QtGui import QPainter, QColorConstants, QLinearGradient, QColor -from PyQt6.QtCore import QRect, QTimer, pyqtSignal, QObject - -class WarningBar(QLabel): - def __init__(self): - super().__init__() - self.setMaximumWidth(15) - self.setStyleSheet("QLabel { border : 1px solid #808D9E; }") - self.level = 0.0 - self.inverted = False - - def paintEvent(self, event): - marker = max(int(self.height()*(1-self.level)-2), 0) - if self.inverted: - marker = min(int(self.height() * self.level), self.height()-2) - painter = QPainter(self) - gradient = QLinearGradient(0,0,0,100) - gradient.setColorAt(0.0, QColorConstants.Red) - gradient.setColorAt(0.5, QColorConstants.DarkYellow) - gradient.setColorAt(1.0, QColorConstants.DarkGreen) - painter.fillRect(QRect(1, 1, 13, self.height()-2), gradient) - painter.fillRect(QRect(1, 1, 13, marker), QColor("#3B3B3B")) - - def setLevel(self, level): - self.level = level - self.update() - -class IndicatorSignals(QObject): - start = pyqtSignal() - -class Indicator(QLabel): - def __init__(self, mw): - super().__init__() - self.mw = mw - self.setMaximumWidth(15) - self.setMaximumHeight(10) - self.setStyleSheet("QLabel { border : 1px solid #808D9E; }") - self.timer = QTimer() - self.timer.setInterval(self.mw.settingsPanel.spnLagTime.value() * 1000) - self.timer.setSingleShot(True) - self.timer.timeout.connect(self.timeout) - self.signals = IndicatorSignals() - self.signals.start.connect(self.timer.start) - self.state = 0 - - def paintEvent(self, event): - color = QColor("#3B3B3B") - if self.state: - color = QColorConstants.Red - painter = QPainter(self) - painter.fillRect(QRect(1, 1, 13, self.height()-2), color) - - def setState(self, state): - self.state = int(state) - if state: - self.signals.start.emit() - self.update() - - def getState(self): - return self.state - - def timeout(self): - self.setState(0) - +#******************************************************************** +# libonvif/onvif-gui/gui/components/warningbar.py +# +# Copyright (c) 2024 Stephen Rhodes +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#*********************************************************************/ + +from PyQt6.QtWidgets import QLabel +from PyQt6.QtGui import QPainter, QColorConstants, QLinearGradient, QColor +from PyQt6.QtCore import QRect, QTimer, pyqtSignal, QObject + +class WarningBar(QLabel): + def __init__(self): + super().__init__() + self.setMaximumWidth(15) + self.setStyleSheet("QLabel { border : 1px solid #808D9E; }") + self.level = 0.0 + self.inverted = False + + def paintEvent(self, event): + marker = max(int(self.height()*(1-self.level)-2), 0) + if self.inverted: + marker = min(int(self.height() * self.level), self.height()-2) + painter = QPainter(self) + gradient = QLinearGradient(0,0,0,100) + gradient.setColorAt(0.0, QColorConstants.Red) + gradient.setColorAt(0.5, QColorConstants.DarkYellow) + gradient.setColorAt(1.0, QColorConstants.DarkGreen) + painter.fillRect(QRect(1, 1, 13, self.height()-2), gradient) + painter.fillRect(QRect(1, 1, 13, marker), QColor("#3B3B3B")) + + def setLevel(self, level): + self.level = level + self.update() + +class IndicatorSignals(QObject): + start = pyqtSignal() + +class Indicator(QLabel): + def __init__(self, mw): + super().__init__() + self.mw = mw + self.setMaximumWidth(15) + self.setMaximumHeight(10) + self.setStyleSheet("QLabel { border : 1px solid #808D9E; }") + self.timer = QTimer() + self.timer.setInterval(self.mw.settingsPanel.alarm.spnLagTime.value() * 1000) + self.timer.setSingleShot(True) + self.timer.timeout.connect(self.timeout) + self.signals = IndicatorSignals() + self.signals.start.connect(self.timer.start) + self.state = 0 + + def paintEvent(self, event): + color = QColor("#3B3B3B") + if self.state: + color = QColorConstants.Red + painter = QPainter(self) + painter.fillRect(QRect(1, 1, 13, self.height()-2), color) + + def setState(self, state): + self.state = int(state) + if state: + self.signals.start.emit() + self.update() + + def getState(self): + return self.state + + def timeout(self): + self.setState(0) + diff --git a/onvif-gui/gui/enums.py b/onvif-gui/gui/enums.py new file mode 100644 index 0000000..2a03f8d --- /dev/null +++ b/onvif-gui/gui/enums.py @@ -0,0 +1,17 @@ +from enum import Enum + +class ProxyType(Enum): + STAND_ALONE = 0 + SERVER = 1 + CLIENT = 2 + +class StreamState(Enum): + IDLE = 0 + CONNECTING = 1 + CONNECTED = 2 + INVALID = 3 + +class MediaSource(Enum): + CAMERA = 0 + FILE = 1 + diff --git a/onvif-gui/gui/glwidget.py b/onvif-gui/gui/glwidget.py index 0ccf752..95badfa 100644 --- a/onvif-gui/gui/glwidget.py +++ b/onvif-gui/gui/glwidget.py @@ -22,8 +22,8 @@ from PyQt6.QtCore import QSize, QPointF, QRectF, QTimer, QObject, pyqtSignal import numpy as np from datetime import datetime -from time import sleep -from gui.onvif.datastructures import StreamState +import time +from gui.enums import StreamState from loguru import logger class GLWidgetSignals(QObject): @@ -48,11 +48,12 @@ def __init__(self, mw): self.timer = QTimer() self.timer.timeout.connect(self.timerCallback) - refreshInterval = self.mw.settingsPanel.spnDisplayRefresh.value() + refreshInterval = self.mw.settingsPanel.general.spnDisplayRefresh.value() self.timer.start(refreshInterval) def renderCallback(self, F, player): try : + player.last_render = datetime.now() if player.analyze_video and self.mw.videoConfigure.initialized: F = self.mw.pyVideoCallback(F, player) else: @@ -83,7 +84,7 @@ def renderCallback(self, F, player): self.mw.pm.sizes[player.uri] = QSize(w_s, h_s) while player.rendering: - sleep(0.001) + time.sleep(0.001) self.image_loading = True @@ -112,6 +113,7 @@ def renderCallback(self, F, player): logger.error(f'GLWidget render callback exception: {str(ex)}') def timerCallback(self): + #self.repaint() self.update() def sizeHint(self): @@ -172,7 +174,8 @@ def paintGL(self): try: painter = QPainter(self) painter.setRenderHint(QPainter.RenderHint.Antialiasing) - + painter.fillRect(self.rect(), QColorConstants.Black) + if self.model_loading: rectSpinner = QRectF(0, 0, 40, 40) rectSpinner.moveCenter(QPointF(self.rect().center())) @@ -181,13 +184,18 @@ def paintGL(self): for player in self.mw.pm.players: - camera = self.mw.cameraPanel.getCamera(player.uri) + if player.isCameraStream() and player.running and player.last_render: + interval = datetime.now() - player.last_render + if interval.total_seconds() > 5: + logger.debug(f'Lost signal for {self.mw.getCameraName(player.uri)}') + player.requestShutdown() + self.mw.playMedia(player.uri) if player.pipe_output_start_time: interval = datetime.now() - player.pipe_output_start_time if interval.total_seconds() > self.mw.STD_FILE_DURATION: - d = self.mw.settingsPanel.dirArchive.txtDirectory.text() - if self.mw.settingsPanel.chkManageDiskUsage.isChecked(): + d = self.mw.settingsPanel.storage.dirArchive.txtDirectory.text() + if self.mw.settingsPanel.storage.chkManageDiskUsage.isChecked(): player.manageDirectory(d) filename = player.getPipeOutFilename(d) @@ -197,6 +205,8 @@ def paintGL(self): if player.disable_video or player.hidden: continue + camera = self.mw.cameraPanel.getCamera(player.uri) + if player.image is None: rect = self.mw.pm.displayRect(player.uri, self.size()) rectSpinner = QRectF(0, 0, 40, 40) @@ -215,7 +225,7 @@ def paintGL(self): continue while self.image_loading: - sleep(0.001) + time.sleep(0.001) player.rendering = True @@ -241,6 +251,9 @@ def paintGL(self): if camera.isAlarming(): painter.drawImage(rectBlinker, QImage("image:alarm_plain.png")) + if not player.isCameraStream() and player.alarm_state: + painter.drawImage(rectBlinker, QImage("image:alarm_plain.png")) + if self.isFocusedURI(player.uri): painter.setPen(QColorConstants.White) painter.drawRect(rect.adjusted(1, 1, -2, -2)) diff --git a/onvif-gui/gui/main.py b/onvif-gui/gui/main.py index fe0dfca..6c0d7d1 100644 --- a/onvif-gui/gui/main.py +++ b/onvif-gui/gui/main.py @@ -39,237 +39,19 @@ from pathlib import Path from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QSplitter, \ QTabWidget, QMessageBox -from PyQt6.QtCore import pyqtSignal, QObject, QSettings, QDir, QSize, QTimer, QFile, Qt +from PyQt6.QtCore import pyqtSignal, QObject, QSettings, QDir, QSize, QTimer, Qt from PyQt6.QtGui import QIcon -from gui.panels import CameraPanel, FilePanel, SettingsPanel, VideoPanel, AudioPanel +from gui.panels import CameraPanel, FilePanel, SettingsPanel, VideoPanel, \ + AudioPanel +from gui.enums import ProxyType from gui.glwidget import GLWidget from gui.manager import Manager +from gui.player import Player from gui.onvif import StreamState -from collections import deque -import shutil import avio +import liblivemedia -VERSION = "2.1.1" - -class PipeManager(): - def __init__(self, mw, uri): - self.mw = mw - self.uri = uri - -class PlayerSignals(QObject): - start = pyqtSignal() - stop = pyqtSignal() - -class Player(avio.Player): - def __init__(self, uri, mw): - super().__init__(uri) - self.mw = mw - self.signals = PlayerSignals() - self.image = None - self.rendering = False - self.desired_aspect = 0 - self.systemTabSettings = None - self.analyze_video = False - self.analyze_audio = False - self.videoModelSettings = None - self.audioModelSettings = None - self.detection_count = deque() - self.last_image = None - self.zombie_counter = 0 - - self.boxes = [] - self.labels = [] - self.scores = [] - - self.save_image_filename = None - self.pipe_output_start_time = None - self.estimated_file_size = 0 - self.packet_drop_frame_counter = 0 - - self.timer = QTimer() - self.timer.setInterval(self.mw.settingsPanel.spnLagTime.value() * 1000) - self.timer.setSingleShot(True) - self.timer.timeout.connect(self.timeout) - self.signals.start.connect(self.timer.start) - self.signals.stop.connect(self.timer.stop) - - self.alarm_state = 0 - self.last_alarm_state = 0 - self.file_progress = 0.0 - - def requestShutdown(self): - self.setAlarmState(0) - self.analyze_video = False - self.analyze_audio = False - self.request_reconnect = False - self.running = False - - def setAlarmState(self, state): - self.alarm_state = int(state) - - record_enable = self.systemTabSettings.record_enable if self.systemTabSettings else False - record_alarm = self.systemTabSettings.record_alarm if self.systemTabSettings else False - camera = self.mw.cameraPanel.getCamera(self.uri) - manual_recording = camera.manual_recording if camera else False - profile = camera.getRecordProfile() if camera else None - player = self.mw.pm.getPlayer(profile.uri()) if profile else None - - if state: - self.signals.start.emit() - if record_enable and record_alarm: - if player: - if not player.isRecording(): - d = self.mw.settingsPanel.dirArchive.txtDirectory.text() - if self.mw.settingsPanel.chkManageDiskUsage.isChecked(): - player.manageDirectory(d) - filename = player.getPipeOutFilename(d) - if filename: - player.toggleRecording(filename) - self.mw.cameraPanel.syncGUI() - else: - self.signals.stop.emit() - if record_alarm and not manual_recording: - if player: - if player.isRecording(): - player.toggleRecording("") - self.mw.cameraPanel.syncGUI() - - def timeout(self): - self.setAlarmState(0) - - def getPipeOutFilename(self, d): - filename = None - camera = self.mw.cameraPanel.getCamera(self.uri) - if camera: - d = self.mw.settingsPanel.dirArchive.txtDirectory.text() - root = d + "/" + camera.text() - Path(root).mkdir(parents=True, exist_ok=True) - self.pipe_output_start_time = datetime.now() - filename = '{0:%Y%m%d%H%M%S}'.format(self.pipe_output_start_time) - filename = root + "/" + filename + ".mp4" - self.setMetaData("title", camera.text()) - return filename - - def estimateFileSize(self): - # duration is in seconds, cameras report bitrate in kbps, result in bytes - result = 0 - bitrate = 0 - profile = self.mw.cameraPanel.getProfile(self.uri) - if profile: - audio_bitrate = min(profile.audio_bitrate(), 128) - video_bitrate = min(profile.bitrate(), 16384) - bitrate = video_bitrate + audio_bitrate - result = (bitrate * 1000 / 8) * self.mw.STD_FILE_DURATION - self.estimated_file_size = result - return result - - def getCommittedSize(self): - committed = 0 - for player in self.mw.pm.players: - if player.isRecording(): - committed += player.estimateFileSize() - player.pipeBytesWritten() - return committed - - def getDirectorySize(self, d): - total_size = 0 - for dirpath, dirnames, filenames in os.walk(d): - for f in filenames: - fp = os.path.join(dirpath, f) - if not os.path.islink(fp): - try: - total_size += os.path.getsize(fp) - except FileNotFoundError: - pass - - dir_size = "{:.2f}".format(total_size / 1000000000) - self.mw.settingsPanel.grpDiskUsage.setTitle(f'Disk Usage (currently {dir_size} GB)') - return total_size - - def getOldestFile(self, d): - oldest_file = None - oldest_time = None - for dirpath, dirnames, filenames in os.walk(d): - for f in filenames: - fp = os.path.join(dirpath, f) - if not os.path.islink(fp): - - stem = Path(fp).stem - if len(stem) == 14 and stem.isnumeric(): - try: - if oldest_file is None: - oldest_file = fp - oldest_time = os.path.getmtime(fp) - else: - file_time = os.path.getmtime(fp) - if file_time < oldest_time: - oldest_file = fp - oldest_time = file_time - except FileNotFoundError: - pass - return oldest_file - - def getMaximumDirectorySize(self, d): - estimated_file_size = self.estimateFileSize() - space_committed = self.getCommittedSize() - allowed_space = min(self.mw.settingsPanel.spnDiskLimit.value() * 1000000000, shutil.disk_usage(d)[2]) - return allowed_space - (space_committed + estimated_file_size) - - def manageDirectory(self, d): - while self.getDirectorySize(d) > self.getMaximumDirectorySize(d): - oldest_file = self.getOldestFile(d) - if oldest_file: - QFile.remove(oldest_file) - #logger.debug(f'File has been deleted by auto process: {oldest_file}') - else: - logger.debug("Unable to find the oldest file for deletion during disk management") - break - - def handleAlarm(self, state): - if self.analyze_video or self.analyze_audio: - if state: - self.setAlarmState(1) - if self.alarm_state: - if self.isCameraStream(): - if self.systemTabSettings.sound_alarm_enable: - filename = f'{self.mw.getLocation()}/gui/resources/{self.mw.settingsPanel.cmbSoundFiles.currentText()}' - if self.systemTabSettings.sound_alarm_once: - if self.alarm_state != self.last_alarm_state: - self.mw.playMedia(filename, True) - if self.systemTabSettings.sound_alarm_loop: - p = self.mw.pm.getPlayer(filename) - if not p: - self.mw.playMedia(filename, True) - self.last_alarm_state = self.alarm_state - else: - self.setAlarmState(0) - - def getFrameRate(self): - frame_rate = self.getVideoFrameRate() - if frame_rate <= 0: - profile = self.mw.cameraPanel.getProfile(self.uri) - if profile: - frame_rate = profile.frame_rate() - return frame_rate - - def processModelOutput(self): - - while self.rendering: - sleep(0.001) - - sum = 0 - - if len(self.detection_count) > self.videoModelSettings.sampleSize - 1 and len(self.detection_count): - self.detection_count.popleft() - - if len(self.boxes): - self.detection_count.append(1) - else: - self.detection_count.append(0) - - for count in self.detection_count: - sum += count - - return sum +VERSION = "2.2.4" class TimerSignals(QObject): timeoutPlayer = pyqtSignal(str) @@ -346,11 +128,14 @@ def __init__(self, clear_settings=False): self.splitKey = "MainWindow/split" self.collapsedKey = "MainWindow/collapsed" + self.signals = MainWindowSignals() + self.pm = Manager(self) self.timers = {} self.audioPlayer = None - self.signals = MainWindowSignals() + self.proxies = {} + self.proxy = None self.settingsPanel = SettingsPanel(self) self.signals.started.connect(self.settingsPanel.onMediaStarted) @@ -370,7 +155,7 @@ def __init__(self, clear_settings=False): self.signals.error.connect(self.onError) self.signals.reconnect.connect(self.startReconnectTimer) self.signals.stopReconnect.connect(self.stopReconnectTimer) - + self.tab = QTabWidget() self.tab.addTab(self.cameraPanel, "Cameras") self.tab.addTab(self.filePanel, "Files") @@ -396,7 +181,7 @@ def __init__(self, clear_settings=False): self.setGeometry(rect) self.discoverTimer = None - if self.settingsPanel.chkAutoDiscover.isChecked(): + if self.settingsPanel.discover.chkAutoDiscover.isChecked(): self.cameraPanel.btnDiscoverClicked() self.videoWorkerHook = None @@ -495,16 +280,12 @@ def playMedia(self, uri, alarm_sound=False): logger.debug(f'Attempt to create player with null uri') return - if existing := self.pm.getPlayer(uri): - logger.debug(f'Duplicate media uri from {self.getCameraName(uri)}') - existing_terminated = False - if not existing.running: - existing.zombie_counter += 1 - if existing.zombie_counter > 60: - logger.debug(f'Removing zombie player {self.getCameraName(uri)}') - self.pm.removePlayer(uri) - existing_terminated = True - if not existing_terminated: + count = 0 + while self.pm.getPlayer(uri) is not None: + sleep(0.01) + count += 1 + if count > 300: + logger.debug(f'Duplicate media uri from {self.getCameraName(uri)} is blocking launch of new player') return player = Player(uri, self) @@ -519,15 +300,16 @@ def playMedia(self, uri, alarm_sound=False): player.infoCallback = lambda msg, uri : self.infoCallback(msg, uri) player.getAudioStatus = lambda : self.getAudioStatus() player.setAudioStatus = lambda status : self.setAudioStatus(status) - player.hw_device_type = self.settingsPanel.getDecoder() + player.hw_device_type = self.settingsPanel.general.getDecoder() + player.audio_driver_index = self.settingsPanel.general.cmbAudioDriver.currentIndex() if player.isCameraStream(): if profile := self.cameraPanel.getProfile(uri): - player.vpq_size = self.settingsPanel.spnCacheMax.value() - player.apq_size = self.settingsPanel.spnCacheMax.value() + player.vpq_size = self.settingsPanel.general.spnCacheMax.value() + player.apq_size = self.settingsPanel.general.spnCacheMax.value() if profile.audio_encoding() == "AAC" and profile.audio_sample_rate() and profile.frame_rate(): player.apq_size = int(player.vpq_size * profile.audio_sample_rate() / profile.frame_rate()) - player.buffer_size_in_seconds = self.settings.value(self.settingsPanel.bufferSizeKey, 10) + player.buffer_size_in_seconds = self.settings.value(self.settingsPanel.alarm.bufferSizeKey, 10) player.onvif_frame_rate.num = profile.frame_rate() player.onvif_frame_rate.den = 1 player.disable_audio = profile.getDisableAudio() @@ -548,7 +330,7 @@ def playMedia(self, uri, alarm_sound=False): if alarm_sound: player.disable_video = True player.setMute(False) - player.setVolume(self.settingsPanel.sldAlarmVolume.value()) + player.setVolume(self.settingsPanel.alarm.sldAlarmVolume.value()) player.analyze_audio = False else: player.setVolume(self.filePanel.getVolume()) @@ -583,30 +365,56 @@ def showEvent(self, event): if not splitterState: self.splitterMoved(0, 0) - if self.settingsPanel.chkStartFullScreen.isChecked(): + if self.settingsPanel.general.chkStartFullScreen.isChecked(): self.showFullScreen() super().showEvent(event) - def closeEvent(self, event): + def startAllCameras(self): + try: + lstCamera = self.cameraPanel.lstCamera + if lstCamera: + cameras = [lstCamera.item(x) for x in range(lstCamera.count())] + for camera in cameras: + self.cameraPanel.setCurrentCamera(camera.uri()) + self.cameraPanel.onItemDoubleClicked(camera) + except Exception as ex: + logger.error(f'start all cameras error {ex}') + + def closeAllStreams(self): try: - self.cameraPanel.closeEvent() for player in self.pm.players: player.requestShutdown() for timer in self.timers.values(): timer.stop() + self.pm.auto_start_mode = False + lstCamera = self.cameraPanel.lstCamera + if lstCamera: + cameras = [lstCamera.item(x) for x in range(lstCamera.count())] + for camera in cameras: + camera.setIconIdle() count = 0 while len(self.pm.players): sleep(0.1) count += 1 - if count > 200: + if count > 20: logger.debug("not all players closed within the allotted time, flushing player manager") self.pm.players.clear() break self.pm.ordinals.clear() self.pm.sizes.clear() + self.cameraPanel.syncGUI() + if self.settingsPanel: + if self.settingsPanel.general: + self.settingsPanel.general.btnCloseAll.setText("Start All Cameras") + except Exception as ex: + logger.error(f'close all streams error {ex}') + + def closeEvent(self, event): + try: + self.closeAllStreams() self.settings.setValue(self.geometryKey, self.geometry()) super().closeEvent(event) @@ -615,7 +423,7 @@ def closeEvent(self, event): def mediaPlayingStarted(self, uri): if self.isCameraStreamURI(uri): - logger.debug(f'camera stream opened {self.getCameraName(uri)}') + logger.debug(f'camera stream opened {self.getCameraName(uri)} : {uri}') if self.pm.auto_start_mode: finished = True @@ -641,8 +449,8 @@ def mediaPlayingStarted(self, uri): if camera.profiles[camera.displayProfileIndex()].uri() == uri: record = True if record: - d = self.settingsPanel.dirArchive.txtDirectory.text() - if self.settingsPanel.chkManageDiskUsage.isChecked(): + d = self.settingsPanel.storage.dirArchive.txtDirectory.text() + if self.settingsPanel.storage.chkManageDiskUsage.isChecked(): player.manageDirectory(d) filename = player.getPipeOutFilename(d) if filename: @@ -702,9 +510,14 @@ def infoCallback(self, msg, uri): else: name = f'File: {uri}' - if msg.startswith("Output file creation failure:") or msg.startswith("Record to file close error:"): + if msg.startswith("Output file creation failure") or \ + msg.startswith("Record to file close error") or \ + msg.startswith("SDL_OpenAudioDevice exception"): logger.error(f'{name}, Message: {msg}') + if msg.startswith("Using SDL audio driver"): + logger.debug(msg) + else: print(f'{name}, Message: {msg}') @@ -728,25 +541,36 @@ def errorCallback(self, msg, uri, reconnect): logger.debug(f'Error from camera: {camera_name} : {msg}, attempting to re-connect') else: name = "" + last_msg = "" if self.isCameraStreamURI(uri): - player = self.pm.getPlayer(uri) - self.pm.removePlayer(uri) - self.pm.removeKeys(uri) + if player := self.pm.getPlayer(uri): + player.requestShutdown() + last_msg = player.last_msg + player.last_msg = msg + + if camera := self.cameraPanel.getCamera(uri): + if c_uri := camera.companionURI(uri): + if c_player := self.pm.getPlayer(c_uri): + c_player.requestShutdown() - camera = self.cameraPanel.getCamera(uri) - if camera: name = f'Camera: {self.getCameraName(uri)}' camera.setIconIdle() self.cameraPanel.syncGUI() self.cameraPanel.setTabsEnabled(True) - self.signals.error.emit(msg) + + if msg != last_msg: + self.signals.error.emit(msg) + else: - name = f'File: {uri}' - self.pm.removePlayer(uri) - self.pm.removeKeys(uri) - self.filePanel.control.btnPlay.setStyleSheet(self.filePanel.control.getButtonStyle("play")) - self.signals.error.emit(msg) + self.closeAllStreams() + #sleep(0.5) + #self.filePanel.control.btnPlay.setStyleSheet(self.filePanel.control.getButtonStyle("play")) + #sleep(0.5) + #self.signals.error.emit(msg) + #sleep(0.5) + #self.pm.removePlayer(uri) + logger.error(f'{name}, Error: {msg}') def mediaProgress(self, pct, uri): @@ -852,6 +676,39 @@ def getLogFilename(self): log_dir += os.path.sep + "logs" + os.path.sep + "onvif-gui" + os.path.sep + datestamp return log_dir + os.path.sep + source + "_" + timestamp + ".csv" + def startProxyServer(self): + try: + self.proxy = liblivemedia.ProxyServer() + self.proxy.init(554) + self.proxy.startLoop() + + except Exception as ex: + logger.error(f'Error starting proxy server {str(ex)}') + + def stopProxyServer(self): + if self.proxy: + self.proxy.stopLoop() + + def getProxyURI(self, arg): + match self.settingsPanel.proxy.proxyType: + case ProxyType.CLIENT: + return self.proxies[arg] + case ProxyType.SERVER: + return self.proxy.getProxyURI(arg) + + def addCameraProxy(self, camera): + match self.settingsPanel.proxy.proxyType: + case ProxyType.SERVER: + for profile in camera.profiles: + key = f'{camera.serial_number()}/{profile.profile()}' + existing_uri = self.proxy.getProxyURI(profile.stream_uri()) + if not len(existing_uri): + self.proxy.addURI(profile.stream_uri(), key, camera.onvif_data.username(), camera.onvif_data.password()) + case ProxyType.CLIENT: + for profile in camera.profiles: + server = self.settingsPanel.proxy.txtRemote.text() + self.proxies[profile.stream_uri()] = f'{server}{camera.serial_number()}/{profile.profile()}' + def style(self): blDefault = "#5B5B5B" bmDefault = "#4B4B4B" diff --git a/onvif-gui/gui/manager.py b/onvif-gui/gui/manager.py index bb49c15..b2f936a 100644 --- a/onvif-gui/gui/manager.py +++ b/onvif-gui/gui/manager.py @@ -1,281 +1,281 @@ -#/******************************************************************** -# libonvif/onvif-gui/gui/manager.py -# -# Copyright (c) 2024 Stephen Rhodes -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#*********************************************************************/ - -from time import sleep -from loguru import logger -from PyQt6.QtCore import QRectF, QSize, Qt, QSizeF, QPointF - -class Manager(): - def __init__(self, mw): - self.players = [] - self.ordinals = {} - self.sizes = {} - self.start_lock = False - self.remove_lock = False - self.mw = mw - self.auto_start_mode = False - - def startPlayer(self, player): - while self.start_lock: - sleep(0.001) - self.start_lock = True - - if not player.disable_video: - if not player.uri in self.ordinals.keys(): - ordinal = self.getOrdinal() - if player.isCameraStream(): - camera = self.mw.cameraPanel.getCamera(player.uri) - if camera: - if self.auto_start_mode and camera.ordinal > -1: - duplicate = False - keys = self.ordinals.keys() - for key in keys: - c = self.mw.cameraPanel.getCamera(key) - if camera.ordinal == self.ordinals[key] and camera.serial_number() != c.serial_number(): - duplicate = True - if duplicate: - camera.ordinal = ordinal - else: - ordinal = camera.ordinal - else: - comp_uri = camera.companionURI(player.uri) - if comp_uri: - if comp_uri in self.ordinals.keys(): - ordinal = self.ordinals[comp_uri] - camera.setOrdinal(ordinal) - - self.ordinals[player.uri] = ordinal - - self.players.append(player) - player.start() - self.start_lock = False - - def getUniqueOrdinals(self): - result = [] - values = self.ordinals.values() - for value in values: - if value not in result: - result.append(value) - return result - - def getOrdinal(self): - ordinal = -1 - - values = self.getUniqueOrdinals() - for i in range(len(values)): - if not i in values: - ordinal = i - break - - if ordinal == -1: - ordinal = len(values) - - return ordinal - - def getPlayer(self, uri): - result = None - if uri: - for player in self.players: - if player.uri == uri: - result = player - break - return result - - def getPlayerByOrdinal(self, ordinal): - result = None - for key, value in self.ordinals.items(): - if value == ordinal: - result = self.getPlayer(key) - break - return result - - def getCurrentPlayer(self): - return self.getPlayer(self.mw.glWidget.focused_uri) - - def getStreamPairProfiles(self, uri): - result = [] - if uri: - camera = self.mw.cameraPanel.getCamera(uri) - if camera: - displayProfile = camera.getDisplayProfile() - if displayProfile: - result.append(displayProfile) - if camera.displayProfileIndex() != camera.recordProfileIndex(): - recordProfile = camera.getRecordProfile() - if recordProfile: - result.append(recordProfile) - - return result - - def getStreamPairURIs(self, uri): - result = [] - profiles = self.getStreamPairProfiles(uri) - for profile in profiles: - result.append(profile.uri()) - return result - - def getStreamPairPlayers(self, uri): - result = [] - profiles = self.getStreamPairProfiles(uri) - for profile in profiles: - player = self.getPlayer(profile.uri()) - if player: - result.append(player) - return result - - def getStreamPairTimers(self, uri): - result = [] - profiles = self.getStreamPairProfiles(uri) - for profile in profiles: - timer = self.mw.timers.get(profile.uri(), None) - if timer: - result.append(timer) - return result - - def removeKeys(self, uri): - if uri in self.ordinals.keys(): - del self.ordinals[uri] - if uri in self.sizes.keys(): - del self.sizes[uri] - - def removePlayer(self, uri): - for player in self.players: - if player.uri == uri: - while player.rendering: - sleep(0.001) - - if not player.request_reconnect: - self.removeKeys(uri) - - self.players.remove(player) - - def playerShutdownWait(self, uri): - player = self.getPlayer(uri) - if player: - player.requestShutdown() - count = 0 - while self.getPlayer(uri): - sleep(0.01) - count += 1 - if count > 200: - logger.error(f'Player did not complete shut down during allocated time interval: {uri}') - break - - def getMostCommonAspectRatio(self): - ratio_counter = {} - for size in self.sizes.values(): - ratio = round(1000 * size.width() / size.height()) - if not ratio in ratio_counter.keys(): - ratio_counter[ratio] = 1 - else: - ratio_counter[ratio] += 1 - - highest_count_key = -1 - if ratio_counter: - keys = list(ratio_counter.keys()) - if len(keys): - highest_count_key = keys[0] - highest_count = ratio_counter[highest_count_key] - for key in keys: - if ratio_counter[key] > highest_count: - highest_count = ratio_counter[key] - highest_count_key = key - - return highest_count_key - - def computeRowsCols(self, size_canvas, aspect_ratio): - - num_cells = len(self.getUniqueOrdinals()) - - if self.auto_start_mode: - num_cells = len(self.mw.cameraPanel.cached_serial_numbers) - - valid_layouts = [] - for i in range(1, num_cells+1): - for j in range(num_cells, 0, -1): - if ((i * j) >= num_cells): - if (((i-1)*j) < num_cells) and ((i*(j-1)) < num_cells): - valid_layouts.append(QSize(i, j)) - - index = -1 - min_ratio = 0 - first_pass = True - for i, layout in enumerate(valid_layouts): - composite = (aspect_ratio * layout.height()) / layout.width() - ratio = (size_canvas.width() / size_canvas.height()) / composite - optimize = abs(1 - ratio) - if first_pass: - first_pass = False - min_ratio = optimize - index = i - else: - if optimize < min_ratio: - min_ratio = optimize - index = i - - if index == -1: - return 0, 0 - - return valid_layouts[index].width(), valid_layouts[index].height() - - def displayRect(self, uri, canvas_size): - ar = self.getMostCommonAspectRatio() - num_rows, num_cols = self.computeRowsCols(canvas_size, ar / 1000) - if num_cols == 0: - return QRectF(QPointF(0, 0), QSizeF(canvas_size)) - - ordinal = -1 - if uri in self.ordinals.keys(): - ordinal = self.ordinals[uri] - else: - return QRectF(QPointF(0, 0), QSizeF(canvas_size)) - - if ordinal > num_rows * num_cols - 1: - ordinal = self.getOrdinal() - uris = self.getStreamPairURIs(uri) - for u in uris: - if u in self.ordinals.keys(): - self.ordinals[u] = ordinal - - col = ordinal % num_cols - row = int(ordinal / num_cols) - - composite_size = QSizeF() - if num_rows: - composite_size = QSizeF(num_cols * ar / 1000, num_rows) - composite_size.scale(QSizeF(canvas_size), Qt.AspectRatioMode.KeepAspectRatio) - - cell_width = composite_size.width() / num_cols - cell_height = composite_size.height() / num_rows - - image_size = QSizeF(ar, 1000) - if uri in self.sizes.keys(): - image_size = QSizeF(self.sizes[uri]) - - image_size.scale(cell_width, cell_height, Qt.AspectRatioMode.KeepAspectRatio) - w = image_size.width() - h = image_size.height() - - x_offset = (canvas_size.width() - composite_size.width() + (cell_width - w)) / 2 - y_offset = (canvas_size.height() - composite_size.height() + (cell_height - h)) / 2 - - x = (col * cell_width) + x_offset - y = (row * cell_height) + y_offset - - return QRectF(x, y, w, h) +#/******************************************************************** +# libonvif/onvif-gui/gui/manager.py +# +# Copyright (c) 2024 Stephen Rhodes +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#*********************************************************************/ + +from time import sleep +from loguru import logger +from PyQt6.QtCore import QRectF, QSize, Qt, QSizeF, QPointF + +class Manager(): + def __init__(self, mw): + self.players = [] + self.ordinals = {} + self.sizes = {} + self.start_lock = False + self.remove_lock = False + self.mw = mw + self.auto_start_mode = False + + def startPlayer(self, player): + while self.start_lock: + sleep(0.001) + self.start_lock = True + + if not player.disable_video: + if not player.uri in self.ordinals.keys(): + ordinal = self.getOrdinal() + if player.isCameraStream(): + camera = self.mw.cameraPanel.getCamera(player.uri) + if camera: + if self.auto_start_mode and camera.ordinal > -1: + duplicate = False + keys = self.ordinals.keys() + for key in keys: + c = self.mw.cameraPanel.getCamera(key) + if camera.ordinal == self.ordinals[key] and camera.serial_number() != c.serial_number(): + duplicate = True + if duplicate: + camera.ordinal = ordinal + else: + ordinal = camera.ordinal + else: + comp_uri = camera.companionURI(player.uri) + if comp_uri: + if comp_uri in self.ordinals.keys(): + ordinal = self.ordinals[comp_uri] + camera.setOrdinal(ordinal) + + self.ordinals[player.uri] = ordinal + + self.players.append(player) + player.start() + self.start_lock = False + + def getUniqueOrdinals(self): + result = [] + values = self.ordinals.values() + for value in values: + if value not in result: + result.append(value) + return result + + def getOrdinal(self): + ordinal = -1 + + values = self.getUniqueOrdinals() + for i in range(len(values)): + if not i in values: + ordinal = i + break + + if ordinal == -1: + ordinal = len(values) + + return ordinal + + def getPlayer(self, uri): + result = None + if uri: + for player in self.players: + if player.uri == uri: + result = player + break + return result + + def getPlayerByOrdinal(self, ordinal): + result = None + for key, value in self.ordinals.items(): + if value == ordinal: + result = self.getPlayer(key) + break + return result + + def getCurrentPlayer(self): + return self.getPlayer(self.mw.glWidget.focused_uri) + + def getStreamPairProfiles(self, uri): + result = [] + if uri: + camera = self.mw.cameraPanel.getCamera(uri) + if camera: + displayProfile = camera.getDisplayProfile() + if displayProfile: + result.append(displayProfile) + if camera.displayProfileIndex() != camera.recordProfileIndex(): + recordProfile = camera.getRecordProfile() + if recordProfile: + result.append(recordProfile) + + return result + + def getStreamPairURIs(self, uri): + result = [] + profiles = self.getStreamPairProfiles(uri) + for profile in profiles: + result.append(profile.uri()) + return result + + def getStreamPairPlayers(self, uri): + result = [] + profiles = self.getStreamPairProfiles(uri) + for profile in profiles: + player = self.getPlayer(profile.uri()) + if player: + result.append(player) + return result + + def getStreamPairTimers(self, uri): + result = [] + profiles = self.getStreamPairProfiles(uri) + for profile in profiles: + timer = self.mw.timers.get(profile.uri(), None) + if timer: + result.append(timer) + return result + + def removeKeys(self, uri): + if uri in self.ordinals.keys(): + del self.ordinals[uri] + if uri in self.sizes.keys(): + del self.sizes[uri] + + def removePlayer(self, uri): + for player in self.players: + if player.uri == uri: + while player.rendering: + sleep(0.001) + + if not player.request_reconnect: + self.removeKeys(uri) + + self.players.remove(player) + + def playerShutdownWait(self, uri): + player = self.getPlayer(uri) + if player: + player.requestShutdown() + count = 0 + while self.getPlayer(uri): + sleep(0.01) + count += 1 + if count > 200: + logger.error(f'Player did not complete shut down during allocated time interval: {uri}') + break + + def getMostCommonAspectRatio(self): + ratio_counter = {} + for size in self.sizes.values(): + ratio = round(1000 * size.width() / size.height()) + if not ratio in ratio_counter.keys(): + ratio_counter[ratio] = 1 + else: + ratio_counter[ratio] += 1 + + highest_count_key = -1 + if ratio_counter: + keys = list(ratio_counter.keys()) + if len(keys): + highest_count_key = keys[0] + highest_count = ratio_counter[highest_count_key] + for key in keys: + if ratio_counter[key] > highest_count: + highest_count = ratio_counter[key] + highest_count_key = key + + return highest_count_key + + def computeRowsCols(self, size_canvas, aspect_ratio): + + num_cells = len(self.getUniqueOrdinals()) + + if self.auto_start_mode: + num_cells = len(self.mw.cameraPanel.cached_serial_numbers) + + valid_layouts = [] + for i in range(1, num_cells+1): + for j in range(num_cells, 0, -1): + if ((i * j) >= num_cells): + if (((i-1)*j) < num_cells) and ((i*(j-1)) < num_cells): + valid_layouts.append(QSize(i, j)) + + index = -1 + min_ratio = 0 + first_pass = True + for i, layout in enumerate(valid_layouts): + composite = (aspect_ratio * layout.height()) / layout.width() + ratio = (size_canvas.width() / size_canvas.height()) / composite + optimize = abs(1 - ratio) + if first_pass: + first_pass = False + min_ratio = optimize + index = i + else: + if optimize < min_ratio: + min_ratio = optimize + index = i + + if index == -1: + return 0, 0 + + return valid_layouts[index].width(), valid_layouts[index].height() + + def displayRect(self, uri, canvas_size): + ar = self.getMostCommonAspectRatio() + num_rows, num_cols = self.computeRowsCols(canvas_size, ar / 1000) + if num_cols == 0: + return QRectF(QPointF(0, 0), QSizeF(canvas_size)) + + ordinal = -1 + if uri in self.ordinals.keys(): + ordinal = self.ordinals[uri] + else: + return QRectF(QPointF(0, 0), QSizeF(canvas_size)) + + if ordinal > num_rows * num_cols - 1: + ordinal = self.getOrdinal() + uris = self.getStreamPairURIs(uri) + for u in uris: + if u in self.ordinals.keys(): + self.ordinals[u] = ordinal + + col = ordinal % num_cols + row = int(ordinal / num_cols) + + composite_size = QSizeF() + if num_rows: + composite_size = QSizeF(num_cols * ar / 1000, num_rows) + composite_size.scale(QSizeF(canvas_size), Qt.AspectRatioMode.KeepAspectRatio) + + cell_width = composite_size.width() / num_cols + cell_height = composite_size.height() / num_rows + + image_size = QSizeF(ar, 1000) + if uri in self.sizes.keys(): + image_size = QSizeF(self.sizes[uri]) + + image_size.scale(cell_width, cell_height, Qt.AspectRatioMode.KeepAspectRatio) + w = image_size.width() + h = image_size.height() + + x_offset = (canvas_size.width() - composite_size.width() + (cell_width - w)) / 2 + y_offset = (canvas_size.height() - composite_size.height() + (cell_height - h)) / 2 + + x = (col * cell_width) + x_offset + y = (row * cell_height) + y_offset + + return QRectF(x, y, w, h) diff --git a/onvif-gui/gui/onvif/datastructures.py b/onvif-gui/gui/onvif/datastructures.py index 6d1d5b5..6147332 100644 --- a/onvif-gui/gui/onvif/datastructures.py +++ b/onvif-gui/gui/onvif/datastructures.py @@ -1,281 +1,286 @@ -#/******************************************************************** -# libonvif/onvif-gui/gui/onvif/datastructures.py -# -# Copyright (c) 2023 Stephen Rhodes -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#*********************************************************************/ - -from time import sleep -from enum import auto, Enum -from PyQt6.QtWidgets import QListWidgetItem -from PyQt6.QtCore import Qt, pyqtSignal, QObject, QTimer -from PyQt6.QtGui import QIcon, QColor -import libonvif as onvif -from gui.onvif.systemtab import SystemTabSettings - -class StreamState(Enum): - IDLE = auto() - CONNECTING = auto() - CONNECTED = auto() - INVALID = auto() - -class MediaSource(Enum): - CAMERA = auto() - FILE = auto() - -class SessionSignals(QObject): - finished = pyqtSignal() - -class Session(onvif.Session): - def __init__(self, cp, interface): - super().__init__() - self.cp = cp - self.interface = interface - self.signals = SessionSignals() - self.discovered = lambda : self.finish() - self.getCredential = lambda D : self.cp.getCredential(D) - self.getData = lambda D : self.cp.getData(D) - self.timer = QTimer() - self.timer.setSingleShot(True) - self.timer.timeout.connect(self.timeout) - self.signals.finished.connect(self.timer.stop) - self.active = False - - def start(self): - self.active = True - self.startDiscover() - self.timer.start(10000) - - def finish(self): - self.active = False - self.signals.finished.emit() - self.cp.discovered() - - def timeout(self): - self.cp.discoveryTimeout() - -class Camera(QListWidgetItem): - def __init__(self, onvif_data, mw): - super().__init__(onvif_data.alias) - self.onvif_data = onvif_data - self.mw = mw - self.icnIdle = QIcon("image:idle_lo.png") - self.icnOn = QIcon("image:record.png") - self.icnRecord = QIcon("image:recording_hi.png") - self.defaultForeground = self.foreground() - self.filled = False - self.last_msg = "" - - onvif_data.setSetting = self.setSetting - onvif_data.getSetting = self.getSetting - for profile in onvif_data.profiles: - profile.setSetting = self.setSetting - profile.getSetting = self.getSetting - self.profiles = onvif_data.profiles - - self.videoModelSettings = None - self.audioModelSettings = None - self.systemTabSettings = SystemTabSettings(self.mw, self) - self.manual_recording = False - self.ordinalKey = f'{self.serial_number()}/Ordinal' - self.ordinal = self.getOrdinal() - self.volumeKey = f'{self.serial_number()}/Volume' - self.volume = self.getVolume() - self.muteKey = f'{self.serial_number()}/Mute' - self.mute = self.getMute() - - def getSetting(self, key, default_value): - return str(self.mw.settings.value(key, default_value)) - - def setSetting(self, key, value): - self.mw.settings.setValue(key, value) - - def uri(self): - return self.onvif_data.uri() - - def serial_number(self): - return self.onvif_data.serial_number() - - def name(self): - return self.onvif_data.alias - - def xaddrs(self): - return self.onvif_data.xaddrs() - - def hasAudio(self): - return bool(self.onvif_data.audio_bitrate()) - - def setOrdinal(self, value): - self.ordinal = value - self.mw.settings.setValue(self.ordinalKey, value) - - def getOrdinal(self): - return int(self.mw.settings.value(self.ordinalKey, -1)) - - def getMute(self): - return bool(int(self.mw.settings.value(self.muteKey, 0))) - - def setMute(self, state): - self.mute = bool(state) - self.mw.settings.setValue(self.muteKey, int(state)) - - def getVolume(self): - return int(self.mw.settings.value(self.volumeKey, 80)) - - def setVolume(self, volume): - self.volume = volume - self.mw.settings.setValue(self.volumeKey, volume) - - def isRunning(self): - result = False - players = self.mw.pm.getStreamPairPlayers(self.uri()) - if len(players): - result = True - return result - - def isRecording(self): - result = False - players = self.mw.pm.getStreamPairPlayers(self.uri()) - for player in players: - if player.isRecording(): - result = True - return result - - def isAlarming(self): - result = False - players = self.mw.pm.getStreamPairPlayers(self.uri()) - for player in players: - if player.alarm_state: - result = True - return result - - def isFocus(self): - result = False - for profile in self.profiles: - if profile.uri() == self.mw.glWidget.focused_uri: - result = True - return result - - def editing(self): - return self.flags() & Qt.ItemFlag.ItemIsEditable - - def setIconIdle(self): - if not self.flags() & Qt.ItemFlag.ItemIsEditable: - self.setIcon(self.icnIdle) - - def setIconOn(self): - if not self.flags() & Qt.ItemFlag.ItemIsEditable: - self.setIcon(self.icnOn) - - def setIconRecord(self): - if not self.flags() & Qt.ItemFlag.ItemIsEditable: - self.setIcon(self.icnRecord) - - def dimForeground(self): - self.setForeground(QColor("#808D9E")) - - def restoreForeground(self): - self.setForeground(self.defaultForeground) - - def isCurrent(self): - result = False - current_camera = self.mw.cameraPanel.getCurrentCamera() - if current_camera: - if current_camera.serial_number() == self.serial_number(): - result = True - return result - - def getStreamState(self, index): - result = StreamState.INVALID - profile = self.profiles[index] - if profile: - player = self.mw.pm.getPlayer(profile.uri()) - if player: - if player.image: - result = StreamState.CONNECTED - else: - result = StreamState.CONNECTING - else: - result = StreamState.IDLE - - timer = self.mw.timers.get(profile.uri(), None) - if timer: - if timer.isActive(): - result = StreamState.CONNECTING - return result - - def profileName(self, uri): - result = "" - for profile in self.profiles: - if profile.uri() == uri: - result = profile.profile() - return result - - def recordProfileIndex(self): - return self.systemTabSettings.record_profile - - def displayProfileIndex(self): - return self.onvif_data.displayProfile - - def setDisplayProfile(self, index): - self.onvif_data.setProfile(index) - self.mw.settings.setValue(f'{self.serial_number()}/DisplayProfile', index) - for i, profile in enumerate(self.profiles): - if i == index: - profile.setHidden(False) - else: - profile.setHidden(True) - - def getDisplayProfileSetting(self): - return int(self.mw.settings.value(f'{self.serial_number()}/DisplayProfile', self.displayProfileIndex())) - - def getProfile(self, uri): - result = None - for profile in self.profiles: - if profile.uri() == uri: - result = profile - break - return result - - def getRecordProfile(self): - result = None - if len(self.profiles) > self.recordProfileIndex(): - result = self.profiles[self.recordProfileIndex()] - return result - - def isRecordProfile(self, uri): - result = False - recordProfile = self.getRecordProfile() - if recordProfile: - if uri == recordProfile.uri(): - result = True - return result - - def getDisplayProfile(self): - result = None - if len(self.profiles) > self.displayProfileIndex(): - result = self.profiles[self.displayProfileIndex()] - return result - - def companionURI(self, uri): - result = None - recordProfile = self.getRecordProfile() - displayProfile = self.getDisplayProfile() - if recordProfile and displayProfile: - if uri == recordProfile.uri(): - result = displayProfile.uri() - if uri == displayProfile.uri(): - result = recordProfile.uri() - return result +#/******************************************************************** +# libonvif/onvif-gui/gui/onvif/datastructures.py +# +# Copyright (c) 2023 Stephen Rhodes +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#*********************************************************************/ + +from time import sleep +from PyQt6.QtWidgets import QListWidgetItem +from PyQt6.QtCore import Qt, pyqtSignal, QObject, QTimer +from PyQt6.QtGui import QIcon, QColor +import libonvif as onvif +from gui.onvif.systemtab import SystemTabSettings +from gui.enums import ProxyType, MediaSource, StreamState + +class SessionSignals(QObject): + finished = pyqtSignal() + +class Session(onvif.Session): + def __init__(self, cp, interface): + super().__init__() + self.cp = cp + self.interface = interface + self.signals = SessionSignals() + self.discovered = lambda : self.finish() + self.getCredential = lambda D : self.cp.getCredential(D) + self.getData = lambda D : self.cp.getData(D) + self.timer = QTimer() + self.timer.setSingleShot(True) + self.timer.timeout.connect(self.timeout) + self.signals.finished.connect(self.timer.stop) + self.active = False + + def start(self): + self.active = True + self.startDiscover() + self.timer.start(10000) + + def finish(self): + self.active = False + self.signals.finished.emit() + self.cp.discovered() + + def timeout(self): + self.cp.discoveryTimeout() + +class Camera(QListWidgetItem): + def __init__(self, onvif_data, mw): + super().__init__(onvif_data.alias) + self.onvif_data = onvif_data + self.mw = mw + self.icnIdle = QIcon("image:idle_lo.png") + self.icnOn = QIcon("image:record.png") + self.icnRecord = QIcon("image:recording_hi.png") + self.defaultForeground = self.foreground() + self.filled = False + self.last_msg = "" + + onvif_data.setSetting = self.setSetting + onvif_data.getSetting = self.getSetting + if mw.settingsPanel.proxy.proxyType != ProxyType.STAND_ALONE: + onvif_data.getProxyURI = self.mw.getProxyURI + + for profile in onvif_data.profiles: + profile.setSetting = self.setSetting + profile.getSetting = self.getSetting + if mw.settingsPanel.proxy.proxyType != ProxyType.STAND_ALONE: + profile.getProxyURI = self.mw.getProxyURI + + self.profiles = onvif_data.profiles + + self.videoModelSettings = None + self.audioModelSettings = None + self.systemTabSettings = SystemTabSettings(self.mw, self) + self.manual_recording = False + self.ordinalKey = f'{self.serial_number()}/Ordinal' + self.ordinal = self.getOrdinal() + self.volumeKey = f'{self.serial_number()}/Volume' + self.volume = self.getVolume() + self.muteKey = f'{self.serial_number()}/Mute' + self.mute = self.getMute() + + ''' + def getProxyURI(self, arg): + match self.mw.settingsPanel.proxy.proxyType: + case ProxyType.CLIENT: + return self.mw.proxies[arg] + case ProxyType.SERVER: + return self.mw.proxy.getProxyURI(arg) + ''' + + def getSetting(self, key, default_value): + return str(self.mw.settings.value(key, default_value)) + + def setSetting(self, key, value): + self.mw.settings.setValue(key, value) + + def uri(self): + return self.onvif_data.uri() + + def serial_number(self): + return self.onvif_data.serial_number() + + def name(self): + return self.onvif_data.alias + + def xaddrs(self): + return self.onvif_data.xaddrs() + + def hasAudio(self): + return bool(self.onvif_data.audio_bitrate()) + + def setOrdinal(self, value): + self.ordinal = value + self.mw.settings.setValue(self.ordinalKey, value) + + def getOrdinal(self): + return int(self.mw.settings.value(self.ordinalKey, -1)) + + def getMute(self): + return bool(int(self.mw.settings.value(self.muteKey, 0))) + + def setMute(self, state): + self.mute = bool(state) + self.mw.settings.setValue(self.muteKey, int(state)) + + def getVolume(self): + return int(self.mw.settings.value(self.volumeKey, 80)) + + def setVolume(self, volume): + self.volume = volume + self.mw.settings.setValue(self.volumeKey, volume) + + def isRunning(self): + result = False + players = self.mw.pm.getStreamPairPlayers(self.uri()) + if len(players): + result = True + return result + + def isRecording(self): + result = False + players = self.mw.pm.getStreamPairPlayers(self.uri()) + for player in players: + if player.isRecording(): + result = True + return result + + def isAlarming(self): + result = False + players = self.mw.pm.getStreamPairPlayers(self.uri()) + for player in players: + if player.alarm_state: + result = True + return result + + def isFocus(self): + result = False + for profile in self.profiles: + if profile.uri() == self.mw.glWidget.focused_uri: + result = True + return result + + def editing(self): + return self.flags() & Qt.ItemFlag.ItemIsEditable + + def setIconIdle(self): + if not self.flags() & Qt.ItemFlag.ItemIsEditable: + self.setIcon(self.icnIdle) + + def setIconOn(self): + if not self.flags() & Qt.ItemFlag.ItemIsEditable: + self.setIcon(self.icnOn) + + def setIconRecord(self): + if not self.flags() & Qt.ItemFlag.ItemIsEditable: + self.setIcon(self.icnRecord) + + def dimForeground(self): + self.setForeground(QColor("#808D9E")) + + def restoreForeground(self): + self.setForeground(self.defaultForeground) + + def isCurrent(self): + result = False + current_camera = self.mw.cameraPanel.getCurrentCamera() + if current_camera: + if current_camera.serial_number() == self.serial_number(): + result = True + return result + + def getStreamState(self, index): + result = StreamState.INVALID + profile = self.profiles[index] + if profile: + player = self.mw.pm.getPlayer(profile.uri()) + if player: + if player.image: + result = StreamState.CONNECTED + else: + result = StreamState.CONNECTING + else: + result = StreamState.IDLE + + timer = self.mw.timers.get(profile.uri(), None) + if timer: + if timer.isActive(): + result = StreamState.CONNECTING + return result + + def profileName(self, uri): + result = "" + for profile in self.profiles: + if profile.uri() == uri: + result = profile.profile() + return result + + def recordProfileIndex(self): + return self.systemTabSettings.record_profile + + def displayProfileIndex(self): + return self.onvif_data.displayProfile + + def setDisplayProfile(self, index): + self.onvif_data.setProfile(index) + self.mw.settings.setValue(f'{self.serial_number()}/DisplayProfile', index) + for i, profile in enumerate(self.profiles): + if i == index: + profile.setHidden(False) + else: + profile.setHidden(True) + + def getDisplayProfileSetting(self): + return int(self.mw.settings.value(f'{self.serial_number()}/DisplayProfile', self.displayProfileIndex())) + + def getProfile(self, uri): + result = None + for profile in self.profiles: + if profile.uri() == uri: + result = profile + break + return result + + def getRecordProfile(self): + result = None + if len(self.profiles) > self.recordProfileIndex(): + result = self.profiles[self.recordProfileIndex()] + return result + + def isRecordProfile(self, uri): + result = False + recordProfile = self.getRecordProfile() + if recordProfile: + if uri == recordProfile.uri(): + result = True + return result + + def getDisplayProfile(self): + result = None + if len(self.profiles) > self.displayProfileIndex(): + result = self.profiles[self.displayProfileIndex()] + return result + + def companionURI(self, uri): + result = None + recordProfile = self.getRecordProfile() + displayProfile = self.getDisplayProfile() + if recordProfile and displayProfile: + if uri == recordProfile.uri(): + result = displayProfile.uri() + if uri == displayProfile.uri(): + result = recordProfile.uri() + return result diff --git a/onvif-gui/gui/onvif/systemtab.py b/onvif-gui/gui/onvif/systemtab.py index 25b64aa..b0ee753 100644 --- a/onvif-gui/gui/onvif/systemtab.py +++ b/onvif-gui/gui/onvif/systemtab.py @@ -1,306 +1,306 @@ -#/******************************************************************** -# libonvif/onvif-gui/gui/onvif/systemtab.py -# -# Copyright (c) 2023 Stephen Rhodes -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#*********************************************************************/ - -from PyQt6.QtWidgets import QGridLayout, QWidget, QPushButton, QGroupBox, \ - QMessageBox, QRadioButton, QComboBox, QLabel -from PyQt6.QtCore import Qt -import datetime -import pathlib -import webbrowser - -class SystemTabSettings(): - def __init__(self, mw, camera): - self.camera = camera - self.mw = mw - - self.record_enable = self.getRecordAlarmEnabled() - self.record_always = self.getRecordAlways() - self.record_alarm = self.getRecordOnAlarm() - self.sound_alarm_enable = self.getSoundAlarmEnabled() - self.sound_alarm_once = self.getSoundAlarmOnce() - self.sound_alarm_loop = self.getSoundAlarmLoop() - self.record_profile = self.getRecordProfile() - - def managePlayers(self): - record = False - if self.record_enable: - if self.record_always or (self.record_alarm and self.camera.isAlarming()): - record = True - if record: - profile = self.camera.getRecordProfile() - if profile: - player = self.mw.pm.getPlayer(profile.uri()) - if player: - if not player.isRecording(): - d = self.mw.settingsPanel.dirArchive.txtDirectory.text() - filename = player.getPipeOutFilename(d) - if filename: - player.toggleRecording(filename) - else: - players = self.mw.pm.getStreamPairPlayers(self.camera.uri()) - for player in players: - if player.isRecording(): - player.toggleRecording("") - - self.mw.cameraPanel.syncGUI() - - def getRecordProfile(self): - key = f'{self.camera.serial_number()}/RecordProfile' - return int(self.mw.settings.value(key, 0)) - - def setRecordProfile(self, ordinal): - self.record_profile = ordinal - key = f'{self.camera.serial_number()}/RecordProfile' - self.mw.settings.setValue(key, ordinal) - - def getRecordAlarmEnabled(self): - key = f'{self.camera.serial_number()}/RecordAlarmEnabled' - return bool(int(self.mw.settings.value(key, 0))) - - def setRecordAlarmEnabled(self, state): - self.record_enable = bool(state) - key = f'{self.camera.serial_number()}/RecordAlarmEnabled' - self.managePlayers() - self.mw.settings.setValue(key, int(state)) - - def getRecordAlways(self): - key = f'{self.camera.serial_number()}/RecordAlways' - return bool(int(self.mw.settings.value(key, 0))) - - def setRecordAlways(self, state): - self.record_always = bool(state) - key = f'{self.camera.serial_number()}/RecordAlways' - self.mw.settings.setValue(key, int(state)) - self.managePlayers() - - def getRecordOnAlarm(self): - key = f'{self.camera.serial_number()}/RecordOnAlarm' - return bool(int(self.mw.settings.value(key, 1))) - - def setRecordOnAlarm(self, state): - self.record_alarm = bool(state) - key = f'{self.camera.serial_number()}/RecordOnAlarm' - self.mw.settings.setValue(key, int(state)) - - def getSoundAlarmEnabled(self): - key = f'{self.camera.serial_number()}/SoundAlarmEnabled' - return bool(int(self.mw.settings.value(key, 0))) - - def setSoundAlarmEnabled(self, state): - self.sound_alarm_enable = bool(state) - key = f'{self.camera.serial_number()}/SoundAlarmEnabled' - self.mw.settings.setValue(key, int(state)) - - def getSoundAlarmOnce(self): - key = f'{self.camera.serial_number()}/SoundAlarmOnce' - return bool(int(self.mw.settings.value(key, 0))) - - def setSoundAlarmOnce(self, state): - self.sound_alarm_once = bool(state) - key = f'{self.camera.serial_number()}/SoundAlarmOnce' - self.mw.settings.setValue(key, int(state)) - - def getSoundAlarmLoop(self): - key = f'{self.camera.serial_number()}/SoundAlarmLoop' - return bool(int(self.mw.settings.value(key, 1))) - - def setSoundAlarmLoop(self, state): - self.sound_alarm_loop = bool(state) - key = f'{self.camera.serial_number()}/SoundAlarmLoop' - self.mw.settings.setValue(key, int(state)) - -class SystemTab(QWidget): - def __init__(self, cp): - super().__init__() - self.cp = cp - - self.radRecordAlways = QRadioButton("Always") - self.radRecordAlways.clicked.connect(self.radRecordAlwaysClicked) - self.radRecordAlways.setFocusPolicy(Qt.FocusPolicy.NoFocus) - self.radRecordOnAlarm = QRadioButton("Alarms") - self.radRecordOnAlarm.clicked.connect(self.radRecordOnAlarmClicked) - self.radRecordOnAlarm.setFocusPolicy(Qt.FocusPolicy.NoFocus) - self.grpRecord = QGroupBox("Record") - self.grpRecord.setCheckable(True) - self.grpRecord.clicked.connect(self.grpRecordClicked) - self.grpRecord.setFocusPolicy(Qt.FocusPolicy.NoFocus) - lytGroup = QGridLayout(self.grpRecord) - lytGroup.addWidget(self.radRecordAlways, 0, 0, 1, 1) - lytGroup.addWidget(self.radRecordOnAlarm, 1, 0, 1, 1) - - self.radSoundOnce = QRadioButton("Once") - self.radSoundOnce.clicked.connect(self.radSoundOnceClicked) - self.radSoundOnce.setFocusPolicy(Qt.FocusPolicy.NoFocus) - self.radSoundLoop = QRadioButton("Loop") - self.radSoundLoop.clicked.connect(self.radSoundLoopClicked) - self.radSoundLoop.setFocusPolicy(Qt.FocusPolicy.NoFocus) - self.grpSounds = QGroupBox("Sounds") - self.grpSounds.setCheckable(True) - self.grpSounds.clicked.connect(self.grpSoundsClicked) - self.grpSounds.setFocusPolicy(Qt.FocusPolicy.NoFocus) - lytGroupSounds = QGridLayout(self.grpSounds) - lytGroupSounds.addWidget(self.radSoundOnce, 0, 0, 1, 1) - lytGroupSounds.addWidget(self.radSoundLoop, 1, 0, 1, 1) - - self.cmbRecordProfile = QComboBox() - self.cmbRecordProfile.setFocusPolicy(Qt.FocusPolicy.NoFocus) - self.cmbRecordProfile.currentIndexChanged.connect(self.cmbRecordProfileChanged) - self.lblRecordProfile = QLabel(" Record ") - pnlRecordProfile = QWidget() - lytRecordProfile = QGridLayout(pnlRecordProfile) - lytRecordProfile.addWidget(self.lblRecordProfile, 0, 0, 1, 1) - lytRecordProfile.addWidget(self.cmbRecordProfile, 0, 1, 1, 1) - lytRecordProfile.setColumnStretch(1, 5) - lytRecordProfile.setContentsMargins(0, 0, 0, 0) - - self.btnReboot = QPushButton("Reboot") - self.btnReboot.clicked.connect(self.btnRebootClicked) - self.btnReboot.setFocusPolicy(Qt.FocusPolicy.NoFocus) - self.btnSyncTime = QPushButton("Sync Time") - self.btnSyncTime.clicked.connect(self.btnSyncTimeClicked) - self.btnSyncTime.setFocusPolicy(Qt.FocusPolicy.NoFocus) - self.btnBrowser = QPushButton("Browser") - self.btnBrowser.clicked.connect(self.btnBrowserClicked) - self.btnBrowser.setFocusPolicy(Qt.FocusPolicy.NoFocus) - - self.btnSnapshot = QPushButton("JPEG") - self.btnSnapshot.clicked.connect(self.btnSnapshotClicked) - self.btnSnapshot.setFocusPolicy(Qt.FocusPolicy.NoFocus) - - pnlButton = QWidget() - lytButton = QGridLayout(pnlButton) - lytButton.addWidget(self.btnReboot, 0, 0, 1, 1) - lytButton.addWidget(self.btnSyncTime, 1, 0, 1, 1) - lytButton.addWidget(self.btnBrowser, 2, 0, 1, 1) - lytButton.addWidget(self.btnSnapshot, 3, 0, 1, 1) - lytButton.setContentsMargins(6, 0, 6, 0) - - lytMain = QGridLayout(self) - lytMain.addWidget(self.grpRecord, 0, 0, 1, 1) - lytMain.addWidget(self.grpSounds, 0, 1, 1, 1) - lytMain.addWidget(pnlRecordProfile, 1, 0, 1, 2) - lytMain.addWidget(pnlButton, 0, 2, 2, 1) - - def grpRecordClicked(self, state): - camera = self.cp.getCurrentCamera() - if camera: - camera.systemTabSettings.setRecordAlarmEnabled(state) - - def radRecordAlwaysClicked(self, state): - camera = self.cp.getCurrentCamera() - if camera: - camera.systemTabSettings.setRecordAlways(state) - camera.systemTabSettings.setRecordOnAlarm(not state) - - def radRecordOnAlarmClicked(self, state): - camera = self.cp.getCurrentCamera() - if camera: - camera.systemTabSettings.setRecordOnAlarm(state) - camera.systemTabSettings.setRecordAlways(not state) - - def grpSoundsClicked(self, state): - camera = self.cp.getCurrentCamera() - if camera: - camera.systemTabSettings.setSoundAlarmEnabled(state) - - def radSoundOnceClicked(self, state): - camera = self.cp.getCurrentCamera() - if camera: - camera.systemTabSettings.setSoundAlarmOnce(state) - camera.systemTabSettings.setSoundAlarmLoop(not state) - - def radSoundLoopClicked(self, state): - camera = self.cp.getCurrentCamera() - if camera: - camera.systemTabSettings.setSoundAlarmLoop(state) - camera.systemTabSettings.setSoundAlarmOnce(not state) - - def cmbRecordProfileChanged(self, index): - camera = self.cp.getCurrentCamera() - if camera: - players = self.cp.mw.pm.getStreamPairPlayers(camera.uri()) - camera.systemTabSettings.setRecordProfile(index) - if len(players): - for player in players: - self.cp.mw.pm.playerShutdownWait(player.uri) - self.cp.onItemDoubleClicked(camera) - - def fill(self, onvif_data): - self.cmbRecordProfile.disconnect() - self.cmbRecordProfile.clear() - for profile in onvif_data.profiles: - self.cmbRecordProfile.addItem(profile.profile()) - - camera = self.cp.getCurrentCamera() - if camera: - self.cmbRecordProfile.setCurrentIndex(camera.systemTabSettings.getRecordProfile()) - - self.cmbRecordProfile.currentIndexChanged.connect(self.cmbRecordProfileChanged) - self.syncGUI() - self.setEnabled(True) - - def syncGUI(self): - camera = self.cp.getCurrentCamera() - if camera: - self.grpRecord.setChecked(camera.systemTabSettings.record_enable) - if camera.systemTabSettings.record_always: - self.radRecordAlways.setChecked(True) - self.radRecordOnAlarm.setChecked(False) - if camera.systemTabSettings.record_alarm: - self.radRecordOnAlarm.setChecked(True) - self.radRecordAlways.setChecked(False) - self.grpSounds.setChecked(camera.systemTabSettings.sound_alarm_enable) - if camera.systemTabSettings.sound_alarm_once: - self.radSoundOnce.setChecked(True) - self.radSoundLoop.setChecked(False) - if camera.systemTabSettings.sound_alarm_loop: - self.radSoundLoop.setChecked(True) - self.radSoundOnce.setChecked(False) - - self.cp.btnRecord.setEnabled(not (self.grpRecord.isChecked() and self.radRecordAlways.isChecked())) - if camera.isRecording(): - self.cp.btnRecord.setStyleSheet(self.cp.getButtonStyle("recording")) - else: - self.cp.btnRecord.setStyleSheet(self.cp.getButtonStyle("record")) - - def btnRebootClicked(self): - camera = self.cp.getCurrentCamera() - if camera: - result = QMessageBox.question(self, "Warning", f'{camera.name()}: Please confirm reboot') - if result == QMessageBox.StandardButton.Yes: - camera.onvif_data.startReboot() - - def btnSyncTimeClicked(self): - camera = self.cp.getCurrentCamera() - if camera: - camera.onvif_data.startUpdateTime() - - def btnBrowserClicked(self): - camera = self.cp.lstCamera.currentItem() - if camera: - host = "http://" + camera.onvif_data.host() - webbrowser.get().open(host) - - def btnSnapshotClicked(self): - if player := self.cp.getCurrentPlayer(): - root = self.cp.mw.settingsPanel.dirPictures.txtDirectory.text() + "/" + self.cp.getCamera(player.uri).text() - pathlib.Path(root).mkdir(parents=True, exist_ok=True) - filename = '{0:%Y%m%d%H%M%S.jpg}'.format(datetime.datetime.now()) - filename = root + "/" + filename - player.save_image_filename = filename +#/******************************************************************** +# libonvif/onvif-gui/gui/onvif/systemtab.py +# +# Copyright (c) 2023 Stephen Rhodes +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#*********************************************************************/ + +from PyQt6.QtWidgets import QGridLayout, QWidget, QPushButton, QGroupBox, \ + QMessageBox, QRadioButton, QComboBox, QLabel +from PyQt6.QtCore import Qt +import datetime +import pathlib +import webbrowser + +class SystemTabSettings(): + def __init__(self, mw, camera): + self.camera = camera + self.mw = mw + + self.record_enable = self.getRecordAlarmEnabled() + self.record_always = self.getRecordAlways() + self.record_alarm = self.getRecordOnAlarm() + self.sound_alarm_enable = self.getSoundAlarmEnabled() + self.sound_alarm_once = self.getSoundAlarmOnce() + self.sound_alarm_loop = self.getSoundAlarmLoop() + self.record_profile = self.getRecordProfile() + + def managePlayers(self): + record = False + if self.record_enable: + if self.record_always or (self.record_alarm and self.camera.isAlarming()): + record = True + if record: + profile = self.camera.getRecordProfile() + if profile: + player = self.mw.pm.getPlayer(profile.uri()) + if player: + if not player.isRecording(): + d = self.mw.settingsPanel.storage.dirArchive.txtDirectory.text() + filename = player.getPipeOutFilename(d) + if filename: + player.toggleRecording(filename) + else: + players = self.mw.pm.getStreamPairPlayers(self.camera.uri()) + for player in players: + if player.isRecording(): + player.toggleRecording("") + + self.mw.cameraPanel.syncGUI() + + def getRecordProfile(self): + key = f'{self.camera.serial_number()}/RecordProfile' + return int(self.mw.settings.value(key, 0)) + + def setRecordProfile(self, ordinal): + self.record_profile = ordinal + key = f'{self.camera.serial_number()}/RecordProfile' + self.mw.settings.setValue(key, ordinal) + + def getRecordAlarmEnabled(self): + key = f'{self.camera.serial_number()}/RecordAlarmEnabled' + return bool(int(self.mw.settings.value(key, 0))) + + def setRecordAlarmEnabled(self, state): + self.record_enable = bool(state) + key = f'{self.camera.serial_number()}/RecordAlarmEnabled' + self.managePlayers() + self.mw.settings.setValue(key, int(state)) + + def getRecordAlways(self): + key = f'{self.camera.serial_number()}/RecordAlways' + return bool(int(self.mw.settings.value(key, 0))) + + def setRecordAlways(self, state): + self.record_always = bool(state) + key = f'{self.camera.serial_number()}/RecordAlways' + self.mw.settings.setValue(key, int(state)) + self.managePlayers() + + def getRecordOnAlarm(self): + key = f'{self.camera.serial_number()}/RecordOnAlarm' + return bool(int(self.mw.settings.value(key, 1))) + + def setRecordOnAlarm(self, state): + self.record_alarm = bool(state) + key = f'{self.camera.serial_number()}/RecordOnAlarm' + self.mw.settings.setValue(key, int(state)) + + def getSoundAlarmEnabled(self): + key = f'{self.camera.serial_number()}/SoundAlarmEnabled' + return bool(int(self.mw.settings.value(key, 0))) + + def setSoundAlarmEnabled(self, state): + self.sound_alarm_enable = bool(state) + key = f'{self.camera.serial_number()}/SoundAlarmEnabled' + self.mw.settings.setValue(key, int(state)) + + def getSoundAlarmOnce(self): + key = f'{self.camera.serial_number()}/SoundAlarmOnce' + return bool(int(self.mw.settings.value(key, 0))) + + def setSoundAlarmOnce(self, state): + self.sound_alarm_once = bool(state) + key = f'{self.camera.serial_number()}/SoundAlarmOnce' + self.mw.settings.setValue(key, int(state)) + + def getSoundAlarmLoop(self): + key = f'{self.camera.serial_number()}/SoundAlarmLoop' + return bool(int(self.mw.settings.value(key, 1))) + + def setSoundAlarmLoop(self, state): + self.sound_alarm_loop = bool(state) + key = f'{self.camera.serial_number()}/SoundAlarmLoop' + self.mw.settings.setValue(key, int(state)) + +class SystemTab(QWidget): + def __init__(self, cp): + super().__init__() + self.cp = cp + + self.radRecordAlways = QRadioButton("Always") + self.radRecordAlways.clicked.connect(self.radRecordAlwaysClicked) + self.radRecordAlways.setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.radRecordOnAlarm = QRadioButton("Alarms") + self.radRecordOnAlarm.clicked.connect(self.radRecordOnAlarmClicked) + self.radRecordOnAlarm.setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.grpRecord = QGroupBox("Record") + self.grpRecord.setCheckable(True) + self.grpRecord.clicked.connect(self.grpRecordClicked) + self.grpRecord.setFocusPolicy(Qt.FocusPolicy.NoFocus) + lytGroup = QGridLayout(self.grpRecord) + lytGroup.addWidget(self.radRecordAlways, 0, 0, 1, 1) + lytGroup.addWidget(self.radRecordOnAlarm, 1, 0, 1, 1) + + self.radSoundOnce = QRadioButton("Once") + self.radSoundOnce.clicked.connect(self.radSoundOnceClicked) + self.radSoundOnce.setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.radSoundLoop = QRadioButton("Loop") + self.radSoundLoop.clicked.connect(self.radSoundLoopClicked) + self.radSoundLoop.setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.grpSounds = QGroupBox("Sounds") + self.grpSounds.setCheckable(True) + self.grpSounds.clicked.connect(self.grpSoundsClicked) + self.grpSounds.setFocusPolicy(Qt.FocusPolicy.NoFocus) + lytGroupSounds = QGridLayout(self.grpSounds) + lytGroupSounds.addWidget(self.radSoundOnce, 0, 0, 1, 1) + lytGroupSounds.addWidget(self.radSoundLoop, 1, 0, 1, 1) + + self.cmbRecordProfile = QComboBox() + self.cmbRecordProfile.setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.cmbRecordProfile.currentIndexChanged.connect(self.cmbRecordProfileChanged) + self.lblRecordProfile = QLabel(" Record ") + pnlRecordProfile = QWidget() + lytRecordProfile = QGridLayout(pnlRecordProfile) + lytRecordProfile.addWidget(self.lblRecordProfile, 0, 0, 1, 1) + lytRecordProfile.addWidget(self.cmbRecordProfile, 0, 1, 1, 1) + lytRecordProfile.setColumnStretch(1, 5) + lytRecordProfile.setContentsMargins(0, 0, 0, 0) + + self.btnReboot = QPushButton("Reboot") + self.btnReboot.clicked.connect(self.btnRebootClicked) + self.btnReboot.setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.btnSyncTime = QPushButton("Sync Time") + self.btnSyncTime.clicked.connect(self.btnSyncTimeClicked) + self.btnSyncTime.setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.btnBrowser = QPushButton("Browser") + self.btnBrowser.clicked.connect(self.btnBrowserClicked) + self.btnBrowser.setFocusPolicy(Qt.FocusPolicy.NoFocus) + + self.btnSnapshot = QPushButton("JPEG") + self.btnSnapshot.clicked.connect(self.btnSnapshotClicked) + self.btnSnapshot.setFocusPolicy(Qt.FocusPolicy.NoFocus) + + pnlButton = QWidget() + lytButton = QGridLayout(pnlButton) + lytButton.addWidget(self.btnReboot, 0, 0, 1, 1) + lytButton.addWidget(self.btnSyncTime, 1, 0, 1, 1) + lytButton.addWidget(self.btnBrowser, 2, 0, 1, 1) + lytButton.addWidget(self.btnSnapshot, 3, 0, 1, 1) + lytButton.setContentsMargins(6, 0, 6, 0) + + lytMain = QGridLayout(self) + lytMain.addWidget(self.grpRecord, 0, 0, 1, 1) + lytMain.addWidget(self.grpSounds, 0, 1, 1, 1) + lytMain.addWidget(pnlRecordProfile, 1, 0, 1, 2) + lytMain.addWidget(pnlButton, 0, 2, 2, 1) + + def grpRecordClicked(self, state): + camera = self.cp.getCurrentCamera() + if camera: + camera.systemTabSettings.setRecordAlarmEnabled(state) + + def radRecordAlwaysClicked(self, state): + camera = self.cp.getCurrentCamera() + if camera: + camera.systemTabSettings.setRecordAlways(state) + camera.systemTabSettings.setRecordOnAlarm(not state) + + def radRecordOnAlarmClicked(self, state): + camera = self.cp.getCurrentCamera() + if camera: + camera.systemTabSettings.setRecordOnAlarm(state) + camera.systemTabSettings.setRecordAlways(not state) + + def grpSoundsClicked(self, state): + camera = self.cp.getCurrentCamera() + if camera: + camera.systemTabSettings.setSoundAlarmEnabled(state) + + def radSoundOnceClicked(self, state): + camera = self.cp.getCurrentCamera() + if camera: + camera.systemTabSettings.setSoundAlarmOnce(state) + camera.systemTabSettings.setSoundAlarmLoop(not state) + + def radSoundLoopClicked(self, state): + camera = self.cp.getCurrentCamera() + if camera: + camera.systemTabSettings.setSoundAlarmLoop(state) + camera.systemTabSettings.setSoundAlarmOnce(not state) + + def cmbRecordProfileChanged(self, index): + camera = self.cp.getCurrentCamera() + if camera: + players = self.cp.mw.pm.getStreamPairPlayers(camera.uri()) + camera.systemTabSettings.setRecordProfile(index) + if len(players): + for player in players: + self.cp.mw.pm.playerShutdownWait(player.uri) + self.cp.onItemDoubleClicked(camera) + + def fill(self, onvif_data): + self.cmbRecordProfile.disconnect() + self.cmbRecordProfile.clear() + for profile in onvif_data.profiles: + self.cmbRecordProfile.addItem(profile.profile()) + + camera = self.cp.getCurrentCamera() + if camera: + self.cmbRecordProfile.setCurrentIndex(camera.systemTabSettings.getRecordProfile()) + + self.cmbRecordProfile.currentIndexChanged.connect(self.cmbRecordProfileChanged) + self.syncGUI() + self.setEnabled(True) + + def syncGUI(self): + camera = self.cp.getCurrentCamera() + if camera: + self.grpRecord.setChecked(camera.systemTabSettings.record_enable) + if camera.systemTabSettings.record_always: + self.radRecordAlways.setChecked(True) + self.radRecordOnAlarm.setChecked(False) + if camera.systemTabSettings.record_alarm: + self.radRecordOnAlarm.setChecked(True) + self.radRecordAlways.setChecked(False) + self.grpSounds.setChecked(camera.systemTabSettings.sound_alarm_enable) + if camera.systemTabSettings.sound_alarm_once: + self.radSoundOnce.setChecked(True) + self.radSoundLoop.setChecked(False) + if camera.systemTabSettings.sound_alarm_loop: + self.radSoundLoop.setChecked(True) + self.radSoundOnce.setChecked(False) + + self.cp.btnRecord.setEnabled(not (self.grpRecord.isChecked() and self.radRecordAlways.isChecked())) + if camera.isRecording(): + self.cp.btnRecord.setStyleSheet(self.cp.getButtonStyle("recording")) + else: + self.cp.btnRecord.setStyleSheet(self.cp.getButtonStyle("record")) + + def btnRebootClicked(self): + camera = self.cp.getCurrentCamera() + if camera: + result = QMessageBox.question(self, "Warning", f'{camera.name()}: Please confirm reboot') + if result == QMessageBox.StandardButton.Yes: + camera.onvif_data.startReboot() + + def btnSyncTimeClicked(self): + camera = self.cp.getCurrentCamera() + if camera: + camera.onvif_data.startUpdateTime() + + def btnBrowserClicked(self): + camera = self.cp.lstCamera.currentItem() + if camera: + host = "http://" + camera.onvif_data.host() + webbrowser.get().open(host) + + def btnSnapshotClicked(self): + if player := self.cp.getCurrentPlayer(): + root = self.cp.mw.settingsPanel.storage.dirPictures.txtDirectory.text() + "/" + self.cp.getCamera(player.uri).text() + pathlib.Path(root).mkdir(parents=True, exist_ok=True) + filename = '{0:%Y%m%d%H%M%S.jpg}'.format(datetime.datetime.now()) + filename = root + "/" + filename + player.save_image_filename = filename diff --git a/onvif-gui/gui/panels/__init__.py b/onvif-gui/gui/panels/__init__.py index 605f0a7..552e38d 100644 --- a/onvif-gui/gui/panels/__init__.py +++ b/onvif-gui/gui/panels/__init__.py @@ -2,4 +2,4 @@ from .filepanel import FilePanel from .videopanel import VideoPanel from .settingspanel import SettingsPanel -from .audiopanel import AudioPanel \ No newline at end of file +from .audiopanel import AudioPanel diff --git a/onvif-gui/gui/panels/audiopanel.py b/onvif-gui/gui/panels/audiopanel.py index a1224d0..d658de9 100644 --- a/onvif-gui/gui/panels/audiopanel.py +++ b/onvif-gui/gui/panels/audiopanel.py @@ -20,7 +20,7 @@ from PyQt6.QtWidgets import QGridLayout, QWidget, QCheckBox, \ QLabel, QComboBox, QVBoxLayout from PyQt6.QtCore import Qt -from gui.onvif.datastructures import MediaSource +from gui.enums import MediaSource class AudioPanel(QWidget): def __init__(self, mw): diff --git a/onvif-gui/gui/panels/camerapanel.py b/onvif-gui/gui/panels/camerapanel.py index 2cf30db..2c9b90f 100644 --- a/onvif-gui/gui/panels/camerapanel.py +++ b/onvif-gui/gui/panels/camerapanel.py @@ -141,10 +141,11 @@ def __init__(self, mw): self.dlgLogin = LoginDialog(self) self.fillers = [] self.fill_first_pass = True + self.sync_lock = False self.cameras_awaiting_authentication = [] self.autoTimeSyncer = None - self.enableAutoTimeSync(self.mw.settingsPanel.chkAutoTimeSync.isChecked()) + self.enableAutoTimeSync(self.mw.settingsPanel.general.chkAutoTimeSync.isChecked()) self.cached_serial_numbers = [] @@ -162,23 +163,33 @@ def __init__(self, mw): self.sldVolume.setEnabled(False) self.btnStop = QPushButton() + self.btnStop.setMinimumWidth(40) + self.btnStop.setMaximumHeight(20) self.btnStop.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.btnStop.clicked.connect(self.btnStopClicked) self.btnRecord = QPushButton() + self.btnRecord.setMinimumWidth(40) + self.btnRecord.setMaximumHeight(20) self.btnRecord.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.btnRecord.clicked.connect(self.btnRecordClicked) self.btnMute = QPushButton() + self.btnMute.setMinimumWidth(40) + self.btnMute.setMaximumHeight(20) self.btnMute.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.btnMute.clicked.connect(self.btnMuteClicked) self.btnDiscover = QPushButton() + self.btnDiscover.setMinimumWidth(40) + self.btnDiscover.setMaximumHeight(20) self.btnDiscover.setStyleSheet(self.getButtonStyle("discover")) self.btnDiscover.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.btnDiscover.clicked.connect(self.btnDiscoverClicked) self.btnApply = QPushButton() + self.btnApply.setMinimumWidth(40) + self.btnApply.setMaximumHeight(20) self.btnApply.setStyleSheet(self.getButtonStyle("apply")) self.btnApply.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.btnApply.clicked.connect(self.btnApplyClicked) @@ -250,16 +261,16 @@ def onMenuInfo(self): self.lstCamera.info() def btnDiscoverClicked(self): - if self.mw.settingsPanel.radDiscover.isChecked(): + if self.mw.settingsPanel.discover.radDiscover.isChecked(): logger.debug("Using broadcast discovery") interfaces = [] self.sessions.clear() - if self.mw.settingsPanel.chkScanAllNetworks.isChecked(): - for i in range(self.mw.settingsPanel.cmbInterfaces.count()): - interfaces.append(self.mw.settingsPanel.cmbInterfaces.itemText(i).split(" - ")[0]) + if self.mw.settingsPanel.discover.chkScanAllNetworks.isChecked(): + for i in range(self.mw.settingsPanel.discover.cmbInterfaces.count()): + interfaces.append(self.mw.settingsPanel.discover.cmbInterfaces.itemText(i).split(" - ")[0]) else: - interfaces.append(self.mw.settingsPanel.cmbInterfaces.currentText().split(" - ")[0]) + interfaces.append(self.mw.settingsPanel.discover.cmbInterfaces.currentText().split(" - ")[0]) for interface in interfaces: session = Session(self, interface) @@ -271,7 +282,7 @@ def btnDiscoverClicked(self): else: logger.debug("Using cached camera addresses for discovery") self.fillers.clear() - tmp = self.mw.settings.value(self.mw.settingsPanel.cameraListKey) + tmp = self.mw.settings.value(self.mw.settingsPanel.discover.cameraListKey) if tmp: numbers = tmp.strip().split("\n") for serial_number in numbers: @@ -312,9 +323,9 @@ def getCredential(self, onvif_data): onvif_data.cancelled = True return onvif_data - if len(self.mw.settingsPanel.txtPassword.text()) > 0 and len(onvif_data.last_error()) == 0: - onvif_data.setUsername(self.mw.settingsPanel.txtUsername.text()) - onvif_data.setPassword(self.mw.settingsPanel.txtPassword.text()) + if len(self.mw.settingsPanel.general.txtPassword.text()) > 0 and len(onvif_data.last_error()) == 0: + onvif_data.setUsername(self.mw.settingsPanel.general.txtUsername.text()) + onvif_data.setPassword(self.mw.settingsPanel.general.txtPassword.text()) else: if onvif_data.last_error().startswith("Network error, unable to connect"): logger.debug(f'Unable to connect with {onvif_data.xaddrs()}') @@ -365,13 +376,15 @@ def getData(self, onvif_data): camera = Camera(onvif_data, self.mw) camera.setIconIdle() camera.dimForeground() + self.mw.addCameraProxy(camera) + self.lstCamera.addItem(camera) self.lstCamera.sortItems() camera.setDisplayProfile(camera.getDisplayProfileSetting()) self.saveCameraList() logger.debug(f'Discovery completed for Camera: {onvif_data.alias}, Serial Number: {onvif_data.serial_number()}, Stream URI: {onvif_data.stream_uri()}, xaddrs: {onvif_data.xaddrs()}') - synchronizeTime = self.mw.settingsPanel.chkAutoTimeSync.isChecked() + synchronizeTime = self.mw.settingsPanel.general.chkAutoTimeSync.isChecked() if not self.closing: onvif_data.startFill(synchronizeTime) @@ -395,12 +408,12 @@ def filled(self, onvif_data): camera.filled = True # auto start after fill, recording needs onvif frame rate - if self.mw.settingsPanel.chkAutoStart.isChecked(): + if self.mw.settingsPanel.discover.chkAutoStart.isChecked(): if self.fill_first_pass: self.fill_first_pass = False if bool(int(self.mw.settings.value(self.mw.collapsedKey, 0))): self.signals.collapseSplitter.emit() - if self.mw.settingsPanel.radCached.isChecked(): + if self.mw.settingsPanel.discover.radCached.isChecked(): self.mw.pm.auto_start_mode = True if not camera.isRunning(): @@ -417,7 +430,7 @@ def saveCameraList(self): cameras = [self.mw.cameraPanel.lstCamera.item(x) for x in range(self.mw.cameraPanel.lstCamera.count())] for camera in cameras: serial_numbers += camera.serial_number() + "\n" - self.mw.settings.setValue(self.mw.settingsPanel.cameraListKey, serial_numbers) + self.mw.settings.setValue(self.mw.settingsPanel.discover.cameraListKey, serial_numbers) def onCurrentItemChanged(self, current, previous): if current: @@ -450,8 +463,8 @@ def onItemDoubleClicked(self, camera): self.mw.signals.stopReconnect.emit(timer.uri) for player in players: player.requestShutdown() - for profile in profiles: - self.mw.pm.removeKeys(profile.uri()) + #for profile in profiles: + # self.mw.pm.removeKeys(profile.uri()) camera.setIconIdle() else: if len(players): @@ -534,14 +547,14 @@ def btnRecordClicked(self): if camera: camera.manual_recording = False else: - d = self.mw.settingsPanel.dirArchive.txtDirectory.text() + d = self.mw.settingsPanel.storage.dirArchive.txtDirectory.text() root = d + "/" + self.getCamera(player.uri).text() pathlib.Path(root).mkdir(parents=True, exist_ok=True) player.pipe_output_start_time = datetime.now() filename = '{0:%Y%m%d%H%M%S}'.format(player.pipe_output_start_time) filename = root + "/" + filename + ".mp4" player.setMetaData("title", self.getCamera(player.uri).text()) - if self.mw.settingsPanel.chkManageDiskUsage.isChecked(): + if self.mw.settingsPanel.storage.chkManageDiskUsage.isChecked(): player.manageDirectory(d) player.toggleRecording(filename) if camera: @@ -577,13 +590,18 @@ def onMediaStopped(self, uri): self.syncGUI() def syncGUI(self): + + while (self.sync_lock): + sleep(0.001) + + self.sync_lock = True if camera := self.getCurrentCamera(): self.btnStop.setEnabled(True) if player := self.mw.pm.getPlayer(camera.uri()): self.btnStop.setStyleSheet(self.getButtonStyle("stop")) - ps = player.systemTabSettings - self.btnRecord.setEnabled(not (ps.record_enable and ps.record_always)) + if ps := player.systemTabSettings: + self.btnRecord.setEnabled(not (ps.record_enable and ps.record_always)) if player.hasAudio() and not player.disable_audio: self.btnMute.setEnabled(True) @@ -644,6 +662,11 @@ def syncGUI(self): self.btnStop.setStyleSheet(self.getButtonStyle("play")) self.btnStop.setEnabled(False) + #print("btn width", self.btnStop.width()) + #print("btn height", self.btnStop.height()) + self.sync_lock = False + + def getButtonStyle(self, name): strStyle = "QPushButton { image : url(image:%1.png); } \ QPushButton:hover { image : url(image:%1_hi.png); } \ diff --git a/onvif-gui/gui/panels/options/__init__.py b/onvif-gui/gui/panels/options/__init__.py new file mode 100644 index 0000000..9dff794 --- /dev/null +++ b/onvif-gui/gui/panels/options/__init__.py @@ -0,0 +1,5 @@ +from .discover import DiscoverOptions +from .general import GeneralOptions +from .storage import StorageOptions +from .alarm import AlarmOptions +from .proxy import ProxyOptions \ No newline at end of file diff --git a/onvif-gui/gui/panels/options/alarm.py b/onvif-gui/gui/panels/options/alarm.py new file mode 100644 index 0000000..3bf41c7 --- /dev/null +++ b/onvif-gui/gui/panels/options/alarm.py @@ -0,0 +1,94 @@ +#/******************************************************************** +# libonvif/onvif-gui/gui/panels/options/alarm.py +# +# Copyright (c) 2024 Stephen Rhodes +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#*********************************************************************/ + +import os +from PyQt6.QtWidgets import QSpinBox, QGridLayout, QWidget, \ + QLabel, QComboBox, QSlider +from PyQt6.QtCore import Qt + +class AlarmOptions(QWidget): + def __init__(self, mw): + super().__init__() + self.mw = mw + + self.bufferSizeKey = "settings/bufferSize" + self.lagTimeKey = "settings/lagTime" + self.alarmSoundFileKey = "settings/alarmSoundFile" + self.alarmSoundVolumeKey = "settings/alarmSoundVolume" + + self.spnBufferSize = QSpinBox() + self.spnBufferSize.setMinimum(1) + self.spnBufferSize.setMaximum(60) + self.spnBufferSize.setMaximumWidth(80) + self.spnBufferSize.setValue(int(self.mw.settings.value(self.bufferSizeKey, 10))) + self.spnBufferSize.valueChanged.connect(self.spnBufferSizeChanged) + lblBufferSize = QLabel("Pre-Alarm Buffer Size (in seconds)") + + self.spnLagTime = QSpinBox() + self.spnLagTime.setMinimum(1) + self.spnLagTime.setMaximum(60) + self.spnLagTime.setMaximumWidth(80) + self.spnLagTime.setValue(int(self.mw.settings.value(self.lagTimeKey, 5))) + self.spnLagTime.valueChanged.connect(self.spnLagTimeChanged) + lblLagTime = QLabel("Post-Alarm Lag Time (in seconds)") + + self.cmbSoundFiles = QComboBox() + d = f'{self.mw.getLocation()}/gui/resources' + sounds = [f for f in os.listdir(d) if os.path.isfile(os.path.join(d, f)) and f.endswith(".mp3")] + self.cmbSoundFiles.addItems(sounds) + self.cmbSoundFiles.currentTextChanged.connect(self.cmbSoundFilesChanged) + self.cmbSoundFiles.setCurrentText(self.mw.settings.value(self.alarmSoundFileKey, "drops.mp3")) + lblSoundFiles = QLabel("Alarm Sounds") + self.sldAlarmVolume = QSlider(Qt.Orientation.Horizontal) + self.sldAlarmVolume.setValue(int(self.mw.settings.value(self.alarmSoundVolumeKey, 80))) + self.sldAlarmVolume.valueChanged.connect(self.sldAlarmVolumeChanged) + + pnlSoundFile = QWidget() + lytSoundFile = QGridLayout(pnlSoundFile) + lytSoundFile.addWidget(lblSoundFiles, 0, 0, 1, 1) + lytSoundFile.addWidget(self.cmbSoundFiles, 0, 1, 1, 1) + lytSoundFile.addWidget(self.sldAlarmVolume, 0, 2, 1, 1) + lytSoundFile.setColumnStretch(1, 10) + + pnlBuffer = QWidget() + lytBuffer = QGridLayout(pnlBuffer) + lytBuffer.addWidget(lblBufferSize, 1, 0, 1, 3) + lytBuffer.addWidget(self.spnBufferSize, 1, 3, 1, 1) + lytBuffer.addWidget(lblLagTime, 2, 0, 1, 3) + lytBuffer.addWidget(self.spnLagTime, 2, 3, 1, 1) + lytBuffer.addWidget(pnlSoundFile, 3, 0, 1, 4) + lytBuffer.setContentsMargins(0, 0, 0, 0) + + lytMain = QGridLayout(self) + lytMain.addWidget(pnlBuffer, 0, 0, 1, 1) + lytMain.addWidget(QLabel(), 1, 0, 1, 1) + lytMain.setRowStretch(1, 10) + + def spnBufferSizeChanged(self, i): + self.mw.settings.setValue(self.bufferSizeKey, i) + + def spnLagTimeChanged(self, i): + self.mw.settings.setValue(self.lagTimeKey, i) + + def cmbSoundFilesChanged(self, value): + self.mw.settings.setValue(self.alarmSoundFileKey, value) + + def sldAlarmVolumeChanged(self, value): + self.mw.settings.setValue(self.alarmSoundVolumeKey, value) + diff --git a/onvif-gui/gui/panels/options/discover.py b/onvif-gui/gui/panels/options/discover.py new file mode 100644 index 0000000..c81a7c4 --- /dev/null +++ b/onvif-gui/gui/panels/options/discover.py @@ -0,0 +1,171 @@ +#/******************************************************************** +# libonvif/onvif-gui/gui/panels/options/discover.py +# +# Copyright (c) 2024 Stephen Rhodes +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#*********************************************************************/ + +from PyQt6.QtWidgets import QLineEdit, QGridLayout, QWidget, QCheckBox, \ + QLabel, QComboBox, QPushButton, QDialog, QDialogButtonBox, \ + QRadioButton, QGroupBox +from PyQt6.QtCore import Qt, QRegularExpression +from PyQt6.QtGui import QRegularExpressionValidator +from loguru import logger +import libonvif as onvif + +class AddCameraDialog(QDialog): + def __init__(self, mw): + super().__init__(mw) + self.mw = mw + + ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])" + ipRegex = QRegularExpression("^" + ipRange + "\\." + ipRange + "\\." + ipRange + "\\." + ipRange + "$") + ipValidator = QRegularExpressionValidator(ipRegex, self) + + self.setWindowTitle("Add Camera") + self.txtIPAddress = QLineEdit() + self.txtIPAddress.setValidator(ipValidator) + self.lblIPAddress = QLabel("IP Address") + self.txtOnvifPort = QLineEdit() + self.lblOnvifPort = QLabel("Onvif Port") + + buttonBox = QDialogButtonBox( \ + QDialogButtonBox.StandardButton.Ok | \ + QDialogButtonBox.StandardButton.Cancel) + + lytMain = QGridLayout(self) + lytMain.addWidget(self.lblIPAddress, 1, 0, 1, 1) + lytMain.addWidget(self.txtIPAddress, 1, 1, 1, 1) + lytMain.addWidget(self.lblOnvifPort, 2, 0, 1, 1) + lytMain.addWidget(self.txtOnvifPort, 2, 1, 1, 1) + lytMain.addWidget(buttonBox, 5, 0, 1, 2) + + buttonBox.accepted.connect(self.accept) + buttonBox.rejected.connect(self.reject) + + self.txtIPAddress.setFocus() + +class DiscoverOptions(QWidget): + def __init__(self, mw): + super().__init__() + self.mw = mw + + self.interfaceKey = "settings/interface" + self.autoDiscoverKey = "settings/autoDiscover" + self.discoveryTypeKey = "settings/discoveryType" + self.autoStartKey = "settings/autoStart" + self.scanAllKey = "settings/scanAll" + self.cameraListKey = "settings/cameraList" + + + self.grpDiscoverType = QGroupBox("Set Camera Discovery Method") + self.radDiscover = QRadioButton("Discover Broadcast", self.grpDiscoverType ) + self.radDiscover.setChecked(int(self.mw.settings.value(self.discoveryTypeKey, 1))) + self.radDiscover.toggled.connect(self.radDiscoverToggled) + self.radCached = QRadioButton("Cached Addresses", self.grpDiscoverType ) + self.radCached.setChecked(not self.radDiscover.isChecked()) + lytDiscoverType = QGridLayout(self.grpDiscoverType ) + lytDiscoverType.addWidget(self.radDiscover, 0, 0, 1, 1) + lytDiscoverType.addWidget(self.radCached, 0, 1, 1, 1) + + self.chkScanAllNetworks = QCheckBox("Scan All Networks During Discovery") + self.chkScanAllNetworks.setChecked(int(mw.settings.value(self.scanAllKey, 1))) + self.chkScanAllNetworks.stateChanged.connect(self.scanAllNetworksChecked) + self.cmbInterfaces = QComboBox() + intf = self.mw.settings.value(self.interfaceKey, "") + self.lblInterfaces = QLabel("Network") + session = onvif.Session() + session.getActiveInterfaces() + i = 0 + while len(session.active_interface(i)) > 0 and i < 16: + self.cmbInterfaces.addItem(session.active_interface(i)) + i += 1 + if len(intf) > 0: + self.cmbInterfaces.setCurrentText(intf) + self.cmbInterfaces.currentTextChanged.connect(self.cmbInterfacesChanged) + self.cmbInterfaces.setEnabled(not self.chkScanAllNetworks.isChecked()) + self.lblInterfaces.setEnabled(not self.chkScanAllNetworks.isChecked()) + + self.btnAddCamera = QPushButton("Add Camera") + self.btnAddCamera.clicked.connect(self.btnAddCameraClicked) + + self.chkAutoDiscover = QCheckBox("Auto Discovery") + self.chkAutoDiscover.setChecked(bool(int(mw.settings.value(self.autoDiscoverKey, 0)))) + self.chkAutoDiscover.stateChanged.connect(self.autoDiscoverChecked) + + self.chkAutoStart = QCheckBox("Auto Start") + self.chkAutoStart.setChecked(bool(int(mw.settings.value(self.autoStartKey, 0)))) + self.chkAutoStart.stateChanged.connect(self.autoStartChecked) + + pnlInterface = QGroupBox("Discovery Options") + lytInterface = QGridLayout(pnlInterface) + lytInterface.addWidget(self.grpDiscoverType, 0, 0, 1, 2) + lytInterface.addWidget(self.chkScanAllNetworks, 2, 0, 1, 2) + lytInterface.addWidget(self.lblInterfaces, 4, 0, 1, 1) + lytInterface.addWidget(self.cmbInterfaces, 4, 1, 1, 1) + lytInterface.addWidget(self.btnAddCamera, 5, 0, 1, 2, Qt.AlignmentFlag.AlignCenter) + lytInterface.setColumnStretch(1, 10) + lytInterface.setContentsMargins(10, 10, 10, 10) + + lytMain = QGridLayout(self) + lytMain.addWidget(pnlInterface, 0, 0, 1, 2) + lytMain.addWidget(QLabel(), 1, 0, 1, 2) + lytMain.addWidget(self.chkAutoDiscover, 2, 0, 1, 1) + lytMain.addWidget(self.chkAutoStart, 2, 1, 1, 1) + lytMain.addWidget(QLabel(), 3, 0, 1, 2) + lytMain.setRowStretch(3, 10) + + self.radDiscoverToggled(self.radDiscover.isChecked()) + + def radDiscoverToggled(self, checked): + self.chkScanAllNetworks.setEnabled(checked) + if self.chkScanAllNetworks.isChecked(): + self.lblInterfaces.setEnabled(False) + self.cmbInterfaces.setEnabled(False) + else: + self.lblInterfaces.setEnabled(checked) + self.cmbInterfaces.setEnabled(checked) + self.mw.settings.setValue(self.discoveryTypeKey, int(checked)) + + def scanAllNetworksChecked(self, state): + self.mw.settings.setValue(self.scanAllKey, state) + self.cmbInterfaces.setEnabled(not self.chkScanAllNetworks.isChecked()) + self.lblInterfaces.setEnabled(not self.chkScanAllNetworks.isChecked()) + + def cmbInterfacesChanged(self, network): + self.mw.settings.setValue(self.interfaceKey, network) + + def btnAddCameraClicked(self): + dlg = AddCameraDialog(self.mw) + if dlg.exec(): + ip_address = dlg.txtIPAddress.text() + onvif_port = dlg.txtOnvifPort.text() + if not len(onvif_port): + onvif_port = "80" + xaddrs = f'http://{ip_address}:{onvif_port}/onvif/device_service' + logger.debug(f'Attempting to add camera manually using xaddrs: {xaddrs}') + data = onvif.Data() + data.getData = self.mw.cameraPanel.getData + data.getCredential = self.mw.cameraPanel.getCredential + data.setXAddrs(xaddrs) + data.setDeviceService(xaddrs) + data.manual_fill() + + def autoDiscoverChecked(self, state): + self.mw.settings.setValue(self.autoDiscoverKey, state) + + def autoStartChecked(self, state): + self.mw.settings.setValue(self.autoStartKey, state) + diff --git a/onvif-gui/gui/panels/options/general.py b/onvif-gui/gui/panels/options/general.py new file mode 100644 index 0000000..6c20413 --- /dev/null +++ b/onvif-gui/gui/panels/options/general.py @@ -0,0 +1,321 @@ +#/******************************************************************** +# libonvif/onvif-gui/gui/panels/options/general.py +# +# Copyright (c) 2024 Stephen Rhodes +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#*********************************************************************/ + +import os +import sys +from PyQt6.QtWidgets import QMessageBox, QLineEdit, QSpinBox, \ + QGridLayout, QWidget, QCheckBox, QLabel, QComboBox, QPushButton, \ + QDialog, QTextEdit, QMessageBox, QFileDialog +from PyQt6.QtCore import QFile, QRect +from PyQt6.QtGui import QTextCursor, QTextOption +from loguru import logger +import avio +import webbrowser +import platform +from gui.player import Player + +class LogText(QTextEdit): + def __init__(self, parent): + super().__init__(parent) + self.setWordWrapMode(QTextOption.WrapMode.NoWrap) + + def scrollToBottom(self): + self.moveCursor(QTextCursor.MoveOperation.End, QTextCursor.MoveMode.MoveAnchor) + self.ensureCursorVisible() + +class LogDialog(QDialog): + def __init__(self, mw): + super().__init__(mw) + self.mw = mw + self.geometryKey = "LogDialog/geometry" + rect = self.mw.settings.value(self.geometryKey, QRect(0, 0, 480, 640)) + if rect is not None: + if rect.width() and rect.height(): + self.setGeometry(rect) + + self.editor = LogText(self) + self.editor.setReadOnly(True) + self.editor.setFontFamily("courier") + + self.lblSize = QLabel("Log File Size:") + self.btnArchive = QPushButton("Archive") + self.btnArchive.clicked.connect(self.btnArchiveClicked) + self.btnClear = QPushButton(" Clear ") + self.btnClear.clicked.connect(self.btnClearClicked) + + pnlButton = QWidget() + lytButton = QGridLayout(pnlButton) + lytButton.addWidget(self.btnArchive, 0, 1, 1, 1) + lytButton.addWidget(self.btnClear, 0, 2, 1, 1) + lytButton.setContentsMargins(0, 0, 0, 0) + + panel = QWidget() + lytPanel = QGridLayout(panel) + lytPanel.addWidget(self.lblSize, 0, 0, 1, 1) + lytPanel.addWidget(pnlButton, 0, 1, 1, 1) + lytPanel.addWidget(QLabel(), 0, 2, 1, 1) + lytPanel.setColumnStretch(2, 10) + + lyt = QGridLayout(self) + lyt.addWidget(self.editor, 0, 0, 1, 1) + lyt.addWidget(panel, 1, 0, 1, 1) + lyt.setRowStretch(0, 10) + + def closeEvent(self, e): + self.mw.settings.setValue(self.geometryKey, self.geometry()) + + def readLogFile(self, path): + data = "" + if os.path.isfile(path): + with open(path, 'r') as file: + data = file.read() + self.setWindowTitle(path) + self.editor.setText(data) + y = "{:.2f}".format(os.stat(path).st_size/1000000) + self.lblSize.setText(f'Log File Size: {y} MB ') + self.editor.scrollToBottom() + + def btnArchiveClicked(self): + path = None + if platform.system() == "Linux": + path = QFileDialog.getOpenFileName(self, "Select File", self.windowTitle(), options=QFileDialog.Option.DontUseNativeDialog)[0] + else: + path = QFileDialog.getOpenFileName(self, "Select File", self.windowTitle())[0] + if path: + if len(path) > 0: + self.readLogFile(path) + + def btnClearClicked(self): + filename = self.windowTitle() + ret = QMessageBox.warning(self, "Deleting File", + f'\n{filename}\n\n' + "You are about to delete this file.\n" + "Are you sure you want to continue?", + QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel) + + if ret == QMessageBox.StandardButton.Ok: + if filename == self.mw.settingsPanel.getLogFilename(): + ret = QMessageBox.warning(self, "Deleting Current Log", + "You are about to delete the current log.\n" + "Are you sure you want to continue?", + QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel) + if ret == QMessageBox.StandardButton.Ok: + QFile.remove(filename) + logger.add(filename) + logger.debug("Created new log file") + self.readLogFile(filename) + else: + QFile.remove(filename) + self.readLogFile(self.mw.settingsPanel.getLogFilename()) + QMessageBox.information(self, "Current Log Displayed", "The current log has been loaded into the display", QMessageBox.StandardButton.Ok) + +class GeneralOptions(QWidget): + def __init__(self, mw): + super().__init__() + self.mw = mw + + self.usernameKey = "settings/username" + self.passwordKey = "settings/password" + self.decoderKey = "settings/decoder" + self.bufferSizeKey = "settings/bufferSize" + self.lagTimeKey = "settings/lagTime" + self.startFullScreenKey = "settings/startFullScreen" + self.autoTimeSyncKey = "settings/autoTimeSync" + self.alarmSoundFileKey = "settings/alarmSoundFile" + self.alarmSoundVolumeKey = "settings/alarmSoundVolume" + self.displayRefreshKey = "settings/displayRefresh" + self.cacheMaxSizeKey = "settings/cacheMaxSize" + self.audioDriverIndexKey = "settings/audioDriverIndex" + + decoders = ["NONE"] + if sys.platform == "win32": + decoders += ["CUDA", "DXVA2", "D3D11VA"] + if sys.platform == "linux": + decoders += ["CUDA", "VAAPI", "VDPAU"] + + p = Player("", self) + audioDrivers = p.getAudioDrivers() + + self.dlgLog = None + + self.txtUsername = QLineEdit() + self.txtUsername.setText(mw.settings.value(self.usernameKey, "")) + self.txtUsername.textChanged.connect(self.usernameChanged) + lblUsername = QLabel("Common Username") + + self.txtPassword = QLineEdit() + self.txtPassword.setText(mw.settings.value(self.passwordKey, "")) + self.txtPassword.textChanged.connect(self.passwordChanged) + lblPassword = QLabel("Common Password") + + self.cmbDecoder = QComboBox() + self.cmbDecoder.addItems(decoders) + self.cmbDecoder.setCurrentText(mw.settings.value(self.decoderKey, "NONE")) + self.cmbDecoder.currentTextChanged.connect(self.cmbDecoderChanged) + lblDecoders = QLabel("Hardware Decoder") + + self.cmbAudioDriver = QComboBox() + self.cmbAudioDriver.addItems(audioDrivers) + self.cmbAudioDriver.setCurrentIndex(int(mw.settings.value(self.audioDriverIndexKey, 0))) + self.cmbAudioDriver.currentIndexChanged.connect(self.cmbAudioDriverChanged) + lblAudioDrivers = QLabel("Audio Driver") + + self.chkStartFullScreen = QCheckBox("Start Full Screen") + self.chkStartFullScreen.setChecked(bool(int(mw.settings.value(self.startFullScreenKey, 0)))) + self.chkStartFullScreen.stateChanged.connect(self.startFullScreenChecked) + + self.chkAutoTimeSync = QCheckBox("Auto Time Sync") + self.chkAutoTimeSync.setChecked(bool(int(mw.settings.value(self.autoTimeSyncKey, 0)))) + self.chkAutoTimeSync.stateChanged.connect(self.autoTimeSyncChecked) + + pnlChecks = QWidget() + lytChecks = QGridLayout(pnlChecks) + lytChecks.addWidget(self.chkStartFullScreen, 0, 0, 1, 1) + lytChecks.addWidget(self.chkAutoTimeSync, 0, 1, 1, 1) + + self.spnDisplayRefresh = QSpinBox() + self.spnDisplayRefresh.setMinimum(1) + self.spnDisplayRefresh.setMaximum(1000) + self.spnDisplayRefresh.setMaximumWidth(80) + refresh = 10 + if sys.platform == "win32": + refresh = 20 + self.spnDisplayRefresh.setValue(int(self.mw.settings.value(self.displayRefreshKey, refresh))) + self.spnDisplayRefresh.valueChanged.connect(self.spnDisplayRefreshChanged) + lblDisplayRefresh = QLabel("Display Refresh Interval (in milliseconds)") + + self.spnCacheMax = QSpinBox() + self.spnCacheMax.setMaximum(200) + self.spnCacheMax.setValue(100) + self.spnCacheMax.setMaximumWidth(80) + self.spnCacheMax.setValue(int(self.mw.settings.value(self.cacheMaxSizeKey, 100))) + self.spnCacheMax.valueChanged.connect(self.spnCacheMaxChanged) + lblCacheMax = QLabel("Maximum Input Stream Cache Size") + + self.btnCloseAll = QPushButton("Start All Cameras") + self.btnCloseAll.clicked.connect(self.btnCloseAllClicked) + + self.btnShowLogs = QPushButton("Show Logs") + self.btnShowLogs.clicked.connect(self.btnShowLogsClicked) + + self.btnHelp = QPushButton("Help") + self.btnHelp.clicked.connect(self.btnHelpClicked) + + pnlBuffer = QWidget() + lytBuffer = QGridLayout(pnlBuffer) + lytBuffer.addWidget(lblDisplayRefresh, 4, 0, 1, 3) + lytBuffer.addWidget(self.spnDisplayRefresh, 4, 3, 1, 1) + lytBuffer.addWidget(lblCacheMax, 5, 0, 1, 3) + lytBuffer.addWidget(self.spnCacheMax, 5, 3, 1, 1) + lytBuffer.setContentsMargins(0, 0, 0, 0) + + pnlButtons = QWidget() + lytButtons = QGridLayout(pnlButtons) + lytButtons.addWidget(self.btnCloseAll, 0, 0, 1, 1) + lytButtons.addWidget(self.btnShowLogs, 0, 1, 1, 1) + lytButtons.addWidget(self.btnHelp, 0, 2, 1, 1) + + lytMain = QGridLayout(self) + lytMain.addWidget(lblUsername, 1, 0, 1, 1) + lytMain.addWidget(self.txtUsername, 1, 1, 1, 1) + lytMain.addWidget(lblPassword, 2, 0, 1, 1) + lytMain.addWidget(self.txtPassword, 2, 1, 1, 1) + lytMain.addWidget(lblDecoders, 3, 0, 1, 1) + lytMain.addWidget(self.cmbDecoder, 3, 1, 1, 1) + lytMain.addWidget(lblAudioDrivers, 4, 0, 1, 1) + lytMain.addWidget(self.cmbAudioDriver, 4, 1, 1, 1) + lytMain.addWidget(pnlChecks, 5, 0, 1, 3) + lytMain.addWidget(pnlBuffer, 6, 0, 1, 3) + lytMain.addWidget(pnlButtons, 7, 0, 1, 3) + lytMain.addWidget(QLabel(), 8, 0, 1, 3) + lytMain.setRowStretch(8, 10) + + def usernameChanged(self, username): + self.mw.settings.setValue(self.usernameKey, username) + + def passwordChanged(self, password): + self.mw.settings.setValue(self.passwordKey, password) + + def cmbDecoderChanged(self, decoder): + self.mw.settings.setValue(self.decoderKey, decoder) + + def cmbAudioDriverChanged(self, index): + self.mw.settings.setValue(self.audioDriverIndexKey, index) + if self.mw.audioStatus != avio.AudioStatus.UNINITIALIZED: + QMessageBox.warning(self.mw, "Application Restart Required", "It is necessary to re-start Onvif GUI in order to enable this change") + + def autoDiscoverChecked(self, state): + self.mw.settings.setValue(self.autoDiscoverKey, state) + + def startFullScreenChecked(self, state): + self.mw.settings.setValue(self.startFullScreenKey, state) + + def autoTimeSyncChecked(self, state): + self.mw.settings.setValue(self.autoTimeSyncKey, state) + self.mw.cameraPanel.enableAutoTimeSync(state) + + def spnDisplayRefreshChanged(self, i): + self.mw.settings.setValue(self.displayRefreshKey, i) + self.mw.glWidget.timer.setInterval(i) + + def spnCacheMaxChanged(self, i): + self.mw.settings.setValue(self.cacheMaxSizeKey, i) + + def cmbInterfacesChanged(self, network): + self.mw.settings.setValue(self.interfaceKey, network) + + def getDecoder(self): + result = avio.AV_HWDEVICE_TYPE_NONE + if self.cmbDecoder.currentText() == "CUDA": + result = avio.AV_HWDEVICE_TYPE_CUDA + if self.cmbDecoder.currentText() == "VAAPI": + result = avio.AV_HWDEVICE_TYPE_VAAPI + if self.cmbDecoder.currentText() == "VDPAU": + result = avio.AV_HWDEVICE_TYPE_VDPAU + if self.cmbDecoder.currentText() == "DXVA2": + result = avio.AV_HWDEVICE_TYPE_DXVA2 + if self.cmbDecoder.currentText() == "D3D11VA": + result = avio.AV_HWDEVICE_TYPE_D3D11VA + return result + + def btnCloseAllClicked(self): + if self.btnCloseAll.text() == "Close All Streams": + self.mw.closeAllStreams() + else: + self.mw.startAllCameras() + + def getLogFilename(self): + filename = "" + if sys.platform == "win32": + filename = os.environ['HOMEPATH'] + "/.cache/onvif-gui/logs.txt" + else: + filename = os.environ['HOME'] + "/.cache/onvif-gui/logs.txt" + return filename + + def btnShowLogsClicked(self): + filename = self.getLogFilename() + if not self.dlgLog: + self.dlgLog = LogDialog(self.mw) + self.dlgLog.readLogFile(filename) + self.dlgLog.exec() + + def btnHelpClicked(self): + result = webbrowser.get().open("https://github.com/sr99622/libonvif#readme-ov-file") + if not result: + webbrowser.get().open("https://github.com/sr99622/libonvif") diff --git a/onvif-gui/gui/panels/options/proxy.py b/onvif-gui/gui/panels/options/proxy.py new file mode 100644 index 0000000..ae8e0ff --- /dev/null +++ b/onvif-gui/gui/panels/options/proxy.py @@ -0,0 +1,129 @@ +#/******************************************************************** +# libonvif/onvif-gui/gui/panels/options/proxy.py +# +# Copyright (c) 2024 Stephen Rhodes +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#*********************************************************************/ + +from PyQt6.QtWidgets import QMessageBox, QLineEdit, \ + QGridLayout, QWidget, QLabel, QMessageBox, QRadioButton, \ + QGroupBox +from PyQt6.QtCore import Qt + +from gui.enums import ProxyType + +class ProxyOptions(QWidget): + def __init__(self, mw): + super().__init__() + self.mw = mw + + self.proxyTypeKey = "settings/proxyType" + self.proxyRemoteKey = "settings/proxyRemote" + + self.grpProxyType = QGroupBox("Select Proxy Type") + self.radStandAlone = QRadioButton("Stand Alone", self.grpProxyType) + self.radServer = QRadioButton("Server", self.grpProxyType) + self.radClient = QRadioButton("Client", self.grpProxyType) + + self.lblServer = QLabel() + self.lblConnect = QLabel("Connect url for clients") + self.lblConnect.setEnabled(False) + + self.proxyRemote = self.mw.settings.value(self.proxyRemoteKey) + + self.txtRemote = QLineEdit() + self.txtRemote.setText(self.proxyRemote) + self.txtRemote.textEdited.connect(self.txtRemoteEdited) + self.txtRemote.setEnabled(False) + self.lblRemote = QLabel("Enter connect url from server") + self.lblRemote.setEnabled(False) + + self.proxyType = (self.mw.settings.value(self.proxyTypeKey, ProxyType.STAND_ALONE)) + + match self.proxyType: + case ProxyType.STAND_ALONE: + self.radStandAlone.setChecked(True) + self.radStandAloneToggled(True) + case ProxyType.SERVER: + self.radServer.setChecked(True) + self.radServerToggled(True) + case ProxyType.CLIENT: + self.radClient.setChecked(True) + self.radClientToggled(True) + + self.radStandAlone.toggled.connect(self.radStandAloneToggled) + self.radServer.toggled.connect(self.radServerToggled) + self.radClient.toggled.connect(self.radClientToggled) + + lytProxyType = QGridLayout(self.grpProxyType) + lytProxyType.addWidget(self.radStandAlone, 0, 0, 1, 1) + lytProxyType.addWidget(self.radServer, 1, 0, 1, 1) + lytProxyType.addWidget(self.lblConnect, 1, 1, 1, 1, Qt.AlignmentFlag.AlignRight) + lytProxyType.addWidget(self.lblServer, 2, 0, 1, 2, Qt.AlignmentFlag.AlignRight) + lytProxyType.addWidget(self.radClient, 3, 0, 1, 1) + lytProxyType.addWidget(self.lblRemote, 3, 1, 1, 1, Qt.AlignmentFlag.AlignRight) + lytProxyType.addWidget(self.txtRemote, 4, 0, 1, 2) + lytProxyType.setColumnStretch(2, 10) + + lytMain = QGridLayout(self) + lytMain.addWidget(self.grpProxyType, 0, 0, 1, 1) + lytMain.addWidget(QLabel(), 1, 0, 1, 1) + lytMain.setRowStretch(1, 10) + + def setProxyType(self, type): + if not hasattr(self.mw, "cameraPanel"): + return + + if len(self.mw.pm.players): + QMessageBox.information(self.mw, "Closing Streams", "All current streams will be closed") + self.mw.closeAllStreams() + + self.proxyType = type + self.mw.settings.setValue(self.proxyTypeKey, type) + + getProxyURI = None + if type != ProxyType.STAND_ALONE: + getProxyURI = self.mw.getProxyURI + + if lstCamera := self.mw.cameraPanel.lstCamera: + cameras = [lstCamera.item(x) for x in range(lstCamera.count())] + for camera in cameras: + self.mw.addCameraProxy(camera) + camera.onvif_data.getProxyURI = getProxyURI + for profile in camera.profiles: + profile.getProxyURI = getProxyURI + + def radStandAloneToggled(self, checked): + if checked: + self.setProxyType(ProxyType.STAND_ALONE) + + def radServerToggled(self, checked): + self.lblConnect.setEnabled(checked) + if checked: + self.setProxyType(ProxyType.SERVER) + self.mw.startProxyServer() + self.lblServer.setText(self.mw.proxy.getRootURI()) + else: + self.mw.stopProxyServer() + self.lblServer.setText("") + + def radClientToggled(self, checked): + self.lblRemote.setEnabled(checked) + self.txtRemote.setEnabled(checked) + if checked: + self.setProxyType(ProxyType.CLIENT) + + def txtRemoteEdited(self, arg): + self.mw.settings.setValue(self.proxyRemoteKey, arg) \ No newline at end of file diff --git a/onvif-gui/gui/panels/options/storage.py b/onvif-gui/gui/panels/options/storage.py new file mode 100644 index 0000000..5b30329 --- /dev/null +++ b/onvif-gui/gui/panels/options/storage.py @@ -0,0 +1,124 @@ +#/******************************************************************** +# libonvif/onvif-gui/gui/panels/options/storage.py +# +# Copyright (c) 2024 Stephen Rhodes +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#*********************************************************************/ + +import os +from PyQt6.QtWidgets import QMessageBox, QSpinBox, \ + QGridLayout, QWidget, QCheckBox, QLabel, QMessageBox, QGroupBox +from PyQt6.QtCore import QStandardPaths +from loguru import logger +from gui.components import DirectorySelector +import shutil + +class StorageOptions(QWidget): + def __init__(self, mw): + super().__init__() + self.mw = mw + + self.archiveKey = "settings/archive" + self.pictureKey = "settings/picture" + self.diskLimitKey = "settings/diskLimit" + self.mangageDiskUsagekey = "settings/manageDiskUsage" + + video_dirs = QStandardPaths.standardLocations(QStandardPaths.StandardLocation.MoviesLocation) + self.dirArchive = DirectorySelector(mw, self.archiveKey, "Archive Dir", video_dirs[0]) + self.dirArchive.signals.dirChanged.connect(self.dirArchiveChanged) + + picture_dirs = QStandardPaths.standardLocations(QStandardPaths.StandardLocation.PicturesLocation) + self.dirPictures = DirectorySelector(mw, self.pictureKey, "Picture Dir", picture_dirs[0]) + self.dirPictures.signals.dirChanged.connect(self.dirPicturesChanged) + + dir_size = "{:.2f}".format(self.getDirectorySize(self.dirArchive.text()) / 1000000000) + self.grpDiskUsage = QGroupBox(f'Disk Usage (currently {dir_size} GB)') + self.spnDiskLimit = QSpinBox() + max_size = int(self.getMaximumDirectorySize()) + self.spnDiskLimit.setMaximum(max_size) + disk_limit = min(int(self.mw.settings.value(self.diskLimitKey, 100)), max_size) + self.spnDiskLimit.setValue(disk_limit) + self.spnDiskLimit.valueChanged.connect(self.spnDiskLimitChanged) + + lbl = f'Auto Manage (max {max_size} GB)' + self.chkManageDiskUsage = QCheckBox(lbl) + self.chkManageDiskUsage.setChecked(bool(int(self.mw.settings.value(self.mangageDiskUsagekey, 0)))) + self.chkManageDiskUsage.clicked.connect(self.chkManageDiskUsageChanged) + + lytDiskUsage = QGridLayout(self.grpDiskUsage) + lytDiskUsage.addWidget(self.chkManageDiskUsage, 0, 0, 1, 1) + lytDiskUsage.addWidget(self.spnDiskLimit, 0, 2, 1, 1) + lytDiskUsage.addWidget(QLabel("GB"), 0, 3, 1, 1) + lytDiskUsage.addWidget(self.dirArchive, 1, 0, 1, 4) + lytDiskUsage.addWidget(self.dirPictures, 2, 0, 1, 4) + + lytMain = QGridLayout(self) + lytMain.addWidget(self.grpDiskUsage, 0, 0, 1, 1) + lytMain.addWidget(QLabel(), 1, 0, 1, 1) + lytMain.setRowStretch(1, 10) + + def spnDiskLimitChanged(self, value): + self.mw.settings.setValue(self.diskLimitKey, value) + + def chkManageDiskUsageChanged(self): + if self.chkManageDiskUsage.isChecked(): + ret = QMessageBox.warning(self, "** WARNING **", + "You are giving full control of the archive directory to this program. " + "Any files contained within this directory or its sub-directories are subject to deletion. " + "You should only enable this feature if you are sure that this is ok.\n\n" + "Are you sure you want to continue?", + QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel) + if ret == QMessageBox.StandardButton.Cancel: + self.chkManageDiskUsage.setChecked(False) + self.mw.settings.setValue(self.mangageDiskUsagekey, int(self.chkManageDiskUsage.isChecked())) + + def dirArchiveChanged(self, path): + logger.debug(f'Video archive directory changed to {path}') + self.mw.settings.setValue(self.archiveKey, path) + max_size = int(self.getMaximumDirectorySize()) + self.spnDiskLimit.setMaximum(max_size) + lbl = f'Auto Manage (max {max_size} GB)' + self.chkManageDiskUsage.setText(lbl) + disk_limit = min(int(self.mw.settings.value(self.diskLimitKey, 100)), max_size) + self.spnDiskLimit.setValue(disk_limit) + self.chkManageDiskUsageChanged() + + def dirPicturesChanged(self, path): + logger.debug(f'Picture directory changed to {path}') + self.mw.settings.setValue(self.pictureKey, path) + + def getMaximumDirectorySize(self): + # compute disk space available for archive directory in GB + d = self.dirArchive.txtDirectory.text() + d_size = 0 + for dirpath, dirnames, filenames in os.walk(d): + for f in filenames: + fp = os.path.join(dirpath, f) + if not os.path.islink(fp): + d_size += os.path.getsize(fp) + total, used, free = shutil.disk_usage(d) + max_available = (free + d_size - 10000000000) / 1000000000 + return max_available + + def getDirectorySize(self, d): + total_size = 0 + for dirpath, dirnames, filenames in os.walk(d): + for f in filenames: + fp = os.path.join(dirpath, f) + if not os.path.islink(fp): + total_size += os.path.getsize(fp) + + return total_size + diff --git a/onvif-gui/gui/panels/settingspanel.py b/onvif-gui/gui/panels/settingspanel.py index c680833..1e314eb 100644 --- a/onvif-gui/gui/panels/settingspanel.py +++ b/onvif-gui/gui/panels/settingspanel.py @@ -17,605 +17,37 @@ # #*********************************************************************/ -import os -import sys -from PyQt6.QtWidgets import QMessageBox, QLineEdit, QSpinBox, \ - QGridLayout, QWidget, QCheckBox, QLabel, QComboBox, QPushButton, \ - QDialog, QTextEdit, QMessageBox, QDialogButtonBox, QRadioButton, \ - QGroupBox, QSlider, QFileDialog -from PyQt6.QtCore import Qt, QStandardPaths, QFile, QRegularExpression, QRect -from PyQt6.QtGui import QRegularExpressionValidator, QTextCursor, QTextOption -from loguru import logger -from gui.components import DirectorySelector -import libonvif as onvif -import avio -import shutil -from time import sleep -import webbrowser -import platform +from PyQt6.QtWidgets import QGridLayout, QWidget, QTabWidget -class AddCameraDialog(QDialog): - def __init__(self, mw): - super().__init__(mw) - self.mw = mw - - ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])" - ipRegex = QRegularExpression("^" + ipRange + "\\." + ipRange + "\\." + ipRange + "\\." + ipRange + "$") - ipValidator = QRegularExpressionValidator(ipRegex, self) - - self.setWindowTitle("Add Camera") - self.txtIPAddress = QLineEdit() - self.txtIPAddress.setValidator(ipValidator) - self.lblIPAddress = QLabel("IP Address") - self.txtOnvifPort = QLineEdit() - self.lblOnvifPort = QLabel("Onvif Port") - - buttonBox = QDialogButtonBox( \ - QDialogButtonBox.StandardButton.Ok | \ - QDialogButtonBox.StandardButton.Cancel) - - lytMain = QGridLayout(self) - lytMain.addWidget(self.lblIPAddress, 1, 0, 1, 1) - lytMain.addWidget(self.txtIPAddress, 1, 1, 1, 1) - lytMain.addWidget(self.lblOnvifPort, 2, 0, 1, 1) - lytMain.addWidget(self.txtOnvifPort, 2, 1, 1, 1) - lytMain.addWidget(buttonBox, 5, 0, 1, 2) - - buttonBox.accepted.connect(self.accept) - buttonBox.rejected.connect(self.reject) - - self.txtIPAddress.setFocus() - -class LogText(QTextEdit): - def __init__(self, parent): - super().__init__(parent) - self.setWordWrapMode(QTextOption.WrapMode.NoWrap) - - def scrollToBottom(self): - self.moveCursor(QTextCursor.MoveOperation.End, QTextCursor.MoveMode.MoveAnchor) - self.ensureCursorVisible() - -class LogDialog(QDialog): - def __init__(self, mw): - super().__init__(mw) - self.mw = mw - self.geometryKey = "LogDialog/geometry" - rect = self.mw.settings.value(self.geometryKey, QRect(0, 0, 480, 640)) - if rect is not None: - if rect.width() and rect.height(): - self.setGeometry(rect) - - self.editor = LogText(self) - self.editor.setReadOnly(True) - self.editor.setFontFamily("courier") - - self.lblSize = QLabel("Log File Size:") - self.btnArchive = QPushButton("Archive") - self.btnArchive.clicked.connect(self.btnArchiveClicked) - self.btnClear = QPushButton(" Clear ") - self.btnClear.clicked.connect(self.btnClearClicked) - - pnlButton = QWidget() - lytButton = QGridLayout(pnlButton) - lytButton.addWidget(self.btnArchive, 0, 1, 1, 1) - lytButton.addWidget(self.btnClear, 0, 2, 1, 1) - lytButton.setContentsMargins(0, 0, 0, 0) - - panel = QWidget() - lytPanel = QGridLayout(panel) - lytPanel.addWidget(self.lblSize, 0, 0, 1, 1) - lytPanel.addWidget(pnlButton, 0, 1, 1, 1) - lytPanel.addWidget(QLabel(), 0, 2, 1, 1) - lytPanel.setColumnStretch(2, 10) - - lyt = QGridLayout(self) - lyt.addWidget(self.editor, 0, 0, 1, 1) - lyt.addWidget(panel, 1, 0, 1, 1) - lyt.setRowStretch(0, 10) - - def closeEvent(self, e): - self.mw.settings.setValue(self.geometryKey, self.geometry()) - - def readLogFile(self, path): - data = "" - if os.path.isfile(path): - with open(path, 'r') as file: - data = file.read() - self.setWindowTitle(path) - self.editor.setText(data) - y = "{:.2f}".format(os.stat(path).st_size/1000000) - self.lblSize.setText(f'Log File Size: {y} MB ') - self.editor.scrollToBottom() - - def btnArchiveClicked(self): - path = None - if platform.system() == "Linux": - path = QFileDialog.getOpenFileName(self, "Select File", self.windowTitle(), options=QFileDialog.Option.DontUseNativeDialog)[0] - else: - path = QFileDialog.getOpenFileName(self, "Select File", self.windowTitle())[0] - if path: - if len(path) > 0: - self.readLogFile(path) - - def btnClearClicked(self): - filename = self.windowTitle() - ret = QMessageBox.warning(self, "Deleting File", - f'\n{filename}\n\n' - "You are about to delete this file.\n" - "Are you sure you want to continue?", - QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel) - - if ret == QMessageBox.StandardButton.Ok: - if filename == self.mw.settingsPanel.getLogFilename(): - ret = QMessageBox.warning(self, "Deleting Current Log", - "You are about to delete the current log.\n" - "Are you sure you want to continue?", - QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel) - if ret == QMessageBox.StandardButton.Ok: - QFile.remove(filename) - logger.add(filename) - logger.debug("Created new log file") - self.readLogFile(filename) - else: - QFile.remove(filename) - self.readLogFile(self.mw.settingsPanel.getLogFilename()) - QMessageBox.information(self, "Current Log Displayed", "The current log has been loaded into the display", QMessageBox.StandardButton.Ok) +from gui.panels.options import DiscoverOptions, GeneralOptions, StorageOptions, \ + AlarmOptions, ProxyOptions class SettingsPanel(QWidget): def __init__(self, mw): super().__init__() self.mw = mw - self.usernameKey = "settings/username" - self.passwordKey = "settings/password" - self.decoderKey = "settings/decoder" - self.bufferSizeKey = "settings/bufferSize" - self.lagTimeKey = "settings/lagTime" - self.interfaceKey = "settings/interface" - self.autoDiscoverKey = "settings/autoDiscover" - self.startFullScreenKey = "settings/startFullScreen" - self.autoTimeSyncKey = "settings/autoTimeSync" - self.autoStartKey = "settings/autoStart" - self.archiveKey = "settings/archive" - self.pictureKey = "settings/picture" - self.scanAllKey = "settings/scanAll" - self.cameraListKey = "settings/cameraList" - self.discoveryTypeKey = "settings/discoveryType" - self.alarmSoundFileKey = "settings/alarmSoundFile" - self.alarmSoundVolumeKey = "settings/alarmSoundVolume" - self.diskLimitKey = "settings/diskLimit" - self.mangageDiskUsagekey = "settings/manageDiskUsage" - self.displayRefreshKey = "settings/displayRefresh" - self.cacheMaxSizeKey = "settings/cacheMaxSize" - - decoders = ["NONE"] - if sys.platform == "win32": - decoders += ["CUDA", "DXVA2", "D3D11VA"] - if sys.platform == "linux": - decoders += ["CUDA", "VAAPI", "VDPAU"] - - self.dlgLog = None - - self.txtUsername = QLineEdit() - self.txtUsername.setText(mw.settings.value(self.usernameKey, "")) - self.txtUsername.textChanged.connect(self.usernameChanged) - lblUsername = QLabel("Common Username") - - self.txtPassword = QLineEdit() - self.txtPassword.setText(mw.settings.value(self.passwordKey, "")) - self.txtPassword.textChanged.connect(self.passwordChanged) - lblPassword = QLabel("Common Password") - - self.cmbDecoder = QComboBox() - self.cmbDecoder.addItems(decoders) - self.cmbDecoder.setCurrentText(mw.settings.value(self.decoderKey, "NONE")) - self.cmbDecoder.currentTextChanged.connect(self.cmbDecoderChanged) - lblDecoders = QLabel("Hardware Decoder") - - self.chkStartFullScreen = QCheckBox("Start Full Screen") - self.chkStartFullScreen.setChecked(bool(int(mw.settings.value(self.startFullScreenKey, 0)))) - self.chkStartFullScreen.stateChanged.connect(self.startFullScreenChecked) - - self.chkAutoDiscover = QCheckBox("Auto Discovery") - self.chkAutoDiscover.setChecked(bool(int(mw.settings.value(self.autoDiscoverKey, 0)))) - self.chkAutoDiscover.stateChanged.connect(self.autoDiscoverChecked) - - self.chkAutoTimeSync = QCheckBox("Auto Time Sync") - self.chkAutoTimeSync.setChecked(bool(int(mw.settings.value(self.autoTimeSyncKey, 0)))) - self.chkAutoTimeSync.stateChanged.connect(self.autoTimeSyncChecked) - - self.chkAutoStart = QCheckBox("Auto Start") - self.chkAutoStart.setChecked(bool(int(mw.settings.value(self.autoStartKey, 0)))) - self.chkAutoStart.stateChanged.connect(self.autoStartChecked) - - pnlChecks = QWidget() - lytChecks = QGridLayout(pnlChecks) - lytChecks.addWidget(self.chkStartFullScreen, 0, 0, 1, 1) - lytChecks.addWidget(self.chkAutoDiscover, 0, 1, 1, 1) - lytChecks.addWidget(self.chkAutoStart, 1, 0, 1, 1) - lytChecks.addWidget(self.chkAutoTimeSync, 1, 1, 1, 1) - - self.spnBufferSize = QSpinBox() - self.spnBufferSize.setMinimum(1) - self.spnBufferSize.setMaximum(60) - self.spnBufferSize.setMaximumWidth(80) - self.spnBufferSize.setValue(int(self.mw.settings.value(self.bufferSizeKey, 10))) - self.spnBufferSize.valueChanged.connect(self.spnBufferSizeChanged) - lblBufferSize = QLabel("Pre-Alarm Buffer Size (in seconds)") - - self.spnLagTime = QSpinBox() - self.spnLagTime.setMinimum(1) - self.spnLagTime.setMaximum(60) - self.spnLagTime.setMaximumWidth(80) - self.spnLagTime.setValue(int(self.mw.settings.value(self.lagTimeKey, 5))) - self.spnLagTime.valueChanged.connect(self.spnLagTimeChanged) - lblLagTime = QLabel("Post-Alarm Lag Time (in seconds)") - - self.spnDisplayRefresh = QSpinBox() - self.spnDisplayRefresh.setMinimum(1) - self.spnDisplayRefresh.setMaximum(1000) - self.spnDisplayRefresh.setMaximumWidth(80) - refresh = 10 - if sys.platform == "win32": - refresh = 20 - self.spnDisplayRefresh.setValue(int(self.mw.settings.value(self.displayRefreshKey, refresh))) - self.spnDisplayRefresh.valueChanged.connect(self.spnDisplayRefreshChanged) - lblDisplayRefresh = QLabel("Display Refresh Interval (in milliseconds)") - - self.spnCacheMax = QSpinBox() - self.spnCacheMax.setMaximum(200) - self.spnCacheMax.setValue(100) - self.spnCacheMax.setMaximumWidth(80) - self.spnCacheMax.setValue(int(self.mw.settings.value(self.cacheMaxSizeKey, 100))) - self.spnCacheMax.valueChanged.connect(self.spnCacheMaxChanged) - lblCacheMax = QLabel("Maximum Input Stream Cache Size") - - self.cmbSoundFiles = QComboBox() - d = f'{self.mw.getLocation()}/gui/resources' - sounds = [f for f in os.listdir(d) if os.path.isfile(os.path.join(d, f)) and f.endswith(".mp3")] - self.cmbSoundFiles.addItems(sounds) - self.cmbSoundFiles.currentTextChanged.connect(self.cmbSoundFilesChanged) - self.cmbSoundFiles.setCurrentText(self.mw.settings.value(self.alarmSoundFileKey, "drops.mp3")) - lblSoundFiles = QLabel("Alarm Sounds") - self.sldAlarmVolume = QSlider(Qt.Orientation.Horizontal) - self.sldAlarmVolume.setValue(int(self.mw.settings.value(self.alarmSoundVolumeKey, 80))) - self.sldAlarmVolume.valueChanged.connect(self.sldAlarmVolumeChanged) - - pnlSoundFile = QWidget() - lytSoundFile = QGridLayout(pnlSoundFile) - lytSoundFile.addWidget(lblSoundFiles, 0, 0, 1, 1) - lytSoundFile.addWidget(self.cmbSoundFiles, 0, 1, 1, 1) - lytSoundFile.addWidget(self.sldAlarmVolume, 0, 2, 1, 1) - lytSoundFile.setColumnStretch(1, 10) - - pnlBuffer = QWidget() - lytBuffer = QGridLayout(pnlBuffer) - lytBuffer.addWidget(lblBufferSize, 1, 0, 1, 3) - lytBuffer.addWidget(self.spnBufferSize, 1, 3, 1, 1) - lytBuffer.addWidget(lblLagTime, 2, 0, 1, 3) - lytBuffer.addWidget(self.spnLagTime, 2, 3, 1, 1) - lytBuffer.addWidget(pnlSoundFile, 3, 0, 1, 4) - lytBuffer.addWidget(lblDisplayRefresh, 4, 0, 1, 3) - lytBuffer.addWidget(self.spnDisplayRefresh, 4, 3, 1, 1) - lytBuffer.addWidget(lblCacheMax, 5, 0, 1, 3) - lytBuffer.addWidget(self.spnCacheMax, 5, 3, 1, 1) - lytBuffer.setContentsMargins(0, 0, 0, 0) - - self.grpDiscoverType = QGroupBox("Set Camera Discovery Method") - self.radDiscover = QRadioButton("Discover Broadcast", self.grpDiscoverType ) - self.radDiscover.setChecked(int(self.mw.settings.value(self.discoveryTypeKey, 1))) - self.radDiscover.toggled.connect(self.radDiscoverToggled) - self.radCached = QRadioButton("Cached Addresses", self.grpDiscoverType ) - self.radCached.setChecked(not self.radDiscover.isChecked()) - lytDiscoverType = QGridLayout(self.grpDiscoverType ) - lytDiscoverType.addWidget(self.radDiscover, 0, 0, 1, 1) - lytDiscoverType.addWidget(self.radCached, 0, 1, 1, 1) - - self.chkScanAllNetworks = QCheckBox("Scan All Networks During Discovery") - self.chkScanAllNetworks.setChecked(int(mw.settings.value(self.scanAllKey, 1))) - self.chkScanAllNetworks.stateChanged.connect(self.scanAllNetworksChecked) - self.cmbInterfaces = QComboBox() - intf = self.mw.settings.value(self.interfaceKey, "") - self.lblInterfaces = QLabel("Network") - session = onvif.Session() - session.getActiveInterfaces() - i = 0 - while len(session.active_interface(i)) > 0 and i < 16: - self.cmbInterfaces.addItem(session.active_interface(i)) - i += 1 - if len(intf) > 0: - self.cmbInterfaces.setCurrentText(intf) - self.cmbInterfaces.currentTextChanged.connect(self.cmbInterfacesChanged) - self.cmbInterfaces.setEnabled(not self.chkScanAllNetworks.isChecked()) - self.lblInterfaces.setEnabled(not self.chkScanAllNetworks.isChecked()) - - self.btnAddCamera = QPushButton("Add Camera") - self.btnAddCamera.clicked.connect(self.btnAddCameraClicked) - - pnlInterface = QGroupBox("Discovery Options") - lytInterface = QGridLayout(pnlInterface) - lytInterface.addWidget(self.grpDiscoverType, 0, 0, 1, 2) - lytInterface.addWidget(self.chkScanAllNetworks, 2, 0, 1, 2) - lytInterface.addWidget(self.lblInterfaces, 4, 0, 1, 1) - lytInterface.addWidget(self.cmbInterfaces, 4, 1, 1, 1) - lytInterface.addWidget(self.btnAddCamera, 5, 0, 1, 2, Qt.AlignmentFlag.AlignCenter) - lytInterface.setColumnStretch(1, 10) - lytInterface.setContentsMargins(10, 10, 10, 10) - self.radDiscoverToggled(self.radDiscover.isChecked()) + self.tab = QTabWidget() - video_dirs = QStandardPaths.standardLocations(QStandardPaths.StandardLocation.MoviesLocation) - self.dirArchive = DirectorySelector(mw, self.archiveKey, "Archive Dir", video_dirs[0]) - self.dirArchive.signals.dirChanged.connect(self.dirArchiveChanged) + self.general = GeneralOptions(mw) + self.discover = DiscoverOptions(mw) + self.storage = StorageOptions(mw) + self.proxy = ProxyOptions(mw) + self.alarm = AlarmOptions(mw) - picture_dirs = QStandardPaths.standardLocations(QStandardPaths.StandardLocation.PicturesLocation) - self.dirPictures = DirectorySelector(mw, self.pictureKey, "Picture Dir", picture_dirs[0]) - self.dirPictures.signals.dirChanged.connect(self.dirPicturesChanged) - - dir_size = "{:.2f}".format(self.getDirectorySize(self.dirArchive.text()) / 1000000000) - self.grpDiskUsage = QGroupBox(f'Disk Usage (currently {dir_size} GB)') - self.spnDiskLimit = QSpinBox() - max_size = int(self.getMaximumDirectorySize()) - self.spnDiskLimit.setMaximum(max_size) - disk_limit = min(int(self.mw.settings.value(self.diskLimitKey, 100)), max_size) - self.spnDiskLimit.setValue(disk_limit) - self.spnDiskLimit.valueChanged.connect(self.spnDiskLimitChanged) - - lbl = f'Auto Manage (max {max_size} GB)' - self.chkManageDiskUsage = QCheckBox(lbl) - self.chkManageDiskUsage.setChecked(bool(int(self.mw.settings.value(self.mangageDiskUsagekey, 0)))) - self.chkManageDiskUsage.clicked.connect(self.chkManageDiskUsageChanged) - - lytDiskUsage = QGridLayout(self.grpDiskUsage) - lytDiskUsage.addWidget(self.chkManageDiskUsage, 0, 0, 1, 1) - lytDiskUsage.addWidget(self.spnDiskLimit, 0, 2, 1, 1) - lytDiskUsage.addWidget(QLabel("GB"), 0, 3, 1, 1) - lytDiskUsage.addWidget(self.dirArchive, 1, 0, 1, 4) - lytDiskUsage.addWidget(self.dirPictures, 2, 0, 1, 4) - - self.btnCloseAll = QPushButton("Start All Cameras") - self.btnCloseAll.clicked.connect(self.btnCloseAllClicked) - - self.btnShowLogs = QPushButton("Show Logs") - self.btnShowLogs.clicked.connect(self.btnShowLogsClicked) - - self.btnHelp = QPushButton("Help") - self.btnHelp.clicked.connect(self.btnHelpClicked) - - pnlButtons = QWidget() - lytButtons = QGridLayout(pnlButtons) - lytButtons.addWidget(self.btnCloseAll, 0, 0, 1, 1) - lytButtons.addWidget(self.btnShowLogs, 0, 1, 1, 1) - lytButtons.addWidget(self.btnHelp, 0, 2, 1, 1) - - self.lblSpacer = QLabel("") + self.tab.addTab(self.general, "General") + self.tab.addTab(self.discover, "Discover") + self.tab.addTab(self.storage, "Storage") + self.tab.addTab(self.proxy, "Proxy") + self.tab.addTab(self.alarm, "Alarm") lytMain = QGridLayout(self) - lytMain.addWidget(lblUsername, 1, 0, 1, 1) - lytMain.addWidget(self.txtUsername, 1, 1, 1, 1) - lytMain.addWidget(lblPassword, 2, 0, 1, 1) - lytMain.addWidget(self.txtPassword, 2, 1, 1, 1) - lytMain.addWidget(lblDecoders, 3, 0, 1, 1) - lytMain.addWidget(self.cmbDecoder, 3, 1, 1, 1) - lytMain.addWidget(pnlChecks, 4, 0, 1, 3) - lytMain.addWidget(pnlBuffer, 5, 0, 1, 3) - lytMain.addWidget(pnlInterface, 6, 0, 1, 3) - lytMain.addWidget(self.grpDiskUsage, 7, 0, 1, 3) - lytMain.addWidget(pnlButtons, 8, 0, 1, 3) - lytMain.addWidget(self.lblSpacer, 9, 0, 1, 3) - lytMain.setRowStretch(9, 10) - - def showEvent(self, event): - self.lblSpacer.setFocus() - return super().showEvent(event) - - def usernameChanged(self, username): - self.mw.settings.setValue(self.usernameKey, username) - - def passwordChanged(self, password): - self.mw.settings.setValue(self.passwordKey, password) - - def cmbDecoderChanged(self, decoder): - self.mw.settings.setValue(self.decoderKey, decoder) - - def autoDiscoverChecked(self, state): - self.mw.settings.setValue(self.autoDiscoverKey, state) - - def startFullScreenChecked(self, state): - self.mw.settings.setValue(self.startFullScreenKey, state) - - def autoTimeSyncChecked(self, state): - self.mw.settings.setValue(self.autoTimeSyncKey, state) - self.mw.cameraPanel.enableAutoTimeSync(state) - - def autoStartChecked(self, state): - self.mw.settings.setValue(self.autoStartKey, state) - - def spnDisplayRefreshChanged(self, i): - self.mw.settings.setValue(self.displayRefreshKey, i) - self.mw.glWidget.timer.setInterval(i) - - def spnCacheMaxChanged(self, i): - self.mw.settings.setValue(self.cacheMaxSizeKey, i) - - def spnBufferSizeChanged(self, i): - self.mw.settings.setValue(self.bufferSizeKey, i) - - def spnLagTimeChanged(self, i): - self.mw.settings.setValue(self.lagTimeKey, i) - - def cmbInterfacesChanged(self, network): - self.mw.settings.setValue(self.interfaceKey, network) + lytMain.addWidget(self.tab, 0, 0, 1, 1) def onMediaStarted(self): if len(self.mw.pm.players): - self.btnCloseAll.setText("Close All Streams") + self.general.btnCloseAll.setText("Close All Streams") def onMediaStopped(self): if not len(self.mw.pm.players): - self.btnCloseAll.setText("Start All Cameras") - - - def btnCloseAllClicked(self): - try: - if self.btnCloseAll.text() == "Close All Streams": - for player in self.mw.pm.players: - player.requestShutdown() - for timer in self.mw.timers.values(): - timer.stop() - self.mw.pm.auto_start_mode = False - lstCamera = self.mw.cameraPanel.lstCamera - if lstCamera: - cameras = [lstCamera.item(x) for x in range(lstCamera.count())] - for camera in cameras: - camera.setIconIdle() - - count = 0 - while len(self.mw.pm.players): - sleep(0.1) - count += 1 - if count > 200: - logger.debug("not all players closed within the allotted time, flushing player manager") - self.mw.pm.players.clear() - break - - self.mw.pm.ordinals.clear() - self.mw.pm.sizes.clear() - self.mw.cameraPanel.syncGUI() - else: - lstCamera = self.mw.cameraPanel.lstCamera - if lstCamera: - cameras = [lstCamera.item(x) for x in range(lstCamera.count())] - for camera in cameras: - self.mw.cameraPanel.setCurrentCamera(camera.uri()) - self.mw.cameraPanel.onItemDoubleClicked(camera) - except Exception as ex: - logger.error(ex) - - def scanAllNetworksChecked(self, state): - self.mw.settings.setValue(self.scanAllKey, state) - self.cmbInterfaces.setEnabled(not self.chkScanAllNetworks.isChecked()) - self.lblInterfaces.setEnabled(not self.chkScanAllNetworks.isChecked()) - - def getLogFilename(self): - filename = "" - if sys.platform == "win32": - filename = os.environ['HOMEPATH'] + "/.cache/onvif-gui/logs.txt" - else: - filename = os.environ['HOME'] + "/.cache/onvif-gui/logs.txt" - return filename - - def btnShowLogsClicked(self): - filename = self.getLogFilename() - if not self.dlgLog: - self.dlgLog = LogDialog(self.mw) - self.dlgLog.readLogFile(filename) - self.dlgLog.exec() - - def btnHelpClicked(self): - result = webbrowser.get().open("https://github.com/sr99622/libonvif#readme-ov-file") - if not result: - webbrowser.get().open("https://github.com/sr99622/libonvif") - - def radDiscoverToggled(self, checked): - self.chkScanAllNetworks.setEnabled(checked) - if self.chkScanAllNetworks.isChecked(): - self.lblInterfaces.setEnabled(False) - self.cmbInterfaces.setEnabled(False) - else: - self.lblInterfaces.setEnabled(checked) - self.cmbInterfaces.setEnabled(checked) - self.mw.settings.setValue(self.discoveryTypeKey, int(checked)) - - def radEntireDiskToggled(self, checked): - self.spnDiskLimit.setEnabled(not checked) - self.mw.settings.setValue(self.entireDiskKey, int(checked)) - - def spnDiskLimitChanged(self, value): - self.mw.settings.setValue(self.diskLimitKey, value) - - def cmbSoundFilesChanged(self, value): - self.mw.settings.setValue(self.alarmSoundFileKey, value) - - def sldAlarmVolumeChanged(self, value): - self.mw.settings.setValue(self.alarmSoundVolumeKey, value) - - def getDecoder(self): - result = avio.AV_HWDEVICE_TYPE_NONE - if self.cmbDecoder.currentText() == "CUDA": - result = avio.AV_HWDEVICE_TYPE_CUDA - if self.cmbDecoder.currentText() == "VAAPI": - result = avio.AV_HWDEVICE_TYPE_VAAPI - if self.cmbDecoder.currentText() == "VDPAU": - result = avio.AV_HWDEVICE_TYPE_VDPAU - if self.cmbDecoder.currentText() == "DXVA2": - result = avio.AV_HWDEVICE_TYPE_DXVA2 - if self.cmbDecoder.currentText() == "D3D11VA": - result = avio.AV_HWDEVICE_TYPE_D3D11VA - return result - - def btnAddCameraClicked(self): - dlg = AddCameraDialog(self.mw) - if dlg.exec(): - ip_address = dlg.txtIPAddress.text() - onvif_port = dlg.txtOnvifPort.text() - if not len(onvif_port): - onvif_port = "80" - xaddrs = f'http://{ip_address}:{onvif_port}/onvif/device_service' - logger.debug(f'Attempting to add camera manually using xaddrs: {xaddrs}') - data = onvif.Data() - data.getData = self.mw.cameraPanel.getData - data.getCredential = self.mw.cameraPanel.getCredential - data.setXAddrs(xaddrs) - data.setDeviceService(xaddrs) - data.manual_fill() - - def chkManageDiskUsageChanged(self): - if self.chkManageDiskUsage.isChecked(): - ret = QMessageBox.warning(self, "** WARNING **", - "You are giving full control of the archive directory to this program. " - "Any files contained within this directory or its sub-directories are subject to deletion. " - "You should only enable this feature if you are sure that this is ok.\n\n" - "Are you sure you want to continue?", - QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel) - if ret == QMessageBox.StandardButton.Cancel: - self.chkManageDiskUsage.setChecked(False) - self.mw.settings.setValue(self.mangageDiskUsagekey, int(self.chkManageDiskUsage.isChecked())) - - def dirArchiveChanged(self, path): - logger.debug(f'Video archive directory changed to {path}') - self.mw.settings.setValue(self.archiveKey, path) - max_size = int(self.getMaximumDirectorySize()) - self.spnDiskLimit.setMaximum(max_size) - lbl = f'Auto Manage (max {max_size} GB)' - self.chkManageDiskUsage.setText(lbl) - disk_limit = min(int(self.mw.settings.value(self.diskLimitKey, 100)), max_size) - self.spnDiskLimit.setValue(disk_limit) - self.chkManageDiskUsageChanged() - - def dirPicturesChanged(self, path): - logger.debug(f'Picture directory changed to {path}') - self.mw.settings.setValue(self.pictureKey, path) - - def getMaximumDirectorySize(self): - # compute disk space available for archive directory in GB - d = self.dirArchive.txtDirectory.text() - d_size = 0 - for dirpath, dirnames, filenames in os.walk(d): - for f in filenames: - fp = os.path.join(dirpath, f) - if not os.path.islink(fp): - d_size += os.path.getsize(fp) - total, used, free = shutil.disk_usage(d) - max_available = (free + d_size - 10000000000) / 1000000000 - return max_available - - def getDirectorySize(self, d): - total_size = 0 - for dirpath, dirnames, filenames in os.walk(d): - for f in filenames: - fp = os.path.join(dirpath, f) - if not os.path.islink(fp): - total_size += os.path.getsize(fp) - - return total_size - + self.general.btnCloseAll.setText("Start All Cameras") diff --git a/onvif-gui/gui/panels/videopanel.py b/onvif-gui/gui/panels/videopanel.py index 6f75aa5..5c3adb5 100644 --- a/onvif-gui/gui/panels/videopanel.py +++ b/onvif-gui/gui/panels/videopanel.py @@ -21,7 +21,7 @@ from PyQt6.QtWidgets import QGridLayout, QWidget, QCheckBox, \ QLabel, QComboBox, QVBoxLayout from PyQt6.QtCore import Qt -from gui.onvif.datastructures import MediaSource +from gui.enums import MediaSource class VideoPanel(QWidget): def __init__(self, mw): diff --git a/onvif-gui/gui/player.py b/onvif-gui/gui/player.py new file mode 100644 index 0000000..5b67f15 --- /dev/null +++ b/onvif-gui/gui/player.py @@ -0,0 +1,231 @@ +import os +from time import sleep +from datetime import datetime +from pathlib import Path +from PyQt6.QtCore import pyqtSignal, QObject, QTimer, QFile +from collections import deque +import shutil +import avio +from loguru import logger + +class PlayerSignals(QObject): + start = pyqtSignal() + stop = pyqtSignal() + +class Player(avio.Player): + def __init__(self, uri, mw): + super().__init__(uri) + self.mw = mw + self.signals = PlayerSignals() + self.image = None + self.rendering = False + self.desired_aspect = 0 + self.systemTabSettings = None + self.analyze_video = False + self.analyze_audio = False + self.videoModelSettings = None + self.audioModelSettings = None + self.detection_count = deque() + self.last_image = None + self.last_render = None + self.timer = None + + self.boxes = [] + self.labels = [] + self.scores = [] + + self.save_image_filename = None + self.pipe_output_start_time = None + self.estimated_file_size = 0 + self.packet_drop_frame_counter = 0 + self.last_msg = "" + + self.alarm_state = 0 + self.last_alarm_state = 0 + self.file_progress = 0.0 + + if (len(uri)): + self.timer = QTimer() + self.timer.setInterval(self.mw.settingsPanel.alarm.spnLagTime.value() * 1000) + self.timer.setSingleShot(True) + self.timer.timeout.connect(self.timeout) + self.signals.start.connect(self.timer.start) + self.signals.stop.connect(self.timer.stop) + + def requestShutdown(self): + self.setAlarmState(0) + self.analyze_video = False + self.analyze_audio = False + self.request_reconnect = False + self.running = False + + def setAlarmState(self, state): + self.alarm_state = int(state) + + record_enable = self.systemTabSettings.record_enable if self.systemTabSettings else False + record_alarm = self.systemTabSettings.record_alarm if self.systemTabSettings else False + if camera := self.mw.cameraPanel.getCamera(self.uri): + manual_recording = camera.manual_recording if camera else False + profile = camera.getRecordProfile() if camera else None + player = self.mw.pm.getPlayer(profile.uri()) if profile else None + + if state: + self.signals.start.emit() + if record_enable and record_alarm: + if player: + if not player.isRecording(): + d = self.mw.settingsPanel.storage.dirArchive.txtDirectory.text() + if self.mw.settingsPanel.storage.chkManageDiskUsage.isChecked(): + player.manageDirectory(d) + filename = player.getPipeOutFilename(d) + if filename: + player.toggleRecording(filename) + if current_camera := self.mw.cameraPanel.getCurrentCamera(): + if camera.serial_number() == current_camera.serial_number(): + self.mw.cameraPanel.syncGUI() + else: + self.signals.stop.emit() + if record_alarm and not manual_recording: + if player: + if player.isRecording(): + player.toggleRecording("") + if current_camera := self.mw.cameraPanel.getCurrentCamera(): + if camera.serial_number() == current_camera.serial_number(): + self.mw.cameraPanel.syncGUI() + + def timeout(self): + self.setAlarmState(0) + + def getPipeOutFilename(self, d): + filename = None + camera = self.mw.cameraPanel.getCamera(self.uri) + if camera: + d = self.mw.settingsPanel.storage.dirArchive.txtDirectory.text() + root = d + "/" + camera.text() + Path(root).mkdir(parents=True, exist_ok=True) + self.pipe_output_start_time = datetime.now() + filename = '{0:%Y%m%d%H%M%S}'.format(self.pipe_output_start_time) + filename = root + "/" + filename + ".mp4" + self.setMetaData("title", camera.text()) + return filename + + def estimateFileSize(self): + # duration is in seconds, cameras report bitrate in kbps, result in bytes + result = 0 + bitrate = 0 + profile = self.mw.cameraPanel.getProfile(self.uri) + if profile: + audio_bitrate = min(profile.audio_bitrate(), 128) + video_bitrate = min(profile.bitrate(), 16384) + bitrate = video_bitrate + audio_bitrate + result = (bitrate * 1000 / 8) * self.mw.STD_FILE_DURATION + self.estimated_file_size = result + return result + + def getCommittedSize(self): + committed = 0 + for player in self.mw.pm.players: + if player.isRecording(): + committed += player.estimateFileSize() - player.pipeBytesWritten() + return committed + + def getDirectorySize(self, d): + total_size = 0 + for dirpath, dirnames, filenames in os.walk(d): + for f in filenames: + fp = os.path.join(dirpath, f) + if not os.path.islink(fp): + try: + total_size += os.path.getsize(fp) + except FileNotFoundError: + pass + + dir_size = "{:.2f}".format(total_size / 1000000000) + self.mw.settingsPanel.storage.grpDiskUsage.setTitle(f'Disk Usage (currently {dir_size} GB)') + return total_size + + def getOldestFile(self, d): + oldest_file = None + oldest_time = None + for dirpath, dirnames, filenames in os.walk(d): + for f in filenames: + fp = os.path.join(dirpath, f) + if not os.path.islink(fp): + + stem = Path(fp).stem + if len(stem) == 14 and stem.isnumeric(): + try: + if oldest_file is None: + oldest_file = fp + oldest_time = os.path.getmtime(fp) + else: + file_time = os.path.getmtime(fp) + if file_time < oldest_time: + oldest_file = fp + oldest_time = file_time + except FileNotFoundError: + pass + return oldest_file + + def getMaximumDirectorySize(self, d): + estimated_file_size = self.estimateFileSize() + space_committed = self.getCommittedSize() + allowed_space = min(self.mw.settingsPanel.storage.spnDiskLimit.value() * 1000000000, shutil.disk_usage(d)[2]) + return allowed_space - (space_committed + estimated_file_size) + + def manageDirectory(self, d): + while self.getDirectorySize(d) > self.getMaximumDirectorySize(d): + oldest_file = self.getOldestFile(d) + if oldest_file: + QFile.remove(oldest_file) + #logger.debug(f'File has been deleted by auto process: {oldest_file}') + else: + logger.debug("Unable to find the oldest file for deletion during disk management") + break + + def handleAlarm(self, state): + if self.analyze_video or self.analyze_audio: + if state: + self.setAlarmState(1) + if self.alarm_state: + if self.isCameraStream(): + if self.systemTabSettings.sound_alarm_enable: + filename = f'{self.mw.getLocation()}/gui/resources/{self.mw.settingsPanel.alarm.cmbSoundFiles.currentText()}' + if self.systemTabSettings.sound_alarm_once: + if self.alarm_state != self.last_alarm_state: + self.mw.playMedia(filename, True) + if self.systemTabSettings.sound_alarm_loop: + p = self.mw.pm.getPlayer(filename) + if not p: + self.mw.playMedia(filename, True) + self.last_alarm_state = self.alarm_state + else: + self.setAlarmState(0) + + def getFrameRate(self): + frame_rate = self.getVideoFrameRate() + if frame_rate <= 0: + profile = self.mw.cameraPanel.getProfile(self.uri) + if profile: + frame_rate = profile.frame_rate() + return frame_rate + + def processModelOutput(self): + + while self.rendering: + sleep(0.001) + + sum = 0 + + if len(self.detection_count) > self.videoModelSettings.sampleSize - 1 and len(self.detection_count): + self.detection_count.popleft() + + if len(self.boxes): + self.detection_count.append(1) + else: + self.detection_count.append(0) + + for count in self.detection_count: + sum += count + + return sum diff --git a/onvif-gui/modules/audio/sample.py b/onvif-gui/modules/audio/sample.py index aeb2a3e..6e270c5 100644 --- a/onvif-gui/modules/audio/sample.py +++ b/onvif-gui/modules/audio/sample.py @@ -27,7 +27,7 @@ from PyQt6.QtGui import QPainter, QColorConstants, QColor from PyQt6.QtCore import QPointF, Qt, QRectF from gui.components import WarningBar, Indicator -from gui.onvif.datastructures import MediaSource +from gui.enums import MediaSource MODULE_NAME = "sample" diff --git a/onvif-gui/modules/video/motion.py b/onvif-gui/modules/video/motion.py index cff9b4d..87f9061 100644 --- a/onvif-gui/modules/video/motion.py +++ b/onvif-gui/modules/video/motion.py @@ -25,7 +25,7 @@ from PyQt6.QtCore import Qt from loguru import logger from gui.components import WarningBar, Indicator -from gui.onvif.datastructures import MediaSource +from gui.enums import MediaSource MODULE_NAME = "motion" diff --git a/onvif-gui/modules/video/yolox.py b/onvif-gui/modules/video/yolox.py index 454a604..b0eba38 100644 --- a/onvif-gui/modules/video/yolox.py +++ b/onvif-gui/modules/video/yolox.py @@ -27,7 +27,7 @@ import numpy as np from pathlib import Path from gui.components import ComboSelector, FileSelector, ThresholdSlider, TargetSelector - from gui.onvif.datastructures import MediaSource + from gui.enums import MediaSource from PyQt6.QtWidgets import QWidget, QGridLayout, QLabel, QCheckBox, QMessageBox, \ QGroupBox, QDialog, QSpinBox from PyQt6.QtCore import Qt, QSize, QObject, pyqtSignal @@ -499,20 +499,25 @@ def postprocess(self, outputs, player): alarmState = result >= player.videoModelSettings.limit if result else False player.handleAlarm(alarmState) + show_alarm = False if camera := self.mw.cameraPanel.getCamera(player.uri): if camera.isFocus(): + show_alarm = True + if not player.isCameraStream(): + show_alarm = True + + if show_alarm: + level = 0 + if player.videoModelSettings.limit: + level = result / player.videoModelSettings.limit + else: + if result: + level = 1.0 - level = 0 - if player.videoModelSettings.limit: - level = result / player.videoModelSettings.limit - else: - if result: - level = 1.0 - - self.mw.videoConfigure.selTargets.barLevel.setLevel(level) + self.mw.videoConfigure.selTargets.barLevel.setLevel(level) - if alarmState: - self.mw.videoConfigure.selTargets.indAlarm.setState(1) + if alarmState: + self.mw.videoConfigure.selTargets.indAlarm.setState(1) def callback(self, infer_request, player): try: diff --git a/onvif-gui/pyproject.toml b/onvif-gui/pyproject.toml index c0fcb85..1e0e4cc 100644 --- a/onvif-gui/pyproject.toml +++ b/onvif-gui/pyproject.toml @@ -19,7 +19,7 @@ [project] name = "onvif-gui" -version = "2.1.1" +version = "2.2.4" dynamic = ["gui-scripts"] description = "A client gui for Onvif" readme = "README.md" @@ -38,11 +38,8 @@ classifiers = [ ] dependencies = [ - 'libonvif==3.2.0', 'avio==3.2.0', 'numpy', 'loguru', 'opencv-python', - 'PyQt6-Qt6==6.6.1; platform_system != "Darwin"', - 'pyqt6==6.6.1; platform_system != "Darwin"', - 'PyQt6-Qt6; platform_system == "Darwin"', - 'pyqt6; platform_system == "Darwin"' + 'libonvif==3.2.1', 'avio==3.2.1', 'liblivemedia==1.0.1', 'numpy', 'loguru', + 'opencv-python', 'PyQt6-Qt6', 'pyqt6' ] [project.urls] diff --git a/onvif-gui/setup.py b/onvif-gui/setup.py index 0b2fe08..03d12ef 100644 --- a/onvif-gui/setup.py +++ b/onvif-gui/setup.py @@ -24,7 +24,7 @@ setup( name="onvif-gui", - version="2.1.1", + version="2.2.4", author="Stephen Rhodes", author_email="sr99622@gmail.com", description="GUI program for onvif",