diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..6b8710a71 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.git diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..682db9004 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +web/api/lib +web/skins/classic/js/jquery-1.11.3.js +web/skins/classic/js/jquery.js +web/tools/mootools diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..fa4d6b6e5 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,29 @@ +"use strict"; + +module.exports = { + "env": { + "browser": true, + }, + "extends": ["google"], + "rules": { + "brace-style": "off", + "camelcase": "off", + "comma-dangle": "off", + "key-spacing": "off", + "max-len": "off", + "new-cap": ["error", { + capIsNewExceptions: ["Error", "Warning", "Debug", "Polygon_calcArea", "Play", "Stop"], + newIsCapExceptionPattern: "^Asset\.." + }], + "no-array-constructor": "off", + "no-caller": "off", + "no-new-object": "off", + "no-unused-vars": "off", + "no-var": "off", + "object-curly-spacing": "off", + "prefer-rest-params": "off", + "quotes": "off", + "require-jsdoc": "off", + "spaced-comment": "off", + }, +}; diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..309d79785 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,21 @@ +You should only file an issue if you found a bug. Feature and enhancement requests, general discussions and support questions should occur in one of the following areas: + +- The ZoneMinder IRC channel - irc.freenode.net #zoneminder +- The [ZoneMinder Forum](https://forums.zoneminder.com/) + +**Do not post feature or enhancement requests, general discussions or support questions here.** + +Make sure you are running the latest version of ZoneMinder before reporting an issue. + +**ZoneMinder Version (`zmaudit.pl -v`):** + +**Are you using a development snapshot / git checkout? If so, what is the latest commit? (`git rev-parse HEAD`):** + +**Linux Distribution and Version (`cat /etc/os-release` or `cat /etc/redhat-release`):** + +**If the issue concerns a camera, provide the make, model, frame rate, resolution and ZoneMinder Source Type:** + +**Relevant log lines:** +``` +log lines here +``` diff --git a/.gitignore b/.gitignore index bd66e684a..fa1e16d4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,71 @@ -configure -config.h.in -config.h.in~ -autom4te.cache -aclocal.m4 -depcomp -install-sh -missing -mkinstalldirs -stamp-h1 -stamp-h.in -scripts/ZoneMinder/blib -Makefile.in +# If you are adding to this file, please ensure sorting is maintained. If using vim, you can use :%sort u +CMakeCache.txt +CMakeFiles/ Makefile -docs/_build +Makefile.in +aclocal.m4 +autom4te.cache +cmake/cmake_uninstall.cmake +cmake_install.cmake compile config.guess config.h +config.h.in +config.h.in~ config.log config.status config.sub +configure +db/CMakeFiles/ +db/cmake_install.cmake db/zm_create.sql +depcomp +docs/_build +install-sh +misc/CMakeFiles/ misc/apache.conf +misc/cmake_install.cmake misc/com.zoneminder.systemctl.policy misc/com.zoneminder.systemctl.rules misc/logrotate.conf misc/syslog.conf +misc/zoneminder-tmpfiles.conf +misc/zoneminder.service +missing +mkinstalldirs +onvif/CMakeFiles/ +onvif/cmake_install.cmake +onvif/modules/CMakeFiles/ onvif/modules/MYMETA.json onvif/modules/MYMETA.yml +onvif/modules/MakefilePerl +onvif/modules/cmake_install.cmake +onvif/modules/output/ +onvif/modules/pm_to_blib +onvif/proxy/CMakeFiles/ onvif/proxy/MYMETA.json onvif/proxy/MYMETA.yml -scripts/ZoneMinder/Makefile.old +onvif/proxy/MakefilePerl +onvif/proxy/cmake_install.cmake +onvif/proxy/output/ +onvif/proxy/pm_to_blib +onvif/scripts/CMakeFiles/ +onvif/scripts/cmake_install.cmake +scripts/CMakeFiles/ +scripts/ZoneMinder/CMakeFiles/ scripts/ZoneMinder/MYMETA.json scripts/ZoneMinder/MYMETA.yml +scripts/ZoneMinder/Makefile.old +scripts/ZoneMinder/MakefilePerl +scripts/ZoneMinder/blib +scripts/ZoneMinder/cmake_install.cmake scripts/ZoneMinder/lib/ZoneMinder/Base.pm scripts/ZoneMinder/lib/ZoneMinder/Config.pm scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm scripts/ZoneMinder/lib/ZoneMinder/Memory.pm +scripts/ZoneMinder/output/ scripts/ZoneMinder/pm_to_blib +scripts/cmake_install.cmake scripts/zm scripts/zmaudit.pl scripts/zmcamtool.pl @@ -49,14 +78,18 @@ scripts/zmfilter.pl scripts/zmlogrotate.conf scripts/zmpkg.pl scripts/zmsystemctl.pl +scripts/zmtelemetry.pl scripts/zmtrack.pl scripts/zmtrigger.pl scripts/zmupdate.pl scripts/zmvideo.pl scripts/zmwatch.pl scripts/zmx10.pl -src/.deps/ src/*.o +src/.deps/ +src/CMakeFiles/ +src/cmake_install.cmake +src/libzm.a src/zm_config.h src/zm_config_defines.h src/zma @@ -64,6 +97,19 @@ src/zmc src/zmf src/zms src/zmu +stamp-h.in +stamp-h1 +web/CMakeFiles/ +web/api/CMakeFiles/ +web/api/app/Config/bootstrap.php +web/api/app/Config/core.php +web/api/cmake_install.cmake +web/cmake_install.cmake web/includes/config.php +web/tools/mootools/CMakeFiles/ +web/tools/mootools/cmake_install.cmake +web/tools/mootools/mootools-core.js +web/tools/mootools/mootools-more.js zm.conf zmconfgen.pl +zmlinkcontent.sh diff --git a/.travis.yml b/.travis.yml index 183d22e57..3afa0f02a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,48 +1,56 @@ language: cpp sudo: required dist: trusty +git: + depth: 9999999 notifications: - irc: "chat.freenode.net#zoneminder-dev" + irc: chat.freenode.net#zoneminder-dev branches: except: - - modern + - modern +cache: ccache addons: - sauce_connect: - username: "zoneminder" - access_key: "046ec7c1-c598-4e7e-949a-f86e725d1722" + ssh_known_hosts: zmrepo.zoneminder.com + apt: + sources: + - sourceline: ppa:iconnor/zoneminder + - key_url: http://keyserver.ubuntu.com:11371/pks/lookup?op=get&search=0x4D0BF748776FFB04 + packages: + - gdebi + - yum-utils + - patch + - git + - curl + - sshfs + - sed env: - global: - - LD_LIBRARY_PATH="/usr/local/lib:/opt/libjpeg-turbo/lib:$LD_LIBRARY_PATH" - - DEB_HOST_GNU_TYPE=$(dpkg-architecture -qDEB_HOST_GNU_TYPE) - - DEB_BUILD_GNU_TYPE=$(dpkg-architecture -qDEB_BUILD_GNU_TYPE) - - CFLAGS="-DZM_FFMPEG_CVS -DHAVE_LIBCRYPTO -I/usr/local/include" - - CXXFLAGS="$CFLAGS" matrix: - - ZM_BUILDMETHOD=cmake + - OS=el DIST=6 + - OS=el DIST=7 + - OS=fedora DIST=24 + - OS=fedora DIST=25 + - OS=ubuntu DIST=trusty + - OS=ubuntu DIST=xenial + - OS=ubuntu DIST=trusty ARCH=i386 + - OS=ubuntu DIST=xenial ARCH=i386 compiler: - - gcc - - clang -before_install: - - sudo apt-get update -qq - - sudo apt-get install -y -qq libpolkit-gobject-1-dev zlib1g-dev apache2 mysql-server php5 php5-mysql build-essential libmysqlclient-dev libssl-dev libbz2-dev libpcre3-dev libdbi-perl libarchive-zip-perl libdate-manip-perl libdevice-serialport-perl libmime-perl libwww-perl libdbd-mysql-perl libsys-mmap-perl yasm automake autoconf cmake libjpeg-turbo8-dev apache2-mpm-prefork libapache2-mod-php5 php5-cli libtheora-dev libvorbis-dev libvpx-dev libx264-dev libvlccore-dev libvlc-dev 2>&1 > /dev/null -install: - - git clone -b n3.0 --depth=1 git://source.ffmpeg.org/ffmpeg.git - - cd ffmpeg - - ./configure --enable-shared --enable-swscale --enable-gpl --enable-libx264 --enable-libvpx --enable-libvorbis --enable-libtheora - - make -j `grep processor /proc/cpuinfo|wc -l` - - sudo make install - - sudo make install-libs -before_script: - - cd $TRAVIS_BUILD_DIR - - mysql -uroot -e "CREATE DATABASE IF NOT EXISTS zm" - - mysql -uroot -e "GRANT ALL ON zm.* TO 'zmuser'@'localhost' IDENTIFIED BY 'zmpass'"; - - mysql -uroot -e "FLUSH PRIVILEGES" +- gcc +services: +- mysql +- docker script: - - if [ "$ZM_BUILDMETHOD" = "cmake" ]; then cmake -DCMAKE_INSTALL_PREFIX="/usr"; fi - - make - - sudo make install - - if [ "$ZM_BUILDMETHOD" = "cmake" ]; then sudo ./zmlinkcontent.sh; fi - - mysql -uzmuser -pzmpass < db/zm_create.sql - - mysql -uzmuser -pzmpass zm < db/test.monitor.sql - - sudo zmpkg.pl start - - sudo zmfilter.pl -f purgewhenfull +- utils/packpack/startpackpack.sh + +before_deploy: +- openssl aes-256-cbc -K $encrypted_62a62750aa73_key -iv $encrypted_62a62750aa73_iv -in ./utils/packpack/deploy_rsa.enc -out /tmp/deploy_rsa -d +- eval "$(ssh-agent -s)" +- chmod 600 /tmp/deploy_rsa +- ssh-add /tmp/deploy_rsa + +deploy: + provider: script + skip_cleanup: true + script: utils/packpack/rsync_xfer.sh + on: + branch: master + diff --git a/CHANGELOG.md b/CHANGELOG.md index 471066ac3..ac76176c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,7 +82,7 @@ - header typo corrections [\#1058](https://github.com/ZoneMinder/ZoneMinder/pull/1058) ([onlyjob](https://github.com/onlyjob)) - quick fix for \#1055: make sure our mmap fd is \> 2 [\#1057](https://github.com/ZoneMinder/ZoneMinder/pull/1057) ([connortechnology](https://github.com/connortechnology)) - Fix sgfault caused by the privacy mask stuff [\#1056](https://github.com/ZoneMinder/ZoneMinder/pull/1056) ([connortechnology](https://github.com/connortechnology)) -- link to cambozola pacakge, rather than download during build [\#1054](https://github.com/ZoneMinder/ZoneMinder/pull/1054) ([knnniggett](https://github.com/knnniggett)) +- link to cambozola package, rather than download during build [\#1054](https://github.com/ZoneMinder/ZoneMinder/pull/1054) ([knnniggett](https://github.com/knnniggett)) - redhat rpm packaging modifications [\#1052](https://github.com/ZoneMinder/ZoneMinder/pull/1052) ([knnniggett](https://github.com/knnniggett)) - remove core.php, modify core.php.default [\#1049](https://github.com/ZoneMinder/ZoneMinder/pull/1049) ([knnniggett](https://github.com/knnniggett)) - Google recaptcha [\#1048](https://github.com/ZoneMinder/ZoneMinder/pull/1048) ([pliablepixels](https://github.com/pliablepixels)) @@ -167,7 +167,7 @@ - alter the logic of ReadData. New behaviour is documented. [\#870](https://github.com/ZoneMinder/ZoneMinder/pull/870) ([connortechnology](https://github.com/connortechnology)) - analysis optimisations [\#867](https://github.com/ZoneMinder/ZoneMinder/pull/867) ([connortechnology](https://github.com/connortechnology)) - Don't die if db goes away during logging [\#866](https://github.com/ZoneMinder/ZoneMinder/pull/866) ([connortechnology](https://github.com/connortechnology)) -- Move iostream inclusion in zm.h and declare explicitely the namespace [\#859](https://github.com/ZoneMinder/ZoneMinder/pull/859) ([manupap1](https://github.com/manupap1)) +- Move iostream inclusion in zm.h and declare explicitly the namespace [\#859](https://github.com/ZoneMinder/ZoneMinder/pull/859) ([manupap1](https://github.com/manupap1)) - Fix detection of deprecated libav / ffmpeg functions [\#858](https://github.com/ZoneMinder/ZoneMinder/pull/858) ([manupap1](https://github.com/manupap1)) - Correct bareword config entries with newer {} style [\#856](https://github.com/ZoneMinder/ZoneMinder/pull/856) ([connortechnology](https://github.com/connortechnology)) - update german translation [\#854](https://github.com/ZoneMinder/ZoneMinder/pull/854) ([seeebek](https://github.com/seeebek)) diff --git a/CMakeLists.txt b/CMakeLists.txt index b23f49e80..cc1067903 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,9 @@ # Created by mastertheknife (Kfir Itzhak) # For more information and installation, see the INSTALL file # -cmake_minimum_required (VERSION 2.6) +cmake_minimum_required (VERSION 2.8.7) project (zoneminder) -set(zoneminder_VERSION "1.30.0") +file (STRINGS "version" zoneminder_VERSION) # make API version a minor of ZM version set(zoneminder_API_VERSION "${zoneminder_VERSION}.1") @@ -66,6 +66,24 @@ set(CMAKE_CXX_FLAGS_DEBUG "-Wall -D__STDC_CONSTANT_MACROS -g") set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") +# GCC below 6.0 doesn't support __target__("fpu=neon") attribute, required for compiling ARM Neon code, otherwise compilation fails. +# Must use -mfpu=neon compiler flag instead, but only do that for processors that support neon, otherwise strip the neon code alltogether, +# because passing -fmpu=neon is unsafe to processors that don't support neon +IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" AND CMAKE_SYSTEM_NAME MATCHES "Linux") + IF(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) + EXEC_PROGRAM(grep ARGS " neon " "/proc/cpuinfo" OUTPUT_VARIABLE neonoutput RETURN_VALUE neonresult) + IF(neonresult EQUAL 0) + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mfpu=neon") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpu=neon") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -mfpu=neon") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mfpu=neon") + ELSE(neonresult EQUAL 0) + add_definitions(-DZM_STRIP_NEON=1) + message(STATUS "ARM Neon is not available on this processor. Neon functions will be absent") + ENDIF(neonresult EQUAL 0) + ENDIF(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) +ENDIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" AND CMAKE_SYSTEM_NAME MATCHES "Linux") + # Modules that we need: include (GNUInstallDirs) include (CheckIncludeFile) @@ -85,7 +103,9 @@ mark_as_advanced( ZM_PERL_MM_PARMS ZM_PERL_SEARCH_PATH ZM_TARGET_DISTRO - ZM_CONFIG_DIR) + ZM_CONFIG_DIR + ZM_CONFIG_SUBDIR + ZM_SYSTEMD) set(ZM_RUNDIR "/var/run/zm" CACHE PATH "Location of transient process files, default: /var/run/zm") @@ -118,6 +138,8 @@ set(ZM_WEB_GROUP "" CACHE STRING # Advanced set(ZM_CONFIG_DIR "/${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH "Location of ZoneMinder configuration, default system config directory") +set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/conf.d" CACHE PATH + "Location of ZoneMinder configuration subfolder, default: ZM_CONFIG_DIR/conf.d") set(ZM_EXTRA_LIBS "" CACHE STRING "A list of optional libraries, separated by semicolons, e.g. ssl;theora") set(ZM_MYSQL_ENGINE "InnoDB" CACHE STRING @@ -147,30 +169,18 @@ set(ZM_PERL_SEARCH_PATH "" CACHE PATH where ZM_PERL_MM_PARMS has been modified such that ZoneMinder's Perl modules are installed outside Perl's default search path.") set(ZM_TARGET_DISTRO "" CACHE STRING - "Build ZoneMinder for a specific distribution. Currently, valid names are: f22, f23, el6, el7, OS13") + "Build ZoneMinder for a specific distribution. Currently, valid names are: fc24, fc25, el6, el7, OS13, FreeBSD") +set(ZM_SYSTEMD "OFF" CACHE BOOL + "Set to ON to force building ZM with systemd support. default: OFF") # Reassign some variables if a target distro has been specified -if((ZM_TARGET_DISTRO STREQUAL "f22") OR (ZM_TARGET_DISTRO STREQUAL "f23")) - set(ZM_RUNDIR "/var/run/zoneminder") - set(ZM_SOCKDIR "/var/lib/zoneminder/sock") - set(ZM_TMPDIR "/var/lib/zoneminder/temp") - set(ZM_LOGDIR "/var/log/zoneminder") - set(ZM_CONFIG_DIR "/etc/zm") - set(ZM_WEBDIR "/usr/share/zoneminder/www") - set(ZM_CGIDIR "/usr/libexec/zoneminder/cgi-bin") -elseif(ZM_TARGET_DISTRO STREQUAL "el6") - set(ZM_RUNDIR "/var/run/zoneminder") - set(ZM_SOCKDIR "/var/lib/zoneminder/sock") - set(ZM_TMPDIR "/var/lib/zoneminder/temp") - set(ZM_LOGDIR "/var/log/zoneminder") - set(ZM_WEBDIR "/usr/share/zoneminder/www") - set(ZM_CGIDIR "/usr/libexec/zoneminder/cgi-bin") -elseif(ZM_TARGET_DISTRO STREQUAL "el7") +if((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) set(ZM_RUNDIR "/var/run/zoneminder") set(ZM_SOCKDIR "/var/lib/zoneminder/sock") set(ZM_TMPDIR "/var/lib/zoneminder/temp") set(ZM_LOGDIR "/var/log/zoneminder") set(ZM_CONFIG_DIR "/etc/zm") + set(ZM_CONFIG_SUBDIR "/etc/zm/conf.d") set(ZM_WEBDIR "/usr/share/zoneminder/www") set(ZM_CGIDIR "/usr/libexec/zoneminder/cgi-bin") elseif(ZM_TARGET_DISTRO STREQUAL "OS13") @@ -190,10 +200,11 @@ elseif(ZM_TARGET_DISTRO STREQUAL "FreeBSD") set(ZM_WEB_USER "www") set(ZM_WEB_GROUP "www") set(ZM_CONFIG_DIR "/usr/local/etc/zm") + set(ZM_CONFIG_SUBDIR "/usr/local/etc/zm/conf.d") set(ZM_WEBDIR "/usr/local/share/zoneminder/www") set(ZM_CGIDIR "/usr/local/libexec/zoneminder/cgi-bin") set(ZM_PERL_MM_PARMS "INSTALLDIRS=site") -endif((ZM_TARGET_DISTRO STREQUAL "f22") OR (ZM_TARGET_DISTRO STREQUAL "f23")) +endif((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) # Required for certain checks to work set(CMAKE_EXTRA_INCLUDE_FILES @@ -204,6 +215,11 @@ include_directories("${CMAKE_BINARY_DIR}") # This is required to enable searching in lib64 (if exists), do not change set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ON) +# Set the systemd flag if systemd is autodetected or ZM_SYSTEMD has been set +if(ZM_SYSTEMD OR (IS_DIRECTORY /usr/lib/systemd/system) OR (IS_DIRECTORY /lib/systemd/system)) + set(WITH_SYSTEMD 1) +endif(ZM_SYSTEMD OR (IS_DIRECTORY /usr/lib/systemd/system) OR (IS_DIRECTORY /lib/systemd/system)) + # System checks check_include_file("libv4l1-videodev.h" HAVE_LIBV4L1_VIDEODEV_H) if(NOT HAVE_LIBV4L1_VIDEODEV_H) @@ -385,13 +401,13 @@ find_library(MYSQLCLIENT_LIBRARIES mysqlclient PATH_SUFFIXES mysql) if(MYSQLCLIENT_LIBRARIES) set(HAVE_LIBMYSQLCLIENT 1) list(APPEND ZM_BIN_LIBS "${MYSQLCLIENT_LIBRARIES}") - find_path(MYSQLCLIENT_INCLUDE_DIR mysql/mysql.h) + find_path(MYSQLCLIENT_INCLUDE_DIR mysql.h PATH_SUFFIXES mysql) if(MYSQLCLIENT_INCLUDE_DIR) include_directories("${MYSQLCLIENT_INCLUDE_DIR}") set(CMAKE_REQUIRED_INCLUDES "${MYSQLCLIENT_INCLUDE_DIR}") endif(MYSQLCLIENT_INCLUDE_DIR) mark_as_advanced(FORCE MYSQLCLIENT_LIBRARIES MYSQLCLIENT_INCLUDE_DIR) - check_include_file("mysql/mysql.h" HAVE_MYSQL_H) + check_include_file("mysql.h" HAVE_MYSQL_H) if(NOT HAVE_MYSQL_H) message(FATAL_ERROR "ZoneMinder requires MySQL headers - check that MySQL development packages are installed") @@ -401,6 +417,59 @@ else(MYSQLCLIENT_LIBRARIES) "ZoneMinder requires mysqlclient but it was not found on your system") endif(MYSQLCLIENT_LIBRARIES) +# x264 (using find_library and find_path) +find_library(X264_LIBRARIES x264) +if(X264_LIBRARIES) + set(HAVE_LIBX264 1) + list(APPEND ZM_BIN_LIBS "${X264_LIBRARIES}") + find_path(X264_INCLUDE_DIR x264.h) + if(X264_INCLUDE_DIR) + include_directories("${X264_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${X264_INCLUDE_DIR}") + endif(X264_INCLUDE_DIR) + mark_as_advanced(FORCE X264_LIBRARIES X264_INCLUDE_DIR) + check_include_files("stdint.h;x264.h" HAVE_X264_H) + set(optlibsfound "${optlibsfound} x264") +else(X264_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} x264") +endif(X264_LIBRARIES) + +# mp4v2 (using find_library and find_path) +find_library(MP4V2_LIBRARIES mp4v2) +if(MP4V2_LIBRARIES) + set(HAVE_LIBMP4V2 1) + list(APPEND ZM_BIN_LIBS "${MP4V2_LIBRARIES}") + + # mp4v2/mp4v2.h + find_path(MP4V2_INCLUDE_DIR mp4v2/mp4v2.h) + if(MP4V2_INCLUDE_DIR) + include_directories("${MP4V2_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") + endif(MP4V2_INCLUDE_DIR) + check_include_file("mp4v2/mp4v2.h" HAVE_MP4V2_MP4V2_H) + + # mp4v2.h + find_path(MP4V2_INCLUDE_DIR mp4v2.h) + if(MP4V2_INCLUDE_DIR) + include_directories("${MP4V2_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") + endif(MP4V2_INCLUDE_DIR) + check_include_file("mp4v2.h" HAVE_MP4V2_H) + + # mp4.h + find_path(MP4V2_INCLUDE_DIR mp4.h) + if(MP4V2_INCLUDE_DIR) + include_directories("${MP4V2_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") + endif(MP4V2_INCLUDE_DIR) + check_include_file("mp4.h" HAVE_MP4_H) + + mark_as_advanced(FORCE MP4V2_LIBRARIES MP4V2_INCLUDE_DIR) + set(optlibsfound "${optlibsfound} mp4v2") +else(MP4V2_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} mp4v2") +endif(MP4V2_LIBRARIES) + set(PATH_FFMPEG "") set(OPT_FFMPEG "no") # Do not check for ffmpeg if ZM_NO_FFMPEG is on @@ -491,6 +560,23 @@ if(NOT ZM_NO_FFMPEG) set(optlibsnotfound "${optlibsnotfound} SWScale") endif(SWSCALE_LIBRARIES) + # rescale (using find_library and find_path) + find_library(AVRESAMPLE_LIBRARIES avresample) + if(AVRESAMPLE_LIBRARIES) + set(HAVE_LIBAVRESAMPLE 1) + list(APPEND ZM_BIN_LIBS "${AVRESAMPLE_LIBRARIES}") + find_path(AVRESAMPLE_INCLUDE_DIR "libavresample/avresample.h" /usr/include/ffmpeg) + if(AVRESAMPLE_INCLUDE_DIR) + include_directories("${AVRESAMPLE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${AVRESAMPLE_INCLUDE_DIR}") + endif(AVRESAMPLE_INCLUDE_DIR) + mark_as_advanced(FORCE AVRESAMPLE_LIBRARIES AVRESAMPLE_INCLUDE_DIR) + check_include_file("libavresample/avresample.h" HAVE_LIBAVRESAMPLE_AVRESAMPLE_H) + set(optlibsfound "${optlibsfound} AVResample") + else(AVRESAMPLE_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} AVResample") + endif(AVRESAMPLE_LIBRARIES) + # Find the path to the ffmpeg executable find_program(FFMPEG_EXECUTABLE NAMES ffmpeg avconv @@ -523,6 +609,13 @@ if(NOT ZM_NO_LIBVLC) endif(LIBVLC_LIBRARIES) endif(NOT ZM_NO_LIBVLC) +find_package(Boost 1.36.0) +if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_INCLUDES "${Boost_INCLUDE_DIRS}") + list(APPEND ZM_BIN_LIBS "${Boost_LIBRARIES}") +endif() + # *** END OF LIBRARY CHECKS *** # Check for gnutls or crypto @@ -658,12 +751,14 @@ endif(NOT ZM_WEB_GROUP) message(STATUS "Using web user: ${ZM_WEB_USER}") message(STATUS "Using web group: ${ZM_WEB_GROUP}") -# Check for polkit -find_package(Polkit) -if(NOT POLKIT_FOUND) - message(FATAL_ERROR - "Running ZoneMinder requires polkit. Building ZoneMinder requires the polkit development package.") -endif(NOT POLKIT_FOUND) +if(WITH_SYSTEMD) + # Check for polkit + find_package(Polkit) + if(NOT POLKIT_FOUND) + message(FATAL_ERROR + "Running ZoneMinder requires polkit. Building ZoneMinder requires the polkit development package.") + endif(NOT POLKIT_FOUND) +endif(WITH_SYSTEMD) # Some variables that zm expects set(ZM_PID "${ZM_RUNDIR}/zm.pid") @@ -685,6 +780,11 @@ else(ZM_PERL_SEARCH_PATH) set(EXTRA_PERL_LIB "# Include from system perl paths only") endif(ZM_PERL_SEARCH_PATH) +# If this is an out-of-source build, copy the files we need to the binary directory +if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf.d" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/conf.d") +endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) + # Generate files from the .in files configure_file(zm.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zm.conf" @ONLY) configure_file(zoneminder-config.cmake "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY) @@ -708,13 +808,11 @@ if(ZM_ONVIF) endif(ZM_ONVIF) # Process distro subdirectories -if((ZM_TARGET_DISTRO STREQUAL "f22") OR (ZM_TARGET_DISTRO STREQUAL "f23")) - add_subdirectory(distros/fedora) -elseif((ZM_TARGET_DISTRO STREQUAL "el6") OR (ZM_TARGET_DISTRO STREQUAL "el7")) +if((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) add_subdirectory(distros/redhat) elseif(ZM_TARGET_DISTRO STREQUAL "OS13") add_subdirectory(distros/opensuse) -endif((ZM_TARGET_DISTRO STREQUAL "f22") OR (ZM_TARGET_DISTRO STREQUAL "f23")) +endif((ZM_TARGET_DISTRO MATCHES "^el") OR (ZM_TARGET_DISTRO MATCHES "^fc")) # Print optional libraries detection status message(STATUS "Optional libraries found:${optlibsfound}") @@ -731,8 +829,9 @@ else(zmconfgen_result EQUAL 0) "ZoneMinder configuration generator failed. Exit code: ${zmconfgen_result}") endif(zmconfgen_result EQUAL 0) -# Install zm.conf +# Install zm.conf and conf.d subfolder install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm.conf" DESTINATION "${ZM_CONFIG_DIR}") +install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/conf.d/" DESTINATION "${ZM_CONFIG_SUBDIR}") # Uninstall target configure_file( @@ -742,3 +841,10 @@ configure_file( add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake) +# Configure CCache if available +find_program(CCACHE_FOUND ccache) +if(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) +endif(CCACHE_FOUND) + diff --git a/COPYING b/COPYING index d60c31a97..d159169d1 100644 --- a/COPYING +++ b/COPYING @@ -1,12 +1,12 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - Preamble + Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public @@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to +the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not @@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. - - GNU GENERAL PUBLIC LICENSE + + GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains @@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions: License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) - + These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in @@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. - + 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is @@ -225,7 +225,7 @@ impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - + 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License @@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - NO WARRANTY + NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN @@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it @@ -303,17 +303,16 @@ the "copyright" line and a pointer to where the full notice is found. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: - Gnomovision version 69, Copyright (C) year name of author + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. @@ -336,5 +335,5 @@ necessary. Here is a sample; alter the names: This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General +library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. diff --git a/Dockerfile b/Dockerfile index 26df17d2b..5486703a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,75 @@ -# ZoneMinder +# ZoneMinder, you need the GIT repository code and submodules (git submodule update --init --recursive) -FROM ubuntu:trusty -MAINTAINER Kyle Johnson - -# Let the container know that there is no tty -ENV DEBIAN_FRONTEND noninteractive +FROM ubuntu:xenial +MAINTAINER Markos Vakondios # Resynchronize the package index files -RUN apt-get update && apt-get install -y \ - libpolkit-gobject-1-dev build-essential libmysqlclient-dev libssl-dev libbz2-dev libpcre3-dev \ - libdbi-perl libarchive-zip-perl libdate-manip-perl libdevice-serialport-perl libmime-perl libpcre3 \ - libwww-perl libdbd-mysql-perl libsys-mmap-perl yasm cmake libjpeg-turbo8-dev \ - libjpeg-turbo8 libtheora-dev libvorbis-dev libvpx-dev libx264-dev libmp4v2-dev libav-tools mysql-client \ - apache2 php5 php5-mysql apache2-mpm-prefork libapache2-mod-php5 php5-cli openssh-server \ - mysql-server libvlc-dev libvlc5 libvlccore-dev libvlccore7 vlc-data libcurl4-openssl-dev \ - libavformat-dev libswscale-dev libavutil-dev libavcodec-dev libavfilter-dev \ - libavresample-dev libavdevice-dev libpostproc-dev libv4l-dev libtool libnetpbm10-dev \ - libmime-lite-perl dh-autoreconf dpatch +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + apache2 \ + build-essential \ + cmake \ + dh-autoreconf \ + dpatch \ + libapache2-mod-php \ + libarchive-zip-perl \ + libavcodec-dev \ + libavdevice-dev \ + libavfilter-dev \ + libavformat-dev \ + libavresample-dev \ + libav-tools \ + libavutil-dev \ + libbz2-dev \ + libcurl4-openssl-dev \ + libdate-manip-perl \ + libdbd-mysql-perl \ + libdbi-perl \ + libdevice-serialport-perl \ + libjpeg-turbo8 \ + libjpeg-turbo8-dev \ + libmime-lite-perl \ + libmime-perl \ + libmp4v2-dev \ + libmysqlclient-dev \ + libnetpbm10-dev \ + libpcre3 \ + libpcre3-dev \ + libpolkit-gobject-1-dev \ + libpostproc-dev \ + libssl-dev \ + libswscale-dev \ + libsys-mmap-perl \ + libtheora-dev \ + libtool \ + libv4l-dev \ + libvlc5 \ + libvlccore8 \ + libvlccore-dev \ + libvlc-dev \ + libvorbis-dev \ + libvpx-dev \ + libwww-perl \ + libx264-dev \ + mysql-client \ + mysql-server \ + php \ + php-cli \ + php-mysql \ + vlc-data \ + yasm \ + && rm -rf /var/lib/apt/lists/* # Copy local code into our container -ADD . /ZoneMinder +ADD cmake /ZoneMinder/cmake/ +ADD db /ZoneMinder/db/ +ADD misc /ZoneMinder/misc/ +ADD onvif /ZoneMinder/onvif/ +ADD scripts /ZoneMinder/scripts/ +ADD src /ZoneMinder/src/ +ADD umutils /ZoneMinder/umutils/ +ADD web /ZoneMinder/web/ +ADD cmakecacheimport.sh CMakeLists.txt version zm.conf.in zmconfgen.pl.in zmlinkcontent.sh.in zoneminder-config.cmake /ZoneMinder/ # Change into the ZoneMinder directory WORKDIR /ZoneMinder @@ -31,11 +81,8 @@ WORKDIR /ZoneMinder #RUN ./configure --with-libarch=lib/$DEB_HOST_GNU_TYPE --disable-debug --host=$DEB_HOST_GNU_TYPE --build=$DEB_BUILD_GNU_TYPE --with-mysql=/usr --with-webdir=/var/www/zm --with-ffmpeg=/usr --with-cgidir=/usr/lib/cgi-bin --with-webuser=www-data --with-webgroup=www-data --enable-mmap=yes --enable-onvif ZM_SSL_LIB=openssl ZM_DB_USER=zm ZM_DB_PASS=zm RUN cmake . -# Build ZoneMinder -RUN make - -# Install ZoneMinder -RUN make install +# Build & install ZoneMinder +RUN make && make install # ensure writable folders RUN ./zmlinkcontent.sh @@ -43,29 +90,20 @@ RUN ./zmlinkcontent.sh # Adding the start script ADD utils/docker/start.sh /tmp/start.sh -# Ensure we can run this -# TODO - Files ADD'ed have 755 already...why do we need this? -RUN chmod 755 /tmp/start.sh - -# give files in /usr/local/share/zoneminder/ +# Settings rights for /usr/local/share/zoneminder/ RUN chown -R www-data:www-data /usr/local/share/zoneminder/ -# Creating SSH privilege escalation dir -RUN mkdir /var/run/sshd - # Adding apache virtual hosts file -ADD utils/docker/apache-vhost /etc/apache2/sites-available/000-default.conf -ADD utils/docker/phpdate.ini /etc/php5/apache2/conf.d/25-phpdate.ini +RUN cp misc/apache.conf /etc/apache2/sites-available/000-default.conf -# Set the root passwd -RUN echo 'root:root' | chpasswd - -# Add a user we can actually login with -RUN useradd -m -s /bin/bash -G sudo zoneminder -RUN echo 'zoneminder:zoneminder' | chpasswd - -# Expose ssh and http ports +# Expose http port EXPOSE 80 -EXPOSE 22 -CMD "/tmp/start.sh" +VOLUME /var/lib/zoneminder/images /var/lib/zoneminder/events /var/lib/mysql /var/log/zm + +# To speed up configuration testing, we put it here +ADD utils/docker /ZoneMinder/utils/docker/ + +CMD /ZoneMinder/utils/docker/setup.sh && /ZoneMinder/utils/docker/start.sh >/var/log/start.log 2>&1 & /bin/bash + +# Run example docker run -it -p 1080:80 -e PHP_TIMEZONE='Europe/Paris' -v /disk/zoneminder/events:/var/lib/zoneminder/events -v /disk/zoneminder/images:/var/lib/zoneminder/images -v /disk/zoneminder/mysql:/var/lib/mysql -v /disk/zoneminder/logs:/var/log/zm --name zoneminder zoneminder/zoneminder diff --git a/README.md b/README.md index 42a570cc1..460fe62ff 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This is the recommended method to install ZoneMinder onto your system. ZoneMinde - RHEL/CentOS and clones via [zmrepo](http://zmrepo.zoneminder.com/) - Fedora via [zmrepo](http://zmrepo.zoneminder.com/) - OpenSuse via [third party repository](http://www.zoneminder.com/wiki/index.php/Installing_using_ZoneMinder_RPMs_for_SuSE) -- Maegia from their default repository +- Mageia from their default repository If a repository that hosts ZoneMinder packages is not available for your distro, then you are encouraged to build your own package, rather than build from source. While each distro is different in ways that set it apart from all the others, they are often similar enough to allow you to adapt another distro's package building instructions to your own. diff --git a/code_of_conduct.md b/code_of_conduct.md new file mode 100644 index 000000000..aa6195359 --- /dev/null +++ b/code_of_conduct.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at abuse@zoneminder.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/conf.d/README b/conf.d/README new file mode 100644 index 000000000..dedfcd11f --- /dev/null +++ b/conf.d/README @@ -0,0 +1,19 @@ +conf.d/README + +Any changes to ZoneMinder's configuration should be made here in this folder, +rather than directly editing the default zm.conf file. + +ZoneMinder will process each file in this folder with a ".conf" extension. +Each "Var = Value" pair, in each config file, will be loaded into ZoneMinder's +running configuration, overriding any variables with the same name found in the +default zm.conf file. + +After creating a custom config file, don't forget to set the proper file and +owner permission on it. For example, this is typically what you should do after +saving the config file to disk: + + sudo chown root:apache *.conf + sudo chmod 640 *.conf + +Substitute "apache" with the name of the web server user account on your system. + diff --git a/conf.d/zm-server-host.conf b/conf.d/zm-server-host.conf new file mode 100644 index 000000000..7c61c200d --- /dev/null +++ b/conf.d/zm-server-host.conf @@ -0,0 +1,7 @@ +# Do NOT set ZM_SERVER_HOST if you are not using Multi-Server +# You have been warned +# +# The name specified here must have a corresponding entry +# in the Servers tab under Options +ZM_SERVER_HOST= + diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 029f20989..8f702e748 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -193,6 +193,7 @@ CREATE TABLE `Events` ( `Length` decimal(10,2) NOT NULL default '0.00', `Frames` int(10) unsigned default NULL, `AlarmFrames` int(10) unsigned default NULL, + `DefaultVideo` VARCHAR( 64 ) NOT NULL, `TotScore` int(10) unsigned NOT NULL default '0', `AvgScore` smallint(5) unsigned default '0', `MaxScore` smallint(5) unsigned default '0', @@ -322,7 +323,7 @@ CREATE TABLE `Monitors` ( `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local', `Function` enum('None','Monitor','Modect','Record','Mocord','Nodect') NOT NULL default 'Monitor', `Enabled` tinyint(3) unsigned NOT NULL default '1', - `LinkedMonitors` varchar(255) NOT NULL default '', + `LinkedMonitors` varchar(255), `Triggers` set('X10') NOT NULL default '', `Device` tinytext NOT NULL default '', `Channel` tinyint(3) unsigned NOT NULL default '0', @@ -331,26 +332,30 @@ CREATE TABLE `Monitors` ( `V4LCapturesPerFrame` tinyint(3) unsigned, `Protocol` varchar(16) NOT NULL default '', `Method` varchar(16) NOT NULL default '', - `Host` varchar(64) NOT NULL default '', + `Host` varchar(64), `Port` varchar(8) NOT NULL default '', `SubPath` varchar(64) NOT NULL default '', - `Path` varchar(255) NOT NULL default '', + `Path` varchar(255), `Options` varchar(255) not null default '', - `User` varchar(64) NOT NULL default '', - `Pass` varchar(64) NOT NULL default '', + `User` varchar(64), + `Pass` varchar(64), `Width` smallint(5) unsigned NOT NULL default '0', `Height` smallint(5) unsigned NOT NULL default '0', `Colours` tinyint(3) unsigned NOT NULL default '1', `Palette` int(10) unsigned NOT NULL default '0', `Orientation` enum('0','90','180','270','hori','vert') NOT NULL default '0', `Deinterlacing` int(10) unsigned NOT NULL default '0', - `RTSPDescribe` tinyint(1) unsigned NOT NULL default '0', + `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' , + `VideoWriter` TINYINT NOT NULL DEFAULT '0', + `EncoderParameters` TEXT, + `RecordAudio` TINYINT NOT NULL DEFAULT '0', + `RTSPDescribe` tinyint(1) unsigned, `Brightness` mediumint(7) NOT NULL default '-1', `Contrast` mediumint(7) NOT NULL default '-1', `Hue` mediumint(7) NOT NULL default '-1', `Colour` mediumint(7) NOT NULL default '-1', `EventPrefix` varchar(32) NOT NULL default 'Event-', - `LabelFormat` varchar(64) NOT NULL default '%N - %y/%m/%d %H:%M:%S', + `LabelFormat` varchar(64), `LabelX` smallint(5) unsigned NOT NULL default '0', `LabelY` smallint(5) unsigned NOT NULL default '0', `LabelSize` smallint(5) unsigned NOT NULL DEFAULT '1', @@ -371,14 +376,14 @@ CREATE TABLE `Monitors` ( `RefBlendPerc` tinyint(3) unsigned NOT NULL default '6', `AlarmRefBlendPerc` tinyint(3) unsigned NOT NULL default '6', `Controllable` tinyint(3) unsigned NOT NULL default '0', - `ControlId` int(10) unsigned NOT NULL default '0', + `ControlId` int(10) unsigned, `ControlDevice` varchar(255) default NULL, `ControlAddress` varchar(255) default NULL, `AutoStopTimeout` decimal(5,2) default NULL, `TrackMotion` tinyint(3) unsigned NOT NULL default '0', - `TrackDelay` smallint(5) unsigned NOT NULL default '0', + `TrackDelay` smallint(5) unsigned, `ReturnLocation` tinyint(3) NOT NULL default '-1', - `ReturnDelay` smallint(5) unsigned NOT NULL default '0', + `ReturnDelay` smallint(5) unsigned, `DefaultView` enum('Events','Control') NOT NULL default 'Events', `DefaultRate` smallint(5) unsigned NOT NULL default '100', `DefaultScale` smallint(5) unsigned NOT NULL default '100', @@ -467,7 +472,7 @@ CREATE TABLE `Users` ( `Id` int(10) unsigned NOT NULL auto_increment, `Username` varchar(32) character set latin1 collate latin1_bin NOT NULL default '', `Password` varchar(64) NOT NULL default '', - `Language` varchar(8) NOT NULL default '', + `Language` varchar(8), `Enabled` tinyint(3) unsigned NOT NULL default '1', `Stream` enum('None','View') NOT NULL default 'None', `Events` enum('None','View','Edit') NOT NULL default 'None', @@ -476,8 +481,8 @@ CREATE TABLE `Users` ( `Groups` enum('None','View','Edit') NOT NULL default 'None', `Devices` enum('None','View','Edit') NOT NULL default 'None', `System` enum('None','View','Edit') NOT NULL default 'None', - `MaxBandwidth` varchar(16) NOT NULL default '', - `MonitorIds` tinytext NOT NULL, + `MaxBandwidth` varchar(16), + `MonitorIds` tinytext, PRIMARY KEY (`Id`), UNIQUE KEY `UC_Username` (`Username`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -594,6 +599,8 @@ INSERT INTO `Controls` VALUES (NULL,'Wanscam HW0025','Libvlc','WanscamHW0025', 1 INSERT INTO `Controls` VALUES (NULL,'IPCC 7210W','Libvlc','IPCC7210W', 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 16, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 350, 0, 0, 1, 0, 10, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10, 0, 0, 0, 0); INSERT INTO `Controls` VALUES (NULL,'Vivotek ePTZ','Remote','Vivotek_ePTZ',0,0,1,1,0,0,0,1,0,0,0,0,1,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,1,0,5,0,0,1,0,0,0,0,1,0,5,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Netcat ONVIF','Ffmpeg','Netcat',0,0,1,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,100,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,100,5,5,0,0,0,1,255,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Keekoon','Remote','Keekoon', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); +INSERT INTO `Controls` VALUES (NULL,'HikVision','Local','',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,1,1,1,1,0,0,0,1,1,0,0,0,0,1,1,100,0,0,1,0,0,0,0,1,1,100,1,0,0,0); -- -- Add some monitor preset values @@ -666,6 +673,8 @@ INSERT INTO MonitorPresets VALUES (NULL,'Foscam FI9821W FFMPEG H.264','Ffmpeg',N INSERT INTO MonitorPresets VALUES (NULL,'Loftek Sentinel PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,'','80','/videostream.cgi?user=&pwd=&resolution=32&rate=11',NULL,640,480,4,NULL,1,'13','',':@',100,100); INSERT INTO MonitorPresets VALUES (NULL,'Airlink 777W PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,':@','80','/cgi/mjpg/mjpg.cgi',NULL,640,480,4,NULL,1,'7','',':@',100,100); INSERT INTO MonitorPresets VALUES (NULL,'SunEyes SP-P1802SWPTZ','Libvlc','/dev/video','0',255,'','rtpMulti','','80','rtsp://:554/11','',1920,1080,0,0.00,1,'16','-speed=64',':',100,33); +INSERT INTO MonitorPresets VALUES (NULL,'Qihan IP, 1280x720, RTP/RTSP','Ffmpeg','rtsp','rtpRtsp',255,'rtsp','rtpRtsp',NULL,554,'rtsp:///tcp_live/ch0_0',NULL,1280,720,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,'Qihan IP, 1920x1080, RTP/RTSP','Ffmpeg','rtsp','rtpRtsp',255,'rtsp','rtpRtsp',NULL,554,'rtsp:///tcp_live/ch0_0',NULL,1920,1080,3,NULL,0,NULL,NULL,NULL,100,100); -- -- Add some zone preset values diff --git a/db/zm_update-1.30.1.sql b/db/zm_update-1.30.1.sql new file mode 100644 index 000000000..f3d5698d3 --- /dev/null +++ b/db/zm_update-1.30.1.sql @@ -0,0 +1,7 @@ +-- +-- This updates a 1.30.0 database to 1.30.1 +-- +-- Alter type of Messages column from VARCHAR(255) to TEXT +-- + +ALTER TABLE Logs MODIFY Message TEXT; diff --git a/db/zm_update-1.30.2.sql b/db/zm_update-1.30.2.sql new file mode 100644 index 000000000..50c318f6b --- /dev/null +++ b/db/zm_update-1.30.2.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.30.1 database to 1.30.2 +-- + +ALTER TABLE Users MODIFY MonitorIds TEXT NOT NULL; diff --git a/db/zm_update-1.30.3.sql b/db/zm_update-1.30.3.sql new file mode 100644 index 000000000..d3a0c740d --- /dev/null +++ b/db/zm_update-1.30.3.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.30.2 database to 1.30.3 +-- +-- No changes required +-- diff --git a/db/zm_update-1.30.4.sql b/db/zm_update-1.30.4.sql new file mode 100644 index 000000000..267562291 --- /dev/null +++ b/db/zm_update-1.30.4.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.30.3 database to 1.30.4 +-- +-- No changes required +-- diff --git a/db/zm_update-1.31.0.sql b/db/zm_update-1.31.0.sql new file mode 100644 index 000000000..06e94c10e --- /dev/null +++ b/db/zm_update-1.31.0.sql @@ -0,0 +1,97 @@ +-- +-- This updates a 1.29.0 database to 1.30.0 +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'SaveJPEGs' + ) > 0, +"SELECT 'Column SaveJPEGs exists in Monitors'", +"ALTER TABLE `Monitors` ADD `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' AFTER `Deinterlacing`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'VideoWriter' + ) > 0, +"SELECT 'Column VideoWriter exists in Monitors'", +"ALTER TABLE `Monitors` ADD `VideoWriter` TINYINT NOT NULL DEFAULT '0' AFTER `SaveJPEGs`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'EncoderParameters' + ) > 0, +"SELECT 'Column EncoderParameters exists in Monitors'", +"ALTER TABLE `Monitors` ADD `EncoderParameters` TEXT NOT NULL AFTER `VideoWriter`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Events' + AND table_schema = DATABASE() + AND column_name = 'DefaultVideo' + ) > 0, +"SELECT 'Column DefaultVideo exists in Events'", +"ALTER TABLE `Events` ADD `DefaultVideo` VARCHAR( 64 ) NOT NULL AFTER `AlarmFrames`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'RecordAudio' + ) > 0, +"SELECT 'Column RecordAudio exists in Monitors'", +"ALTER TABLE `Monitors` ADD `RecordAudio` TINYINT NOT NULL DEFAULT '0' AFTER `EncoderParameters`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +-- +-- The following alters various columns to allow NULLs +-- + +ALTER TABLE Monitors MODIFY Host varchar(64); +ALTER TABLE Monitors MODIFY LabelFormat varchar(64); +ALTER TABLE Monitors MODIFY LinkedMonitors varchar(255); +ALTER TABLE Monitors MODIFY Options varchar(255); +ALTER TABLE Monitors MODIFY Protocol varchar(16); +ALTER TABLE Monitors MODIFY User varchar(64); +ALTER TABLE Monitors MODIFY Pass varchar(64); +ALTER TABLE Monitors MODIFY RTSPDescribe tinyint(1) unsigned; +ALTER TABLE Monitors MODIFY ControlId int(10) unsigned; +ALTER TABLE Monitors MODIFY TrackDelay smallint(5) unsigned; +ALTER TABLE Monitors MODIFY ReturnDelay smallint(5) unsigned; +ALTER TABLE Monitors MODIFY EncoderParameters TEXT; +ALTER TABLE Monitors MODIFY Path varchar(255); +ALTER TABLE Monitors MODIFY V4LMultiBuffer tinyint(1) unsigned; + +ALTER TABLE Users MODIFY MonitorIds tinytext; +ALTER TABLE Users MODIFY Language varchar(8); +ALTER TABLE Users MODIFY MaxBandwidth varchar(16); + diff --git a/distros/debian/apache.conf b/distros/debian/apache.conf index 6f2f30fd9..b0a22c4d8 100644 --- a/distros/debian/apache.conf +++ b/distros/debian/apache.conf @@ -1,8 +1,20 @@ Alias /zm /usr/share/zoneminder/www - - Options Indexes FollowSymLinks - - DirectoryIndex index.php - - + + + Options -Indexes +ExecCGI + AllowOverride All + AddHandler fcgid-script .php + FCGIWrapper /usr/bin/php5-cgi + Order allow,deny + Allow from all + + + + + Options -Indexes +FollowSymLinks + + DirectoryIndex index.php + + + diff --git a/distros/debian/control b/distros/debian/control index ebb59b3ab..af37ee7bc 100644 --- a/distros/debian/control +++ b/distros/debian/control @@ -6,14 +6,14 @@ Build-Depends: debhelper (>= 9), cmake , libphp-serialization-perl , libgnutls28-dev | libgnutls-dev , libmysqlclient-dev | libmariadbclient-dev - , libjpeg8-dev + , libjpeg8-dev | libjpeg-dev , libpcre3-dev , libavcodec-dev, libavformat-dev (>= 3:0.svn20090204), libswscale-dev (>= 3:0.svn20090204), libavutil-dev + , libavdevice-dev , libv4l-dev (>= 0.8.3) , libbz2-dev - , libav-tools + , ffmpeg | libav-tools , libnetpbm10-dev - , libavdevice-dev , libvlccore-dev, libvlc-dev , libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev , libgcrypt11-dev, libpolkit-gobject-1-dev @@ -42,11 +42,11 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} , libsys-cpu-perl, libsys-meminfo-perl , libdata-uuid-perl , libpcre3 - , libav-tools, libavdevice53 + , ffmpeg | libav-tools, libavdevice53 | libavdevice55 | libavdevice57 , rsyslog | system-log-daemon , netpbm , libjpeg8 , zip - , libvlccore5 | libvlccore7, libvlc5 + , libvlccore5 | libvlccore7 | libvlccore8, libvlc5 , libpolkit-gobject-1-0, php5-gd Recommends: mysql-server | mariadb-server Description: Video camera security and surveillance solution diff --git a/distros/debian/postinst b/distros/debian/postinst index 39e3dfc32..20f715793 100644 --- a/distros/debian/postinst +++ b/distros/debian/postinst @@ -38,6 +38,7 @@ if [ "$1" = "configure" ]; then invoke-rc.d zoneminder stop || true zmupdate.pl --nointeractive zmupdate.pl --nointeractive -f + echo "Done Updating, starting ZoneMinder" invoke-rc.d zoneminder start || true else echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.' diff --git a/distros/debian/rules b/distros/debian/rules index 4bdc7cb0c..45a64332c 100755 --- a/distros/debian/rules +++ b/distros/debian/rules @@ -22,14 +22,15 @@ override_dh_auto_configure: -DZM_CGIDIR=/usr/lib/zoneminder/cgi-bin \ -DZM_WEB_USER=www-data \ -DZM_WEB_GROUP=www-data \ - -DCMAKE_INSTALL_SYSCONFDIR=etc/zm + -DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \ + -DZM_CONFIG_DIR="/etc/zm" override_dh_auto_install: dh_auto_install --buildsystem=cmake install -D -m 0644 debian/apache.conf $(INSTDIR)/etc/zm/apache.conf - rm $(INSTDIR)/usr/share/zoneminder/api/lib/Cake/LICENSE.txt - rm $(INSTDIR)/usr/share/zoneminder/api/.gitignore - rm -r $(INSTDIR)/usr/share/zoneminder/api/lib/Cake/Test + rm -f $(INSTDIR)/usr/share/zoneminder/www/api/lib/Cake/LICENSE.txt + rm -f $(INSTDIR)/usr/share/zoneminder/www/api/.gitignore + rm -rf $(INSTDIR)/usr/share/zoneminder/www/api/lib/Cake/Test override_dh_auto_test: # do not run tests... diff --git a/distros/fedora b/distros/fedora new file mode 120000 index 000000000..4d0827986 --- /dev/null +++ b/distros/fedora @@ -0,0 +1 @@ +redhat \ No newline at end of file diff --git a/distros/fedora/CMakeLists.txt b/distros/fedora/CMakeLists.txt deleted file mode 100644 index e6fc72add..000000000 --- a/distros/fedora/CMakeLists.txt +++ /dev/null @@ -1,52 +0,0 @@ -# CMakeLists.txt for the Fedora Target Distro. - -# Display a message to show the Fedora build options are being processed. -message([STATUS] "Starting Fedora Build Options" ...) - -# Create the ZoneMinder Apache config file -configure_file(zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY) -configure_file(zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY) -configure_file(zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY) -configure_file(zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY) - -# Download jscalendar & move files into position -file(DOWNLOAD http://iweb.dl.sourceforge.net/project/jscalendar/jscalendar/1.0/jscalendar-1.0.zip ${CMAKE_CURRENT_SOURCE_DIR}/jscalendar-1.0.zip STATUS download_jsc) -if(download_jsc EQUAL 0) -message(STATUS "Jscalander successfully downloaded. Installing...") -execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/jscalendar.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ERROR_VARIABLE unzip_jsc) -message(STATUS "Status of jscalender script was: ${unzip_jsc}") -else(download_jsc EQUAL 0) -message(STATUS "Unable to download optional jscalander. Skipping...") -endif(download_jsc EQUAL 0) - -# Create several empty folders -file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp) - -# Install the empty folders -#install(DIRECTORY run DESTINATION /var DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_WRITE GROUP_READ GROUP_EXECUTE WORLD_WRITE WORLD_READ WORLD_EXECUTE) -install(DIRECTORY sock swap DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(DIRECTORY zoneminder DESTINATION /var/log DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(DIRECTORY zoneminder DESTINATION /run DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(DIRECTORY zoneminder-upload DESTINATION /var/spool DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(DIRECTORY events images temp DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) - -# Create symlinks -install(CODE "execute_process(COMMAND ln -sf ../../../../var/lib/zoneminder/events \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/events\")") -install(CODE "execute_process(COMMAND ln -sf ../../../../var/lib/zoneminder/images \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/images\")") -install(CODE "execute_process(COMMAND ln -sf ../../../../var/lib/zoneminder/temp \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/temp\")") -install(CODE "execute_process(COMMAND ln -sf ../../../../../../var/lib/zoneminder/temp \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/api/app/tmp\")") - -# Fedora requires cambozola as a separate package so just link to it -install(CODE "execute_process(COMMAND ln -sf ../../java/cambozola.jar \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/cambozola.jar\")") - -# Install auxiliary files required to run zoneminder on Fedora -install(FILES zoneminder.conf DESTINATION /etc/httpd/conf.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) -install(FILES zoneminder.logrotate DESTINATION /etc/logrotate.d RENAME zoneminder PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) -install(FILES ../../misc/zoneminder-tmpfiles.conf DESTINATION /etc/tmpfiles.d RENAME zoneminder.conf PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) -install(FILES redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/sounds PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(FILES zoneminder.service DESTINATION /usr/lib/systemd/system PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) - -# Install jscalendar -if(unzip_jsc STREQUAL "") -install(DIRECTORY jscalendar-1.0/ DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/tools/jscalendar) -endif(unzip_jsc STREQUAL "") diff --git a/distros/fedora/README.https b/distros/fedora/README.https deleted file mode 120000 index 18f7407c1..000000000 --- a/distros/fedora/README.https +++ /dev/null @@ -1 +0,0 @@ -../redhat/README.https \ No newline at end of file diff --git a/distros/fedora/archive/zoneminder-1.24.3-runlevel.patch b/distros/fedora/archive/zoneminder-1.24.3-runlevel.patch deleted file mode 100644 index de7b49b0b..000000000 --- a/distros/fedora/archive/zoneminder-1.24.3-runlevel.patch +++ /dev/null @@ -1,11 +0,0 @@ -diff -up ./scripts/zm.in.runlevel ./scripts/zm.in ---- ./scripts/zm.in.runlevel 2010-11-28 15:22:05.000000000 -0600 -+++ ./scripts/zm.in 2011-03-24 21:39:01.973010160 -0500 -@@ -1,6 +1,6 @@ - #!/bin/sh - # description: ZoneMinder is the top Linux video camera security and surveillance solution. ZoneMinder is intended for use in single or multi-camera video security applications.Copyright: Philip Coombes, Corey DeLasaux 2003-2008 --# chkconfig: 2345 99 00 -+# chkconfig: - 99 00 - # processname: zmpkg.pl - - # Source function library. diff --git a/distros/fedora/archive/zoneminder-1.24.4-installfix.patch b/distros/fedora/archive/zoneminder-1.24.4-installfix.patch deleted file mode 100644 index 8831d597a..000000000 --- a/distros/fedora/archive/zoneminder-1.24.4-installfix.patch +++ /dev/null @@ -1,22 +0,0 @@ -diff -up ./Makefile.am.installfix ./Makefile.am ---- ./Makefile.am.installfix 2011-06-19 15:51:14.000000000 -0500 -+++ ./Makefile.am 2011-08-13 20:33:30.288587436 -0500 -@@ -21,12 +21,12 @@ EXTRA_DIST = \ - # Yes, you are correct. This is a HACK! - install-data-hook: - ( cd $(DESTDIR)$(sysconfdir); chown $(webuser):$(webgroup) $(sysconf_DATA); chmod 600 $(sysconf_DATA) ) -- ( if ! test -e $(ZM_RUNDIR); then mkdir -p $(ZM_RUNDIR); fi; if test "$(ZM_RUNDIR)" != "/var/run"; then chown $(webuser):$(webgroup) $(ZM_RUNDIR); chmod u+w $(ZM_RUNDIR); fi ) -- ( if ! test -e $(ZM_TMPDIR); then mkdir -m 700 -p $(ZM_TMPDIR); fi; if test "$(ZM_TMPDIR)" != "/tmp"; then chown $(webuser):$(webgroup) $(ZM_TMPDIR); chmod u+w $(ZM_TMPDIR); fi ) -- ( if ! test -e $(ZM_LOGDIR); then mkdir -p $(ZM_LOGDIR); fi; if test "$(ZM_LOGDIR)" != "/var/log"; then chown $(webuser):$(webgroup) $(ZM_LOGDIR); chmod u+w $(ZM_LOGDIR); fi ) -+ ( if ! test -e $(DESTDIR)$(ZM_RUNDIR); then mkdir -p $(DESTDIR)$(ZM_RUNDIR); fi; if test "$(DESTDIR)$(ZM_RUNDIR)" != "/var/run"; then chown $(webuser):$(webgroup) $(DESTDIR)$(ZM_RUNDIR); chmod u+w $(DESTDIR)$(ZM_RUNDIR); fi ) -+ ( if ! test -e $(DESTDIR)$(ZM_TMPDIR); then mkdir -m 700 -p $(DESTDIR)$(ZM_TMPDIR); fi; if test "$(DESTDIR)$(ZM_TMPDIR)" != "/tmp"; then chown $(webuser):$(webgroup) $(DESTDIR)$(ZM_TMPDIR); chmod u+w $(DESTDIR)$(ZM_TMPDIR); fi ) -+ ( if ! test -e $(DESTDIR)$(ZM_LOGDIR); then mkdir -p $(DESTDIR)$(ZM_LOGDIR); fi; if test "$(DESTDIR)$(ZM_LOGDIR)" != "/var/log"; then chown $(webuser):$(webgroup) $(DESTDIR)$(ZM_LOGDIR); chmod u+w $(DESTDIR)$(ZM_LOGDIR); fi ) - - uninstall-hook: - @-( cd $(DESTDIR)$(webdir); rm -rf events graphics images sounds temp ) -- @-( if test "$(ZM_RUNDIR)" != "/var/run"; then rm -rf $(ZM_RUNDIR); fi ) -- @-( if test "$(ZM_TMPDIR)" != "/tmp"; then rm -rf $(ZM_TMPDIR); fi ) -- @-( if test "$(ZM_LOGDIR)" != "/var/log"; then rm -rf $(ZM_LOGDIR); fi ) -+ @-( if test "$(DESTDIR)$(ZM_RUNDIR)" != "/var/run"; then rm -rf $(DESTDIR)$(ZM_RUNDIR); fi ) -+ @-( if test "$(DESTDIR)$(ZM_TMPDIR)" != "/tmp"; then rm -rf $(DESTDIR)$(ZM_TMPDIR); fi ) -+ @-( if test "$(DESTDIR)$(ZM_LOGDIR)" != "/var/log"; then rm -rf $(DESTDIR)$(ZM_LOGDIR); fi ) diff --git a/distros/fedora/archive/zoneminder-1.26.0-defaults.patch b/distros/fedora/archive/zoneminder-1.26.0-defaults.patch deleted file mode 100644 index 3e5dda67c..000000000 --- a/distros/fedora/archive/zoneminder-1.26.0-defaults.patch +++ /dev/null @@ -1,76 +0,0 @@ ---- configure.ac 2013-08-15 11:44:10.000000000 -0500 -+++ configure.ac.logdir 2013-08-17 09:20:07.326053328 -0500 -@@ -46,7 +46,7 @@ - AC_SUBST(ZM_TMPDIR,[/tmp/zm]) - fi - if test "$ZM_LOGDIR" == ""; then -- AC_SUBST(ZM_LOGDIR,[/var/log/zm]) -+ AC_SUBST(ZM_LOGDIR,[/var/log/zoneminder]) - fi - - LIB_ARCH=lib ---- scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in 2013-08-01 18:14:45.175241378 -0500 -+++ scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in.defaults 2013-08-07 18:57:42.525006149 -0500 -@@ -187,7 +187,7 @@ - }, - { - name => "ZM_PATH_ZMS", -- default => "/cgi-bin/nph-zms", -+ default => "/cgi-bin/zm/nph-zms", - description => "Web path to zms streaming server", - help => "The ZoneMinder streaming server is required to send streamed images to your browser. It will be installed into the cgi-bin path given at configuration time. This option determines what the web path to the server is rather than the local path on your machine. Ordinarily the streaming server runs in parser-header mode however if you experience problems with streaming you can change this to non-parsed-header (nph) mode by changing 'zms' to 'nph-zms'.", - type => $types{rel_path}, -@@ -276,7 +276,7 @@ - }, - { - name => "ZM_OPT_CAMBOZOLA", -- default => "no", -+ default => "yes", - description => "Is the (optional) cambozola java streaming client installed", - help => "Cambozola is a handy low fat cheese flavoured Java applet that ZoneMinder uses to view image streams on browsers such as Internet Explorer that don't natively support this format. If you use this browser it is highly recommended to install this from http://www.charliemouse.com/code/cambozola/ however if it is not installed still images at a lower refresh rate can still be viewed.", - type => $types{boolean}, -@@ -526,7 +526,7 @@ - }, - { - name => "ZM_LOG_DEBUG_FILE", -- default => "@ZM_TMPDIR@/zm_debug.log+", -+ default => "/var/log/zoneminder/zm_debug_log+", - description => "Where extra debug is output to", - help => "This option allows you to specify a different target for debug output. All components have a default log file which will norally be in /tmp or /var/log and this is where debug will be written to if this value is empty. Adding a path here will temporarily redirect debug, and other logging output, to this file. This option is a simple filename and you are debugging several components then they will all try and write to the same file with undesirable consequences. Appending a '+' to the filename will cause the file to be created with a '.' suffix containing your process id. In this way debug from each run of a component is kept separate. This is the recommended setting as it will also prevent subsequent runs from overwriting the same log. You should ensure that permissions are set up to allow writing to the file and directory specified here.", - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], -@@ -623,7 +623,7 @@ - }, - { - name => "ZM_PATH_SOCKS", -- default => "@ZM_TMPDIR@", -+ default => "/var/lib/zoneminder/sock", - description => "Path to the various Unix domain socket files that ZoneMinder uses", - help => "ZoneMinder generally uses Unix domain sockets where possible. This reduces the need for port assignments and prevents external applications from possibly compromising the daemons. However each Unix socket requires a .sock file to be created. This option indicates where those socket files go.", - type => $types{abs_path}, -@@ -639,7 +639,7 @@ - }, - { - name => "ZM_PATH_SWAP", -- default => "@ZM_TMPDIR@", -+ default => "/dev/shm", - description => "Path to location for temporary swap images used in streaming", - help => "Buffered playback requires temporary swap images to be stored for each instance of the streaming daemons. This option determines where these images will be stored. The images will actually be stored in sub directories beneath this location and will be automatically cleaned up after a period of time.", - type => $types{abs_path}, -@@ -902,7 +902,7 @@ - }, - { - name => "ZM_UPLOAD_FTP_LOC_DIR", -- default => "@ZM_TMPDIR@", -+ default => "/var/spool/zoneminder-upload", - description => "The local directory in which to create upload files", - help => "You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the local directory that ZoneMinder should use for temporary upload files. These are files that are created from events, uploaded and then deleted.", - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], -@@ -1258,7 +1258,7 @@ - }, - { - name => "ZM_OPT_CONTROL", -- default => "no", -+ default => "yes", - description => "Support controllable (e.g. PTZ) cameras", - help => "ZoneMinder includes limited support for controllable cameras. A number of sample protocols are included and others can easily be added. If you wish to control your cameras via ZoneMinder then select this option otherwise if you only have static cameras or use other control methods then leave this option off.", - type => $types{boolean}, diff --git a/distros/fedora/archive/zoneminder-1.26.3-dbinstall.patch b/distros/fedora/archive/zoneminder-1.26.3-dbinstall.patch deleted file mode 100644 index 04af9e8b2..000000000 --- a/distros/fedora/archive/zoneminder-1.26.3-dbinstall.patch +++ /dev/null @@ -1,72 +0,0 @@ ---- configure.ac 2013-09-05 10:33:08.000000000 -0500 -+++ configure.ac.dbinstall 2013-09-05 17:23:28.555553447 -0500 -@@ -1,13 +1,11 @@ - AC_PREREQ(2.59) --AC_INIT(zm,1.26.3,[http://www.zoneminder.com/forums/ - Please check FAQ first],ZoneMinder,http://www.zoneminder.com/downloads.html) -+AC_INIT(zm,1.26.3,[http://www.zoneminder.com/forums/ - Please check FAQ first],zoneminder,http://www.zoneminder.com/downloads.html) - AM_INIT_AUTOMAKE - AC_CONFIG_SRCDIR(src/zm.h) - AC_CONFIG_HEADERS(config.h) - - AC_SUBST([AM_CXXFLAGS], [-D__STDC_CONSTANT_MACROS]) - --PATH_BUILD=`pwd` --AC_SUBST(PATH_BUILD) - TIME_BUILD=`date +'%s'` - AC_SUBST(TIME_BUILD) - -@@ -354,6 +352,8 @@ AC_PROG_PERL_MODULES(X10::ActiveHome,,AC - - AC_DEFINE_DIR([BINDIR],[bindir],[Expanded binary directory]) - AC_DEFINE_DIR([LIBDIR],[libdir],[Expanded library directory]) -+AC_DEFINE_DIR([DATADIR],[datadir],[Expanded data directory]) -+AC_SUBST(PKGDATADIR,"$DATADIR/$PACKAGE") - AC_SUBST(ZM_PID,"$ZM_RUNDIR/zm.pid") - AC_DEFINE_DIR([SYSCONFDIR],[sysconfdir],[Expanded configuration directory]) - AC_SUBST(ZM_CONFIG,"$SYSCONFDIR/zm.conf") -diff -up ./db/Makefile.am.dbinstall ./db/Makefile.am ---- ./db/Makefile.am.dbinstall 2009-10-14 04:42:46.000000000 -0500 -+++ ./db/Makefile.am 2011-03-24 22:50:14.173912137 -0500 -@@ -1,7 +1,16 @@ - AUTOMAKE_OPTIONS = gnu - -+zmdbdatadir = $(pkgdatadir)/db -+ - EXTRA_DIST = \ - zm_create.sql.in \ -+ $(dbupgrade_scripts) -+ -+dist_zmdbdata_DATA = \ -+ zm_create.sql \ -+ $(dbupgrade_scripts) -+ -+dbupgrade_scripts = \ - zm_update-0.0.1.sql \ - zm_update-0.9.7.sql \ - zm_update-0.9.8.sql \ -diff -up ./scripts/zmupdate.pl.in.dbinstall ./scripts/zmupdate.pl.in ---- ./scripts/zmupdate.pl.in.dbinstall 2011-08-27 15:44:05.335602405 -0500 -+++ ./scripts/zmupdate.pl.in 2011-08-26 02:51:37.000000000 -0500 -@@ -424,7 +424,7 @@ if ( $version ) - } - else - { -- $command .= ZM_PATH_BUILD."/db"; -+ $command .= ZM_PATH_DATA."/db"; - } - $command .= "/zm_update-".$version.".sql"; - -diff -up ./zm.conf.in.dbinstall ./zm.conf.in ---- ./zm.conf.in.dbinstall 2008-07-25 04:48:16.000000000 -0500 -+++ ./zm.conf.in 2011-03-24 22:50:14.175912077 -0500 -@@ -12,8 +12,8 @@ - # Current version of ZoneMinder - ZM_VERSION=@VERSION@ - --# Path to build directory, used mostly for finding DB upgrade scripts --ZM_PATH_BUILD=@PATH_BUILD@ -+# Path to installed data directory, used mostly for finding DB upgrade scripts -+ZM_PATH_DATA=@PKGDATADIR@ - - # Build time, used to record when to trigger various checks - ZM_TIME_BUILD=@TIME_BUILD@ diff --git a/distros/fedora/archive/zoneminder-1.26.3-noffmpeg.patch b/distros/fedora/archive/zoneminder-1.26.3-noffmpeg.patch deleted file mode 100644 index e8e8d4b3e..000000000 --- a/distros/fedora/archive/zoneminder-1.26.3-noffmpeg.patch +++ /dev/null @@ -1,26 +0,0 @@ ---- configure.ac 2013-09-10 12:42:56.000000000 -0500 -+++ configure.ac.noffmpeg 2013-09-14 17:25:41.988388970 -0500 -@@ -284,15 +284,15 @@ - AC_CHECK_LIB(pcre,pcre_compile,,AC_MSG_WARN(libpcre.a may be required for remote/network camera support)) - AC_CHECK_LIB(z,zlibVersion) - AC_CHECK_LIB(x264,x264_predict_16x16_init) --AC_CHECK_LIB(avutil,av_malloc,,AC_MSG_WARN(libavutil.a may be required for MPEG streaming)) -+dnl AC_CHECK_LIB(avutil,av_malloc,,AC_MSG_WARN(libavutil.a may be required for MPEG streaming)) - # Don't bother to warn about this one --AC_CHECK_LIB(avcore,av_image_copy,,) --AC_CHECK_LIB(avcodec,avcodec_version,,AC_MSG_WARN(libavcodec.a is required for MPEG streaming)) --AC_CHECK_LIB(avformat,avformat_version,,AC_MSG_WARN(libavformat.a is required for MPEG streaming)) --#AC_CHECK_LIB(avcodec,avcodec_open,,AC_MSG_WARN(libavcodec.a is required for MPEG streaming)) --#AC_CHECK_LIB(avformat,av_new_stream,,AC_MSG_WARN(libavformat.a is required for MPEG streaming)) --AC_CHECK_LIB(avdevice,avdevice_register_all,,AC_MSG_WARN(libavdevice.a may be required for MPEG streaming)) --AC_CHECK_LIB(swscale,sws_scale,,,-lswscale) -+dnl AC_CHECK_LIB(avcore,av_image_copy,,) -+dnl AC_CHECK_LIB(avcodec,avcodec_version,,AC_MSG_WARN(libavcodec.a is required for MPEG streaming)) -+dnl AC_CHECK_LIB(avformat,avformat_version,,AC_MSG_WARN(libavformat.a is required for MPEG streaming)) -+dnl AC_CHECK_LIB(avcodec,avcodec_open,,AC_MSG_WARN(libavcodec.a is required for MPEG streaming)) -+dnl AC_CHECK_LIB(avformat,av_new_stream,,AC_MSG_WARN(libavformat.a is required for MPEG streaming)) -+dnl AC_CHECK_LIB(avdevice,avdevice_register_all,,AC_MSG_WARN(libavdevice.a may be required for MPEG streaming)) -+dnl AC_CHECK_LIB(swscale,sws_scale,,,-lswscale) - AC_CHECK_LIB(bz2,BZ2_bzCompress,,AC_MSG_WARN(zm requires libbz2.a for recent versions of ffmpeg)) - AC_CHECK_LIB(z,compress,,) - diff --git a/distros/fedora/archive/zoneminder-1.26.4-dbinstall.patch b/distros/fedora/archive/zoneminder-1.26.4-dbinstall.patch deleted file mode 100644 index 6c4a43d9d..000000000 --- a/distros/fedora/archive/zoneminder-1.26.4-dbinstall.patch +++ /dev/null @@ -1,81 +0,0 @@ ---- configure.ac 2013-09-05 10:33:08.000000000 -0500 -+++ configure.ac.dbinstall 2013-09-05 17:23:28.555553447 -0500 -@@ -1,13 +1,11 @@ - AC_PREREQ(2.59) --AC_INIT(zm,1.26.4,[http://www.zoneminder.com/forums/ - Please check FAQ first],ZoneMinder,http://www.zoneminder.com/downloads.html) -+AC_INIT(zm,1.26.4,[http://www.zoneminder.com/forums/ - Please check FAQ first],zoneminder,http://www.zoneminder.com/downloads.html) - AM_INIT_AUTOMAKE - AC_CONFIG_SRCDIR(src/zm.h) - AC_CONFIG_HEADERS(config.h) - - AC_SUBST([AM_CXXFLAGS], [-D__STDC_CONSTANT_MACROS]) - --PATH_BUILD=`pwd` --AC_SUBST(PATH_BUILD) - TIME_BUILD=`date +'%s'` - AC_SUBST(TIME_BUILD) - -@@ -354,6 +352,8 @@ AC_PROG_PERL_MODULES(X10::ActiveHome,,AC - - AC_DEFINE_DIR([BINDIR],[bindir],[Expanded binary directory]) - AC_DEFINE_DIR([LIBDIR],[libdir],[Expanded library directory]) -+AC_DEFINE_DIR([DATADIR],[datadir],[Expanded data directory]) -+AC_SUBST(PKGDATADIR,"$DATADIR/$PACKAGE") - AC_SUBST(ZM_PID,"$ZM_RUNDIR/zm.pid") - AC_DEFINE_DIR([SYSCONFDIR],[sysconfdir],[Expanded configuration directory]) - AC_SUBST(ZM_CONFIG,"$SYSCONFDIR/zm.conf") -diff -up ./db/Makefile.am.dbinstall ./db/Makefile.am ---- ./db/Makefile.am.dbinstall 2009-10-14 04:42:46.000000000 -0500 -+++ ./db/Makefile.am 2011-03-24 22:50:14.173912137 -0500 -@@ -1,7 +1,16 @@ - AUTOMAKE_OPTIONS = gnu - -+zmdbdatadir = $(pkgdatadir)/db -+ - EXTRA_DIST = \ - zm_create.sql.in \ -+ $(dbupgrade_scripts) -+ -+dist_zmdbdata_DATA = \ -+ zm_create.sql \ -+ $(dbupgrade_scripts) -+ -+dbupgrade_scripts = \ - zm_update-0.0.1.sql \ - zm_update-0.9.7.sql \ - zm_update-0.9.8.sql \ -diff -up ./scripts/zmupdate.pl.in.dbinstall ./scripts/zmupdate.pl.in ---- scripts/zmupdate.pl.in 2013-10-05 14:46:16.000000000 -0500 -+++ scripts/zmupdate.pl.in.dbinstall 2013-10-05 18:56:05.431045910 -0500 -@@ -429,7 +429,7 @@ if ( $version ) - } - else - { -- $command .= ZM_PATH_BUILD."/db"; -+ $command .= ZM_PATH_DATA."/db"; - } - $command .= "/zm_update-".$version.".sql"; - -@@ -1030,7 +1030,7 @@ if ( $version ) - if ( $version ge '1.26.0' ) { - - my @files; -- $updateDir = ZM_PATH_BUILD."/db" if ! $updateDir; -+ $updateDir = ZM_PATH_DATA."/db" if ! $updateDir; - opendir( my $dh, $updateDir ) || die "Can't open updateDir $!"; - @files = sort grep { (!/^\./) && /^zm_update\-[\d\.]+\.sql$/ && -f "$updateDir/$_" } readdir($dh); - closedir $dh; -diff -up ./zm.conf.in.dbinstall ./zm.conf.in ---- ./zm.conf.in.dbinstall 2008-07-25 04:48:16.000000000 -0500 -+++ ./zm.conf.in 2011-03-24 22:50:14.175912077 -0500 -@@ -12,8 +12,8 @@ - # Current version of ZoneMinder - ZM_VERSION=@VERSION@ - --# Path to build directory, used mostly for finding DB upgrade scripts --ZM_PATH_BUILD=@PATH_BUILD@ -+# Path to installed data directory, used mostly for finding DB upgrade scripts -+ZM_PATH_DATA=@PKGDATADIR@ - - # Build time, used to record when to trigger various checks - ZM_TIME_BUILD=@TIME_BUILD@ diff --git a/distros/fedora/archive/zoneminder.cmake.f19.spec b/distros/fedora/archive/zoneminder.cmake.f19.spec deleted file mode 100644 index 0af88f41f..000000000 --- a/distros/fedora/archive/zoneminder.cmake.f19.spec +++ /dev/null @@ -1,397 +0,0 @@ -%define zmuid $(id -un) -%define zmgid $(id -gn) -%define zmuid_final apache -%define zmgid_final apache - -%global _hardened_build 1 - -### Delete the lines below to build with ffmpeg and/or x10 -%define _without_ffmpeg 1 -%define _without_x10 1 - -Name: zoneminder -Version: 1.27 -Release: 1%{?dist} -Summary: A camera monitoring and analysis tool -Group: System Environment/Daemons -# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/ -# Mootools is inder the MIT license: http://mootools.net/ -License: GPLv2+ and LGPLv2+ and MIT -URL: http://www.zoneminder.com/ - -#Source: https://github.com/ZoneMinder/ZoneMinder/archive/v%{version}.tar.gz -Source: ZoneMinder-%{version}.tar.gz - -Patch1: zoneminder-1.26.0-defaults.patch - -BuildRequires: cmake gnutls-devel systemd-units bzip2-devel -BuildRequires: community-mysql-devel pcre-devel libjpeg-turbo-devel -BuildRequires: perl(Archive::Tar) perl(Archive::Zip) -BuildRequires: perl(Date::Manip) perl(DBD::mysql) -BuildRequires: perl(ExtUtils::MakeMaker) perl(LWP::UserAgent) -BuildRequires: perl(MIME::Entity) perl(MIME::Lite) -BuildRequires: perl(PHP::Serialization) perl(Sys::Mmap) -BuildRequires: perl(Time::HiRes) perl(Net::SFTP::Foreign) -BuildRequires: perl(Expect) perl(Sys::Syslog) -BuildRequires: gcc gcc-c++ vlc-devel libcurl-devel -%{!?_without_ffmpeg:BuildRequires: ffmpeg-devel} -%{!?_without_x10:BuildRequires: perl(X10::ActiveHome) perl(Astro::SunTime)} -# cmake needs the following installed at build time due to the way it auto-detects certain parameters -BuildRequires: httpd polkit-devel -%{!?_without_ffmpeg:BuildRequires: ffmpeg} - -Requires: httpd php php-mysql cambozola polkit -Requires: libjpeg-turbo vlc-core libcurl -Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) -Requires: perl(DBD::mysql) perl(Archive::Tar) perl(Archive::Zip) -Requires: perl(MIME::Entity) perl(MIME::Lite) perl(Net::SMTP) perl(Net::FTP) -Requires: perl(LWP::Protocol::https) -%{!?_without_ffmpeg:Requires: ffmpeg} - -Requires(post): systemd-units systemd-sysv -Requires(post): /usr/bin/gpasswd -Requires(post): /usr/bin/less -Requires(preun): systemd-units -Requires(postun): systemd-units - -%description -ZoneMinder is a set of applications which is intended to provide a complete -solution allowing you to capture, analyse, record and monitor any cameras you -have attached to a Linux based machine. It is designed to run on kernels which -support the Video For Linux (V4L) interface and has been tested with cameras -attached to BTTV cards, various USB cameras and IP network cameras. It is -designed to support as many cameras as you can attach to your computer without -too much degradation of performance. - -%prep -%setup -q -n ZoneMinder-%{version} - -%patch1 -p0 -b .defaults -#%patch2 -p0 -b .noffmpeg - -%build -%cmake \ - -DZM_TARGET_DISTRO="f19" \ -%{?_without_ffmpeg:-DZM_NO_FFMPEG=ON} \ -%{?_without_x10:-DZM_NO_X10=ON} \ - . - -make %{?_smp_mflags} - -%install -export DESTDIR=%{buildroot} -make install - -%post -if [ $1 -eq 1 ] ; then - # Initial installation - /bin/systemctl daemon-reload >/dev/null 2>&1 || : -fi - -# Allow zoneminder access to local video sources, serial ports, and x10 -/usr/bin/gpasswd -a %{zmuid_final} video -/usr/bin/gpasswd -a %{zmuid_final} dialout - -# Display the README for post installation instructions -/usr/bin/less %{_docdir}/%{name}-%{version}/README.Fedora - -%preun -if [ $1 -eq 0 ] ; then - # Package removal, not upgrade - /bin/systemctl --no-reload disable zoneminder.service > /dev/null 2>&1 || : - /bin/systemctl stop zoneminder.service > /dev/null 2>&1 || : -fi - -%postun -/bin/systemctl daemon-reload >/dev/null 2>&1 || : -if [ $1 -ge 1 ] ; then - # Package upgrade, not uninstall - /bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || : -fi - -%triggerun -- zoneminder < 1.25.0-4 -# Save the current service runlevel info -# User must manually run systemd-sysv-convert --apply zoneminder -# to migrate them to systemd targets -/usr/bin/systemd-sysv-convert --save zoneminder >/dev/null 2>&1 ||: - -# Run these because the SysV package being removed won't do them -/sbin/chkconfig --del zoneminder >/dev/null 2>&1 || : -/bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || : - - -%files -%defattr(-,root,root,-) -%doc AUTHORS COPYING README.md distros/fedora/README.Fedora distros/fedora/jscalendar-doc -%config %attr(640,root,%{zmgid_final}) /etc/zm.conf -%config(noreplace) %attr(644,root,root) /etc/httpd/conf.d/zoneminder.conf -%config(noreplace) /etc/tmpfiles.d/zoneminder.conf -%config(noreplace) /etc/logrotate.d/zoneminder - -%{_unitdir}/zoneminder.service - -%{_bindir}/zma -%{_bindir}/zmaudit.pl -%{_bindir}/zmc -%{_bindir}/zmcontrol.pl -%{_bindir}/zmdc.pl -%{_bindir}/zmf -%{_bindir}/zmfilter.pl -# zmfix removed from zoneminder 1.26.6 -#%attr(4755,root,root) %{_bindir}/zmfix -%{_bindir}/zmpkg.pl -%{_bindir}/zmtrack.pl -%{_bindir}/zmtrigger.pl -%{_bindir}/zmu -%{_bindir}/zmupdate.pl -%{_bindir}/zmvideo.pl -%{_bindir}/zmwatch.pl -%{_bindir}/zmcamtool.pl -%{_bindir}/zmsystemctl.pl -%{!?_without_x10:%{_bindir}/zmx10.pl} -%{_bindir}/zmonvif-probe.pl - -%{perl_vendorlib}/ZoneMinder* -%{perl_vendorlib}/%{_arch}-linux-thread-multi/auto/ZoneMinder* -%{perl_vendorlib}/ONVIF* -%{perl_vendorlib}/WSDiscovery* -%{perl_vendorlib}/WSSecurity* -%{perl_vendorlib}/%{_arch}-linux-thread-multi/auto/ONVIF* -%{_mandir}/man*/* -%dir %{_libexecdir}/zoneminder -%{_libexecdir}/zoneminder/cgi-bin -%dir %{_datadir}/zoneminder -%{_datadir}/zoneminder/db -%{_datadir}/zoneminder/www - -%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy -%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules - -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/events -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/images -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/sock -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/swap -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/temp -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/log/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/spool/zoneminder-upload -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /run/zoneminder - - -%changelog -* Sun Aug 03 2014 Andrew Bauer - 1.27 -- Include ONVIF support files - -* Fri Mar 14 2014 Andrew Bauer - 1.27 -- Tweak build requirements for cmake - -* Sat Feb 01 2014 Andrew Bauer - 1.27 -- Add zmcamtool.pl. Bump version for 1.27 release. - -* Mon Dec 16 2013 Andrew Bauer - 1.26.5 -- This is a bug fixe release -- RTSP fixes, cmake enhancements, couple other misc fixes - -* Mon Oct 07 2013 Andrew Bauer - 1.26.4 -- Initial cmake build. - -* Sat Oct 05 2013 Andrew Bauer - 1.26.4 -- Fedora specific path changes have been moved to zoneminder-1.26.0-defaults.patch -- All files are now part of the zoneminder source tree. Update specfile accordingly. - -* Sat Sep 21 2013 Andrew Bauer - 1.26.3 -- Initial rebuild for ZoneMinder 1.26.3 release. - -* Fri Feb 15 2013 Fedora Release Engineering - 1.25.0-13 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Mon Jan 21 2013 Adam Tkac - 1.25.0-12 -- rebuild due to "jpeg8-ABI" feature drop - -* Mon Jan 7 2013 Remi Collet - 1.25.0-11 -- fix configuration file for httpd 2.4, #871502 - -* Fri Dec 21 2012 Adam Tkac - 1.25.0-10 -- rebuild against new libjpeg - -* Thu Aug 09 2012 Jason L Tibbitts III - 1.25.0-9 -- Add patch to work around v4l2 api breakage in 3.5 kernel. - -* Sun Jul 22 2012 Fedora Release Engineering - 1.25.0-8 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild - -* Sat Jun 23 2012 Petr Pisar - 1.25.0-7 -- Perl 5.16 rebuild - -* Wed Mar 21 2012 Jason L Tibbitts III - 1.25.0-6 -- Fix stupid thinko in sql modifications. - -* Sat Feb 25 2012 Jason L Tibbitts III - 1.25.0-5 -- Clean up macro usage. - -* Sat Feb 25 2012 Jason L Tibbitts III - 1.25.0-4 -- Convert to systemd. -- Add tmpfiles.d configuration since the initscript isn't around to create - /run/zoneminder. -- Remove some pointless executable permissions. -- Add logrotate file. - -* Wed Feb 22 2012 Jason L Tibbitts III - 1.25.0-3 -- Update README.Fedora to reference systemctl and mention timezone info in - php.ini. -- Add proper default for EYEZM_LOG_TO_FILE. - - -* Thu Feb 09 2012 Jason L Tibbitts III - 1.25.0-2 -- Rebuild for new pcre. - -* Thu Jan 19 2012 Jason L Tibbitts III - 1.25.0-1 -- Update to 1.25.0 -- Fix gcc4.7 build problems. -- Drop gcc4.4 build fixes; for whatever reason they now break the build. -- Clean up old patches. -- Force setting of ZM_TMPDIR and ZM_RUNDIR. - -* Sat Jan 14 2012 Fedora Release Engineering - 1.24.4-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild - -* Thu Sep 15 2011 Jason L Tibbitts III - 1.24.4-3 -- Re-add the dist-tag that somehow got lost. - -* Thu Sep 15 2011 Jason L Tibbitts III - 1.24.4-2 -- Add patch for bug 711780 - fix syntax issue in Mapped.pm. -- Undo that patch, and undo another which was the cause of the whole mess. -- Fix up other patches so ZM_PATH_BUILD is both defined and useful. -- Make sure database creation mods actually take. -- Update Fedora-specific docs with some additional info. -- Use bundled mootools (javascript, so no guideline violation). -- Update download location. -- Update the gcrypt patch to actually work. -- Upstream changed the tarball without changing the version to patch a - vulnerability, so redownload. - -* Sun Aug 14 2011 Jason L Tibbitts III - 1.24.4-1 -- Initial attempt to upgrade to 1.24.4. -- Add patch from BZ 460310 to build against libgcrypt instead of requiring the - gnutls openssl libs. - -* Thu Jul 21 2011 Petr Sabata - 1.24.3-7.20110324svn3310 -- Perl mass rebuild - -* Wed Jul 20 2011 Petr Sabata - 1.24.3-6.20110324svn3310 -- Perl mass rebuild - -* Mon May 09 2011 Jason L Tibbitts III - 1.24.3-5.20110324svn3310 -- Bump for gnutls update. - -* Thu Mar 24 2011 Jason L Tibbitts III - 1.24.3-4.20110324svn3310 -- Update to latest 1.24.3 subversion. Turns out that what upstream was calling - 1.24.3 is really just an occasionally updated devel snapshot. -- Rebase various patches. - -* Wed Mar 23 2011 Dan Horák - 1.24.3-3 -- rebuilt for mysql 5.5.10 (soname bump in libmysqlclient) - -* Tue Feb 08 2011 Fedora Release Engineering - 1.24.3-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild - -* Tue Jan 25 2011 Jason L Tibbitts III - 1.24.3-1 -- Update to latest upstream version. -- Rebase patches. -- Initial incomplete attempt to disable v4l1 support. - -* Fri Jan 21 2011 Jason L Tibbitts III - 1.24.2-6 -- Unbundle cambozola; instead link to the separately pacakged copy. -- Remove BuildRoot:, %%clean and buildroot cleaning in %%install. -- Git rid of mixed space/tab usage by removing all tabs. -- Remove unnecessary Conflicts: line. -- Attempt to force short_open_tag on for the code directories. -- Move default location of sockets, swaps, logfiles and some temporary files to - make more sense and allow things to work better with a future selinux policy. -- Fix errors in README.Fedora. - -* Wed Jun 02 2010 Marcela Maslanova - 1.24.2-5 -- Mass rebuild with perl-5.12.0 - -* Fri Dec 4 2009 Stepan Kasal - 1.24.2-4 -- rebuild against perl 5.10.1 -- use Perl vendorarch and archlib variables correctly - -* Mon Jul 27 2009 Fedora Release Engineering - 1.24.2-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-2 -- Bump release since 1.24.2-1 was mistakenly tagged a few months ago. - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-1 -- Initial update to 1.24.2. -- Rebase patches. -- Update mootools download location. -- Update to mootools 1.2.3. -- Add additional dependencies for some optional features. - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-3 -- Remove unused Sys::Mmap perl dependency RPM is finding - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-2 -- Update gcc44 patch to disable -frepo, seems to be broken with gcc44 -- Added noffmpeg patch to make building outside mock easier - -* Sat Mar 21 2009 Martin Ebourne - 1.24.1-1 -- Patch for gcc 4.4 compilation errors -- Upgrade to 1.24.1 - -* Wed Feb 25 2009 Fedora Release Engineering - 1.23.3-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild - -* Sat Jan 24 2009 Caolán McNamara - 1.23.3-3 -- rebuild for dependencies - -* Mon Dec 15 2008 Martin Ebourne - 1.23.3-2 -- Fix permissions on zm.conf - -* Fri Jul 11 2008 Jason L Tibbitts III - 1.23.3-1 -- Initial attempt at packaging 1.23. - -* Tue Jul 1 2008 Martin Ebourne - 1.22.3-15 -- Add perl module compat dependency, bz #453590 - -* Tue May 6 2008 Martin Ebourne - 1.22.3-14 -- Remove default runlevel, bz #441315 - -* Mon Apr 28 2008 Jason L Tibbitts III - 1.22.3-13 -- Backport patch for CVE-2008-1381 from 1.23.3 to 1.22.3. - -* Tue Feb 19 2008 Fedora Release Engineering - 1.22.3-12 -- Autorebuild for GCC 4.3 - -* Thu Jan 3 2008 Martin Ebourne - 1.22.3-11 -- Fix compilation on gcc 4.3 - -* Thu Dec 6 2007 Martin Ebourne - 1.22.3-10 -- Rebuild for new openssl - -* Thu Aug 2 2007 Martin Ebourne - 1.22.3-8 -- Fix licence tag - -* Thu Jul 12 2007 Martin Ebourne - 1.22.3-7 -- Fixes from testing by Jitz including missing dependencies and database creation - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-6 -- Disable crashtrace on ppc - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-5 -- Fix uid for directories in /var/lib/zoneminder - -* Tue Jun 26 2007 Martin Ebourne - 1.22.3-4 -- Added perl Archive::Tar dependency -- Disabled web interface due to lack of access control on the event images - -* Sun Jun 10 2007 Martin Ebourne - 1.22.3-3 -- Changes recommended in review by Jason Tibbitts - -* Mon Apr 2 2007 Martin Ebourne - 1.22.3-2 -- Standardised on package name of zoneminder - -* Thu Dec 28 2006 Martin Ebourne - 1.22.3-1 -- First version. Uses some parts from zm-1.20.1 by Corey DeLasaux and Serg Oskin diff --git a/distros/fedora/archive/zoneminder.conf b/distros/fedora/archive/zoneminder.conf deleted file mode 100644 index 66b3dc146..000000000 --- a/distros/fedora/archive/zoneminder.conf +++ /dev/null @@ -1,45 +0,0 @@ -# The Zoneminder web interface has been disabled by default due to a small -# security issue in the default install. -# -# When using Zoneminder's own authentication, recorded CCTV images are -# accessible from the web directly without passing the authentication. This -# means any attacker could see your CCTV images without a password. In order -# to avoid this you can disable Zoneminder's authentication and configure -# standard Apache authentication (see the Apache documentation for details on -# this). -# -# If you still wish to use Zoneminder's own authentication, or have an -# internal site which needs no authentication, you need to delete the line -# marked below and restart Apache. - -Alias /zm "/usr/share/zoneminder/www" - - Options -Indexes +MultiViews +FollowSymLinks - AllowOverride All - - # Apache 2.4 - Require all granted - - - # Apache 2.2 - Order deny,allow - Allow from all - - # The code unfortunately uses short tags in many places - php_value short_open_tag 1 - - -ScriptAlias /cgi-bin/zm "/usr/libexec/zoneminder/cgi-bin" - - AllowOverride All - Options +ExecCGI +FollowSymLinks - - # Apache 2.4 - Require all granted - - - # Apache 2.2 - Order deny,allow - Allow from all - - diff --git a/distros/fedora/archive/zoneminder.f19.spec b/distros/fedora/archive/zoneminder.f19.spec deleted file mode 100644 index d1be12aa2..000000000 --- a/distros/fedora/archive/zoneminder.f19.spec +++ /dev/null @@ -1,478 +0,0 @@ -%define cambrev 0.931 -%define moorev 1.3.2 -%define jscrev 1.0 - -%define zmuid $(id -un) -%define zmgid $(id -gn) -%define zmuid_final apache -%define zmgid_final apache - -%global _hardened_build 1 - -### Delete the lines below to build with ffmpeg and/or x10 -%define _without_ffmpeg 1 -%define _without_x10 1 - -Name: zoneminder -Version: 1.27 -Release: 1%{?dist} -Summary: A camera monitoring and analysis tool -Group: System Environment/Daemons -# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/ -# Mootools is inder the MIT license: http://mootools.net/ -License: GPLv2+ and LGPLv2+ and MIT -URL: http://www.zoneminder.com/ - -#Source: https://github.com/ZoneMinder/ZoneMinder/archive/v%{version}.tar.gz -Source: ZoneMinder-%{version}.tar.gz -Source2: jscalendar-%{jscrev}.zip -#Source2: http://downloads.sourceforge.net/jscalendar/jscalendar-%{jscrev}.zip - -# Need to unravel the proper mootools files to grab from upstream, since the -# number of them keeps multiplying. In the meantime, rely on the ones bundled -# with zoneminder. As these are javascript, there is no guideline violation -# here. -#Source3: http://mootools.net/download/get/mootools-core-%{moorev}-full-compat-yc.js - -Patch1: zoneminder-1.24.3-runlevel.patch -Patch2: zoneminder-1.26.0-defaults.patch -%{?_without_ffmpeg:Patch3: zoneminder-1.26.3-noffmpeg.patch} - -BuildRequires: automake gnutls-devel systemd-units -BuildRequires: libtool bzip2-devel -BuildRequires: community-mysql-devel pcre-devel libjpeg-turbo-devel -BuildRequires: perl(Archive::Tar) perl(Archive::Zip) -BuildRequires: perl(Date::Manip) perl(DBD::mysql) -BuildRequires: perl(ExtUtils::MakeMaker) perl(LWP::UserAgent::Determined) -BuildRequires: perl(MIME::Entity) perl(MIME::Lite) -BuildRequires: perl(PHP::Serialization) perl(Sys::Mmap) -BuildRequires: perl(Time::HiRes) perl(Net::SFTP::Foreign) -BuildRequires: perl(Expect) perl(Sys::Syslog) -BuildRequires: gcc gcc-c++ vlc-devel libcurl-devel -BuildRequires: autoconf autoconf-archive polkit-devel -%{!?_without_ffmpeg:BuildRequires: ffmpeg-devel} -%{!?_without_x10:BuildRequires: perl(X10::ActiveHome) perl(Astro::SunTime)} - -Requires: httpd php php-mysql cambozola polkit -Requires: libjpeg-turbo libcurl vlc-core ffmpeg -Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) -Requires: perl(DBD::mysql) perl(Archive::Tar) perl(Archive::Zip) -Requires: perl(MIME::Entity) perl(MIME::Lite) perl(Net::SMTP) perl(Net::FTP) -Requires: perl(LWP::Protocol::https) - -Requires(post): systemd-units systemd-sysv -Requires(post): /usr/bin/gpasswd -Requires(post): /usr/bin/less -Requires(preun): systemd-units -Requires(postun): systemd-units - -%description -ZoneMinder is a set of applications which is intended to provide a complete -solution allowing you to capture, analyse, record and monitor any cameras you -have attached to a Linux based machine. It is designed to run on kernels which -support the Video For Linux (V4L) interface and has been tested with cameras -attached to BTTV cards, various USB cameras and IP network cameras. It is -designed to support as many cameras as you can attach to your computer without -too much degradation of performance. - -%prep -%setup -q -n ZoneMinder-%{version} - -# Unpack jscalendar and move some files around -%setup -q -D -T -a 2 -n ZoneMinder-%{version} -mkdir jscalendar-doc -pushd jscalendar-1.0 -mv *html *php doc/* README ../jscalendar-doc -rmdir doc -popd - -%patch1 -p0 -b .runlevel -%patch2 -p0 -b .defaults -%{?_without_ffmpeg:%patch3 -p0 -b .noffmpeg} - -chmod -x src/zm_event.cpp src/zm_user.h - -%build -libtoolize --force -aclocal -autoheader -automake --force-missing --add-missing -autoconf - -OPTS="" - -%configure \ - --disable-crashtrace \ - --with-libarch=%{_lib} \ - --with-mysql=%{_prefix} \ - --with-ffmpeg=%{_prefix} \ - --with-webdir=%{_datadir}/%{name}/www \ - --with-cgidir=%{_libexecdir}/%{name}/cgi-bin \ - --with-webuser=%{zmuid} \ - --with-webgroup=%{zmgid} \ - --enable-mmap=yes \ - --disable-debug \ - --with-webhost=zm.local \ - ZM_SSL_LIB="gnutls" \ - ZM_RUNDIR=/var/run/zoneminder \ - ZM_TMPDIR=/var/lib/zoneminder/temp \ -%ifarch x86_64 - CXXFLAGS="-D__STDC_CONSTANT_MACROS -msse2" \ -%else - CXXFLAGS="-D__STDC_CONSTANT_MACROS" \ -%endif - --with-extralibs="" \ - $OPTS - -make %{?_smp_mflags} -%{__perl} -pi -e 's/(ZM_WEB_USER=).*$/${1}%{zmuid_final}/;' \ - -e 's/(ZM_WEB_GROUP=).*$/${1}%{zmgid_final}/;' zm.conf - -%install -install -d %{buildroot}/%{_localstatedir}/run -make install DESTDIR=%{buildroot} \ - INSTALLDIRS=vendor -rm -rf %{buildroot}/%{perl_vendorarch} %{buildroot}/%{perl_archlib} -%{?_without_x10:%{__rm} -f %{buildroot}/%{_bindir}/zmx10.pl} - -install -m 755 -d %{buildroot}/var/log/zoneminder -for dir in events images temp -do - install -m 755 -d %{buildroot}/var/lib/zoneminder/$dir - if [ -d %{buildroot}/%{_datadir}/zoneminder/www/$dir ]; then - rmdir %{buildroot}/%{_datadir}/zoneminder/www/$dir - fi - ln -sf ../../../../var/lib/zoneminder/$dir %{buildroot}/%{_datadir}/zoneminder/www/$dir -done -install -m 755 -d %{buildroot}/var/lib/zoneminder/sock -install -m 755 -d %{buildroot}/var/lib/zoneminder/swap -install -m 755 -d %{buildroot}/var/spool/zoneminder-upload - -install -D -m 644 distros/fedora/zoneminder.conf %{buildroot}/etc/httpd/conf.d/zoneminder.conf -install -D -m 755 distros/fedora/redalert.wav %{buildroot}/%{_datadir}/zoneminder/www/sounds/redalert.wav -install -D -m 644 distros/fedora/zoneminder.service %{buildroot}/%{_unitdir}/zoneminder.service -install -D -m 644 distros/fedora/zoneminder.logrotate %{buildroot}/etc/logrotate.d/zoneminder - -# Install jscalendar - this really should be in its own package -install -d -m 755 %{buildroot}/%{_datadir}/%{name}/www/jscalendar -cp -rp jscalendar-1.0/* %{buildroot}/%{_datadir}/zoneminder/www/jscalendar - -# Set up cambozola -pushd %{buildroot}/%{_datadir}/zoneminder/www -%{__ln_s} ../../java/cambozola.jar -popd - -# Set up mootools -pushd %{buildroot}/%{_datadir}/%{name}/www -ln -f -s tools/mootools/mootools-core-%{moorev}-yc.js mootools-core.js -ln -f -s tools/mootools/mootools-more-%{moorev}.1-yc.js mootools-more.js -popd - -# Create an entry for tmpfiles.d -install -D -m 755 distros/fedora/zoneminder.tmpfiles %{buildroot}/etc/tmpfiles.d/zoneminder.conf - -install -m 755 -d %{buildroot}/run/zoneminder - - -%post -if [ $1 -eq 1 ] ; then - # Initial installation - /bin/systemctl daemon-reload >/dev/null 2>&1 || : -fi - -# Allow zoneminder access to local video sources, serial ports, and x10 -/usr/bin/gpasswd -a %{zmuid_final} video -/usr/bin/gpasswd -a %{zmuid_final} dialout - -# Display the README for post installation instructions -/usr/bin/less %{_docdir}/%{name}-%{version}/README.Fedora - -%preun -if [ $1 -eq 0 ] ; then - # Package removal, not upgrade - /bin/systemctl --no-reload disable zoneminder.service > /dev/null 2>&1 || : - /bin/systemctl stop zoneminder.service > /dev/null 2>&1 || : -fi - -%postun -/bin/systemctl daemon-reload >/dev/null 2>&1 || : -if [ $1 -ge 1 ] ; then - # Package upgrade, not uninstall - /bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || : -fi - -%triggerun -- zoneminder < 1.25.0-4 -# Save the current service runlevel info -# User must manually run systemd-sysv-convert --apply zoneminder -# to migrate them to systemd targets -/usr/bin/systemd-sysv-convert --save zoneminder >/dev/null 2>&1 ||: - -# Run these because the SysV package being removed won't do them -/sbin/chkconfig --del zoneminder >/dev/null 2>&1 || : -/bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || : - - -%files -%defattr(-,root,root,-) -%doc AUTHORS COPYING README.md distros/fedora/README.Fedora jscalendar-doc -%config %attr(640,root,%{zmgid_final}) /etc/zm.conf -%config(noreplace) %attr(644,root,root) /etc/httpd/conf.d/zoneminder.conf -%config(noreplace) /etc/tmpfiles.d/zoneminder.conf -%config(noreplace) /etc/logrotate.d/zoneminder - -%{_unitdir}/zoneminder.service - -%{_bindir}/zma -%{_bindir}/zmaudit.pl -%{_bindir}/zmc -%{_bindir}/zmcontrol.pl -%{_bindir}/zmdc.pl -%{_bindir}/zmf -%{_bindir}/zmfilter.pl -# zmfix removed from zoneminder 1.26.6 -#%attr(4755,root,root) %{_bindir}/zmfix -%{_bindir}/zmpkg.pl -%{_bindir}/zmtrack.pl -%{_bindir}/zmtrigger.pl -%{_bindir}/zmu -%{_bindir}/zmupdate.pl -%{_bindir}/zmvideo.pl -%{_bindir}/zmwatch.pl -%{_bindir}/zmcamtool.pl -%{_bindir}/zmsystemctl.pl -%{!?_without_x10:%{_bindir}/zmx10.pl} -%{_bindir}/zmonvif-probe.pl - -%{perl_vendorlib}/ZoneMinder* -%{perl_vendorlib}/ONVIF* -%{perl_vendorlib}/WSDiscovery* -%{perl_vendorlib}/WSSecurity* -%{_mandir}/man*/* -%dir %{_libexecdir}/zoneminder -%{_libexecdir}/zoneminder/cgi-bin -%dir %{_datadir}/zoneminder -%{_datadir}/zoneminder/db -%{_datadir}/zoneminder/www - -%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy -%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules - -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/events -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/images -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/sock -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/swap -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/temp -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/log/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/spool/zoneminder-upload -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /run/zoneminder - - -%changelog -* Sat Feb 01 2014 Andrew Bauer - 1.27 -- Add zmcamtool.pl. Bump version for 1.27 release. - -* Mon Dec 16 2013 Andrew Bauer - 1.26.5 -- This is a bug fixe release -- RTSP fixes, cmake enhancements, couple other misc fixes - -* Sat Oct 05 2013 Andrew Bauer - 1.26.4 -- Fedora specific path changes have been moved to zoneminder-1.26.0-defaults.patch -- All files are now part of the zoneminder source tree. Update specfile accordingly. - -* Sat Sep 21 2013 Andrew Bauer - 1.26.3 -- Initial rebuild for ZoneMinder 1.26.3 release. - -* Fri Feb 15 2013 Fedora Release Engineering - 1.25.0-13 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Mon Jan 21 2013 Adam Tkac - 1.25.0-12 -- rebuild due to "jpeg8-ABI" feature drop - -* Mon Jan 7 2013 Remi Collet - 1.25.0-11 -- fix configuration file for httpd 2.4, #871502 - -* Fri Dec 21 2012 Adam Tkac - 1.25.0-10 -- rebuild against new libjpeg - -* Thu Aug 09 2012 Jason L Tibbitts III - 1.25.0-9 -- Add patch to work around v4l2 api breakage in 3.5 kernel. - -* Sun Jul 22 2012 Fedora Release Engineering - 1.25.0-8 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild - -* Sat Jun 23 2012 Petr Pisar - 1.25.0-7 -- Perl 5.16 rebuild - -* Wed Mar 21 2012 Jason L Tibbitts III - 1.25.0-6 -- Fix stupid thinko in sql modifications. - -* Sat Feb 25 2012 Jason L Tibbitts III - 1.25.0-5 -- Clean up macro usage. - -* Sat Feb 25 2012 Jason L Tibbitts III - 1.25.0-4 -- Convert to systemd. -- Add tmpfiles.d configuration since the initscript isn't around to create - /run/zoneminder. -- Remove some pointless executable permissions. -- Add logrotate file. - -* Wed Feb 22 2012 Jason L Tibbitts III - 1.25.0-3 -- Update README.Fedora to reference systemctl and mention timezone info in - php.ini. -- Add proper default for EYEZM_LOG_TO_FILE. - - -* Thu Feb 09 2012 Jason L Tibbitts III - 1.25.0-2 -- Rebuild for new pcre. - -* Thu Jan 19 2012 Jason L Tibbitts III - 1.25.0-1 -- Update to 1.25.0 -- Fix gcc4.7 build problems. -- Drop gcc4.4 build fixes; for whatever reason they now break the build. -- Clean up old patches. -- Force setting of ZM_TMPDIR and ZM_RUNDIR. - -* Sat Jan 14 2012 Fedora Release Engineering - 1.24.4-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild - -* Thu Sep 15 2011 Jason L Tibbitts III - 1.24.4-3 -- Re-add the dist-tag that somehow got lost. - -* Thu Sep 15 2011 Jason L Tibbitts III - 1.24.4-2 -- Add patch for bug 711780 - fix syntax issue in Mapped.pm. -- Undo that patch, and undo another which was the cause of the whole mess. -- Fix up other patches so ZM_PATH_BUILD is both defined and useful. -- Make sure database creation mods actually take. -- Update Fedora-specific docs with some additional info. -- Use bundled mootools (javascript, so no guideline violation). -- Update download location. -- Update the gcrypt patch to actually work. -- Upstream changed the tarball without changing the version to patch a - vulnerability, so redownload. - -* Sun Aug 14 2011 Jason L Tibbitts III - 1.24.4-1 -- Initial attempt to upgrade to 1.24.4. -- Add patch from BZ 460310 to build against libgcrypt instead of requiring the - gnutls openssl libs. - -* Thu Jul 21 2011 Petr Sabata - 1.24.3-7.20110324svn3310 -- Perl mass rebuild - -* Wed Jul 20 2011 Petr Sabata - 1.24.3-6.20110324svn3310 -- Perl mass rebuild - -* Mon May 09 2011 Jason L Tibbitts III - 1.24.3-5.20110324svn3310 -- Bump for gnutls update. - -* Thu Mar 24 2011 Jason L Tibbitts III - 1.24.3-4.20110324svn3310 -- Update to latest 1.24.3 subversion. Turns out that what upstream was calling - 1.24.3 is really just an occasionally updated devel snapshot. -- Rebase various patches. - -* Wed Mar 23 2011 Dan Horák - 1.24.3-3 -- rebuilt for mysql 5.5.10 (soname bump in libmysqlclient) - -* Tue Feb 08 2011 Fedora Release Engineering - 1.24.3-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild - -* Tue Jan 25 2011 Jason L Tibbitts III - 1.24.3-1 -- Update to latest upstream version. -- Rebase patches. -- Initial incomplete attempt to disable v4l1 support. - -* Fri Jan 21 2011 Jason L Tibbitts III - 1.24.2-6 -- Unbundle cambozola; instead link to the separately pacakged copy. -- Remove BuildRoot:, %%clean and buildroot cleaning in %%install. -- Git rid of mixed space/tab usage by removing all tabs. -- Remove unnecessary Conflicts: line. -- Attempt to force short_open_tag on for the code directories. -- Move default location of sockets, swaps, logfiles and some temporary files to - make more sense and allow things to work better with a future selinux policy. -- Fix errors in README.Fedora. - -* Wed Jun 02 2010 Marcela Maslanova - 1.24.2-5 -- Mass rebuild with perl-5.12.0 - -* Fri Dec 4 2009 Stepan Kasal - 1.24.2-4 -- rebuild against perl 5.10.1 -- use Perl vendorarch and archlib variables correctly - -* Mon Jul 27 2009 Fedora Release Engineering - 1.24.2-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-2 -- Bump release since 1.24.2-1 was mistakenly tagged a few months ago. - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-1 -- Initial update to 1.24.2. -- Rebase patches. -- Update mootools download location. -- Update to mootools 1.2.3. -- Add additional dependencies for some optional features. - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-3 -- Remove unused Sys::Mmap perl dependency RPM is finding - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-2 -- Update gcc44 patch to disable -frepo, seems to be broken with gcc44 -- Added noffmpeg patch to make building outside mock easier - -* Sat Mar 21 2009 Martin Ebourne - 1.24.1-1 -- Patch for gcc 4.4 compilation errors -- Upgrade to 1.24.1 - -* Wed Feb 25 2009 Fedora Release Engineering - 1.23.3-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild - -* Sat Jan 24 2009 Caolán McNamara - 1.23.3-3 -- rebuild for dependencies - -* Mon Dec 15 2008 Martin Ebourne - 1.23.3-2 -- Fix permissions on zm.conf - -* Fri Jul 11 2008 Jason L Tibbitts III - 1.23.3-1 -- Initial attempt at packaging 1.23. - -* Tue Jul 1 2008 Martin Ebourne - 1.22.3-15 -- Add perl module compat dependency, bz #453590 - -* Tue May 6 2008 Martin Ebourne - 1.22.3-14 -- Remove default runlevel, bz #441315 - -* Mon Apr 28 2008 Jason L Tibbitts III - 1.22.3-13 -- Backport patch for CVE-2008-1381 from 1.23.3 to 1.22.3. - -* Tue Feb 19 2008 Fedora Release Engineering - 1.22.3-12 -- Autorebuild for GCC 4.3 - -* Thu Jan 3 2008 Martin Ebourne - 1.22.3-11 -- Fix compilation on gcc 4.3 - -* Thu Dec 6 2007 Martin Ebourne - 1.22.3-10 -- Rebuild for new openssl - -* Thu Aug 2 2007 Martin Ebourne - 1.22.3-8 -- Fix licence tag - -* Thu Jul 12 2007 Martin Ebourne - 1.22.3-7 -- Fixes from testing by Jitz including missing dependencies and database creation - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-6 -- Disable crashtrace on ppc - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-5 -- Fix uid for directories in /var/lib/zoneminder - -* Tue Jun 26 2007 Martin Ebourne - 1.22.3-4 -- Added perl Archive::Tar dependency -- Disabled web interface due to lack of access control on the event images - -* Sun Jun 10 2007 Martin Ebourne - 1.22.3-3 -- Changes recommended in review by Jason Tibbitts - -* Mon Apr 2 2007 Martin Ebourne - 1.22.3-2 -- Standardised on package name of zoneminder - -* Thu Dec 28 2006 Martin Ebourne - 1.22.3-1 -- First version. Uses some parts from zm-1.20.1 by Corey DeLasaux and Serg Oskin diff --git a/distros/fedora/archive/zoneminder.f20.spec b/distros/fedora/archive/zoneminder.f20.spec deleted file mode 100644 index 513a9cfee..000000000 --- a/distros/fedora/archive/zoneminder.f20.spec +++ /dev/null @@ -1,395 +0,0 @@ -%define zmuid $(id -un) -%define zmgid $(id -gn) -%define zmuid_final apache -%define zmgid_final apache - -%global _hardened_build 1 - -### Delete the lines below to build with ffmpeg and/or x10 -%define _without_ffmpeg 1 -%define _without_x10 1 - -Name: zoneminder -Version: 1.28.1 -Release: 1%{?dist} -Summary: A camera monitoring and analysis tool -Group: System Environment/Daemons -# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/ -# Mootools is inder the MIT license: http://mootools.net/ -License: GPLv2+ and LGPLv2+ and MIT -URL: http://www.zoneminder.com/ - -#Source: https://github.com/ZoneMinder/ZoneMinder/archive/v%{version}.tar.gz -Source: ZoneMinder-%{version}.tar.gz - -BuildRequires: cmake gnutls-devel systemd-units bzip2-devel -BuildRequires: community-mysql-devel pcre-devel libjpeg-turbo-devel -BuildRequires: perl(Archive::Tar) perl(Archive::Zip) perl-podlators -BuildRequires: perl(Date::Manip) perl(DBD::mysql) -BuildRequires: perl(ExtUtils::MakeMaker) perl(LWP::UserAgent) -BuildRequires: perl(MIME::Entity) perl(MIME::Lite) -BuildRequires: perl(PHP::Serialization) perl(Sys::Mmap) -BuildRequires: perl(Time::HiRes) perl(Net::SFTP::Foreign) -BuildRequires: perl(Expect) perl(Sys::Syslog) -BuildRequires: gcc gcc-c++ vlc-devel libcurl-devel libv4l-devel -%{!?_without_ffmpeg:BuildRequires: ffmpeg-devel} -%{!?_without_x10:BuildRequires: perl(X10::ActiveHome) perl(Astro::SunTime)} -# cmake needs the following installed at build time due to the way it auto-detects certain parameters -BuildRequires: httpd polkit-devel -%{!?_without_ffmpeg:BuildRequires: ffmpeg} - -Requires: httpd php php-gd php-mysql cambozola polkit net-tools psmisc -Requires: libjpeg-turbo vlc-core libcurl -Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) -Requires: perl(DBD::mysql) perl(Archive::Tar) perl(Archive::Zip) -Requires: perl(MIME::Entity) perl(MIME::Lite) perl(Net::SMTP) perl(Net::FTP) -Requires: perl(LWP::Protocol::https) -%{!?_without_ffmpeg:Requires: ffmpeg} - -Requires(post): systemd-units systemd-sysv -Requires(post): /usr/bin/gpasswd -Requires(post): /usr/bin/less -Requires(preun): systemd-units -Requires(postun): systemd-units - -%description -ZoneMinder is a set of applications which is intended to provide a complete -solution allowing you to capture, analyse, record and monitor any cameras you -have attached to a Linux based machine. It is designed to run on kernels which -support the Video For Linux (V4L) interface and has been tested with cameras -attached to BTTV cards, various USB cameras and IP network cameras. It is -designed to support as many cameras as you can attach to your computer without -too much degradation of performance. - -%prep -%setup -q -n ZoneMinder-%{version} - -# Change the following default values -./utils/zmeditconfigdata.sh ZM_PATH_ZMS /cgi-bin/zm/nph-zms -./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes -./utils/zmeditconfigdata.sh ZM_PATH_SWAP /dev/shm -./utils/zmeditconfigdata.sh ZM_UPLOAD_FTP_LOC_DIR /var/spool/zoneminder-upload -./utils/zmeditconfigdata.sh ZM_OPT_CONTROL yes -./utils/zmeditconfigdata.sh ZM_CHECK_FOR_UPDATES no -./utils/zmeditconfigdata.sh ZM_DYN_SHOW_DONATE_REMINDER no - -%build -%cmake \ - -DZM_TARGET_DISTRO="f20" \ -%{?_without_ffmpeg:-DZM_NO_FFMPEG=ON} \ -%{?_without_x10:-DZM_NO_X10=ON} \ - . - -make %{?_smp_mflags} - -%install -export DESTDIR=%{buildroot} -make install - -%post -if [ $1 -eq 1 ] ; then - # Initial installation - /bin/systemctl daemon-reload >/dev/null 2>&1 || : -fi - -# Allow zoneminder access to local video sources, serial ports, and x10 -/usr/bin/gpasswd -a %{zmuid_final} video -/usr/bin/gpasswd -a %{zmuid_final} dialout - -# Display the README for post installation instructions -/usr/bin/less %{_docdir}/%{name}/README.Fedora - -%preun -if [ $1 -eq 0 ] ; then - # Package removal, not upgrade - /bin/systemctl --no-reload disable zoneminder.service > /dev/null 2>&1 || : - /bin/systemctl stop zoneminder.service > /dev/null 2>&1 || : -fi - -%postun -/bin/systemctl daemon-reload >/dev/null 2>&1 || : -if [ $1 -ge 1 ] ; then - # Package upgrade, not uninstall - /bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || : -fi - -%triggerun -- zoneminder < 1.25.0-4 -# Save the current service runlevel info -# User must manually run systemd-sysv-convert --apply zoneminder -# to migrate them to systemd targets -/usr/bin/systemd-sysv-convert --save zoneminder >/dev/null 2>&1 ||: - -# Run these because the SysV package being removed won't do them -/sbin/chkconfig --del zoneminder >/dev/null 2>&1 || : -/bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || : - - -%files -%defattr(-,root,root,-) -%doc AUTHORS COPYING README.md distros/fedora/README.Fedora distros/fedora/jscalendar-doc -%config %attr(640,root,%{zmgid_final}) /etc/zm.conf -%config(noreplace) %attr(644,root,root) /etc/httpd/conf.d/zoneminder.conf -%config(noreplace) /etc/tmpfiles.d/zoneminder.conf -%config(noreplace) /etc/logrotate.d/zoneminder - -%{_unitdir}/zoneminder.service - -%{_bindir}/zma -%{_bindir}/zmaudit.pl -%{_bindir}/zmc -%{_bindir}/zmcontrol.pl -%{_bindir}/zmdc.pl -%{_bindir}/zmf -%{_bindir}/zmfilter.pl -%{_bindir}/zmpkg.pl -%{_bindir}/zmtrack.pl -%{_bindir}/zmtrigger.pl -%{_bindir}/zmu -%{_bindir}/zmupdate.pl -%{_bindir}/zmvideo.pl -%{_bindir}/zmwatch.pl -%{_bindir}/zmcamtool.pl -%{_bindir}/zmsystemctl.pl -%{!?_without_x10:%{_bindir}/zmx10.pl} - -%{perl_vendorlib}/ZoneMinder* -%{perl_vendorlib}/%{_arch}-linux-thread-multi/auto/ZoneMinder* -#%{perl_archlib}/ZoneMinder* -%{_mandir}/man*/* -%dir %{_libexecdir}/zoneminder -%{_libexecdir}/zoneminder/cgi-bin -%dir %{_datadir}/zoneminder -%{_datadir}/zoneminder/db -%{_datadir}/zoneminder/www - -%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy -%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules - -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/events -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/images -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/sock -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/swap -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/temp -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/log/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/spool/zoneminder-upload -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /run/zoneminder - - -%changelog -* Sun Oct 5 2014 Andrew Bauer - 1.28.0 -- Bump version for 1.28.0 release. - -* Fri Mar 14 2014 Andrew Bauer - 1.27 -- Tweak build requirements for cmake - -* Sat Feb 01 2014 Andrew Bauer - 1.27 -- Add zmcamtool.pl. Bump version for 1.27 release. - -* Mon Dec 16 2013 Andrew Bauer - 1.26.5 -- This is a bug fixe release -- RTSP fixes, cmake enhancements, couple other misc fixes - -* Mon Oct 07 2013 Andrew Bauer - 1.26.4 -- Initial cmake build. - -* Sat Oct 05 2013 Andrew Bauer - 1.26.4 -- Fedora specific path changes have been moved to zoneminder-1.26.0-defaults.patch -- All files are now part of the zoneminder source tree. Update specfile accordingly. - -* Sat Sep 21 2013 Andrew Bauer - 1.26.3 -- Initial rebuild for ZoneMinder 1.26.3 release. - -* Fri Feb 15 2013 Fedora Release Engineering - 1.25.0-13 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Mon Jan 21 2013 Adam Tkac - 1.25.0-12 -- rebuild due to "jpeg8-ABI" feature drop - -* Mon Jan 7 2013 Remi Collet - 1.25.0-11 -- fix configuration file for httpd 2.4, #871502 - -* Fri Dec 21 2012 Adam Tkac - 1.25.0-10 -- rebuild against new libjpeg - -* Thu Aug 09 2012 Jason L Tibbitts III - 1.25.0-9 -- Add patch to work around v4l2 api breakage in 3.5 kernel. - -* Sun Jul 22 2012 Fedora Release Engineering - 1.25.0-8 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild - -* Sat Jun 23 2012 Petr Pisar - 1.25.0-7 -- Perl 5.16 rebuild - -* Wed Mar 21 2012 Jason L Tibbitts III - 1.25.0-6 -- Fix stupid thinko in sql modifications. - -* Sat Feb 25 2012 Jason L Tibbitts III - 1.25.0-5 -- Clean up macro usage. - -* Sat Feb 25 2012 Jason L Tibbitts III - 1.25.0-4 -- Convert to systemd. -- Add tmpfiles.d configuration since the initscript isn't around to create - /run/zoneminder. -- Remove some pointless executable permissions. -- Add logrotate file. - -* Wed Feb 22 2012 Jason L Tibbitts III - 1.25.0-3 -- Update README.Fedora to reference systemctl and mention timezone info in - php.ini. -- Add proper default for EYEZM_LOG_TO_FILE. - - -* Thu Feb 09 2012 Jason L Tibbitts III - 1.25.0-2 -- Rebuild for new pcre. - -* Thu Jan 19 2012 Jason L Tibbitts III - 1.25.0-1 -- Update to 1.25.0 -- Fix gcc4.7 build problems. -- Drop gcc4.4 build fixes; for whatever reason they now break the build. -- Clean up old patches. -- Force setting of ZM_TMPDIR and ZM_RUNDIR. - -* Sat Jan 14 2012 Fedora Release Engineering - 1.24.4-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild - -* Thu Sep 15 2011 Jason L Tibbitts III - 1.24.4-3 -- Re-add the dist-tag that somehow got lost. - -* Thu Sep 15 2011 Jason L Tibbitts III - 1.24.4-2 -- Add patch for bug 711780 - fix syntax issue in Mapped.pm. -- Undo that patch, and undo another which was the cause of the whole mess. -- Fix up other patches so ZM_PATH_BUILD is both defined and useful. -- Make sure database creation mods actually take. -- Update Fedora-specific docs with some additional info. -- Use bundled mootools (javascript, so no guideline violation). -- Update download location. -- Update the gcrypt patch to actually work. -- Upstream changed the tarball without changing the version to patch a - vulnerability, so redownload. - -* Sun Aug 14 2011 Jason L Tibbitts III - 1.24.4-1 -- Initial attempt to upgrade to 1.24.4. -- Add patch from BZ 460310 to build against libgcrypt instead of requiring the - gnutls openssl libs. - -* Thu Jul 21 2011 Petr Sabata - 1.24.3-7.20110324svn3310 -- Perl mass rebuild - -* Wed Jul 20 2011 Petr Sabata - 1.24.3-6.20110324svn3310 -- Perl mass rebuild - -* Mon May 09 2011 Jason L Tibbitts III - 1.24.3-5.20110324svn3310 -- Bump for gnutls update. - -* Thu Mar 24 2011 Jason L Tibbitts III - 1.24.3-4.20110324svn3310 -- Update to latest 1.24.3 subversion. Turns out that what upstream was calling - 1.24.3 is really just an occasionally updated devel snapshot. -- Rebase various patches. - -* Wed Mar 23 2011 Dan Horák - 1.24.3-3 -- rebuilt for mysql 5.5.10 (soname bump in libmysqlclient) - -* Tue Feb 08 2011 Fedora Release Engineering - 1.24.3-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild - -* Tue Jan 25 2011 Jason L Tibbitts III - 1.24.3-1 -- Update to latest upstream version. -- Rebase patches. -- Initial incomplete attempt to disable v4l1 support. - -* Fri Jan 21 2011 Jason L Tibbitts III - 1.24.2-6 -- Unbundle cambozola; instead link to the separately pacakged copy. -- Remove BuildRoot:, %%clean and buildroot cleaning in %%install. -- Git rid of mixed space/tab usage by removing all tabs. -- Remove unnecessary Conflicts: line. -- Attempt to force short_open_tag on for the code directories. -- Move default location of sockets, swaps, logfiles and some temporary files to - make more sense and allow things to work better with a future selinux policy. -- Fix errors in README.Fedora. - -* Wed Jun 02 2010 Marcela Maslanova - 1.24.2-5 -- Mass rebuild with perl-5.12.0 - -* Fri Dec 4 2009 Stepan Kasal - 1.24.2-4 -- rebuild against perl 5.10.1 -- use Perl vendorarch and archlib variables correctly - -* Mon Jul 27 2009 Fedora Release Engineering - 1.24.2-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-2 -- Bump release since 1.24.2-1 was mistakenly tagged a few months ago. - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-1 -- Initial update to 1.24.2. -- Rebase patches. -- Update mootools download location. -- Update to mootools 1.2.3. -- Add additional dependencies for some optional features. - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-3 -- Remove unused Sys::Mmap perl dependency RPM is finding - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-2 -- Update gcc44 patch to disable -frepo, seems to be broken with gcc44 -- Added noffmpeg patch to make building outside mock easier - -* Sat Mar 21 2009 Martin Ebourne - 1.24.1-1 -- Patch for gcc 4.4 compilation errors -- Upgrade to 1.24.1 - -* Wed Feb 25 2009 Fedora Release Engineering - 1.23.3-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild - -* Sat Jan 24 2009 Caolán McNamara - 1.23.3-3 -- rebuild for dependencies - -* Mon Dec 15 2008 Martin Ebourne - 1.23.3-2 -- Fix permissions on zm.conf - -* Fri Jul 11 2008 Jason L Tibbitts III - 1.23.3-1 -- Initial attempt at packaging 1.23. - -* Tue Jul 1 2008 Martin Ebourne - 1.22.3-15 -- Add perl module compat dependency, bz #453590 - -* Tue May 6 2008 Martin Ebourne - 1.22.3-14 -- Remove default runlevel, bz #441315 - -* Mon Apr 28 2008 Jason L Tibbitts III - 1.22.3-13 -- Backport patch for CVE-2008-1381 from 1.23.3 to 1.22.3. - -* Tue Feb 19 2008 Fedora Release Engineering - 1.22.3-12 -- Autorebuild for GCC 4.3 - -* Thu Jan 3 2008 Martin Ebourne - 1.22.3-11 -- Fix compilation on gcc 4.3 - -* Thu Dec 6 2007 Martin Ebourne - 1.22.3-10 -- Rebuild for new openssl - -* Thu Aug 2 2007 Martin Ebourne - 1.22.3-8 -- Fix licence tag - -* Thu Jul 12 2007 Martin Ebourne - 1.22.3-7 -- Fixes from testing by Jitz including missing dependencies and database creation - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-6 -- Disable crashtrace on ppc - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-5 -- Fix uid for directories in /var/lib/zoneminder - -* Tue Jun 26 2007 Martin Ebourne - 1.22.3-4 -- Added perl Archive::Tar dependency -- Disabled web interface due to lack of access control on the event images - -* Sun Jun 10 2007 Martin Ebourne - 1.22.3-3 -- Changes recommended in review by Jason Tibbitts - -* Mon Apr 2 2007 Martin Ebourne - 1.22.3-2 -- Standardised on package name of zoneminder - -* Thu Dec 28 2006 Martin Ebourne - 1.22.3-1 -- First version. Uses some parts from zm-1.20.1 by Corey DeLasaux and Serg Oskin diff --git a/distros/fedora/archive/zoneminder.f21.spec b/distros/fedora/archive/zoneminder.f21.spec deleted file mode 100644 index 35662bf6c..000000000 --- a/distros/fedora/archive/zoneminder.f21.spec +++ /dev/null @@ -1,396 +0,0 @@ -%define zmuid $(id -un) -%define zmgid $(id -gn) -%define zmuid_final apache -%define zmgid_final apache - -%global _hardened_build 1 - -### Delete the lines below to build with ffmpeg and/or x10 -%define _without_ffmpeg 1 -%define _without_x10 1 - -Name: zoneminder -Version: 1.28.1 -Release: 1%{?dist} -Summary: A camera monitoring and analysis tool -Group: System Environment/Daemons -# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/ -# Mootools is inder the MIT license: http://mootools.net/ -License: GPLv2+ and LGPLv2+ and MIT -URL: http://www.zoneminder.com/ - -#Source: https://github.com/ZoneMinder/ZoneMinder/archive/v%{version}.tar.gz -Source: ZoneMinder-%{version}.tar.gz - -BuildRequires: cmake gnutls-devel systemd-units bzip2-devel -BuildRequires: community-mysql-devel pcre-devel libjpeg-turbo-devel -BuildRequires: perl(Archive::Tar) perl(Archive::Zip) perl-podlators -BuildRequires: perl(Date::Manip) perl(DBD::mysql) -BuildRequires: perl(ExtUtils::MakeMaker) perl(LWP::UserAgent) -BuildRequires: perl(MIME::Entity) perl(MIME::Lite) -BuildRequires: perl(PHP::Serialization) perl(Sys::Mmap) -BuildRequires: perl(Time::HiRes) perl(Net::SFTP::Foreign) -BuildRequires: perl(Expect) perl(Sys::Syslog) -BuildRequires: gcc gcc-c++ vlc-devel libcurl-devel libv4l-devel -%{!?_without_ffmpeg:BuildRequires: ffmpeg-devel} -%{!?_without_x10:BuildRequires: perl(X10::ActiveHome) perl(Astro::SunTime)} -# cmake needs the following installed at build time due to the way it auto-detects certain parameters -BuildRequires: httpd polkit-devel -%{!?_without_ffmpeg:BuildRequires: ffmpeg} - -Requires: httpd php php-gd php-mysql cambozola polkit net-tools psmisc -Requires: libjpeg-turbo vlc-core libcurl -Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) -Requires: perl(DBD::mysql) perl(Archive::Tar) perl(Archive::Zip) -Requires: perl(MIME::Entity) perl(MIME::Lite) perl(Net::SMTP) perl(Net::FTP) -Requires: perl(LWP::Protocol::https) -%{!?_without_ffmpeg:Requires: ffmpeg} - -Requires(post): systemd-units systemd-sysv -Requires(post): /usr/bin/gpasswd -Requires(post): /usr/bin/less -Requires(preun): systemd-units -Requires(postun): systemd-units - -%description -ZoneMinder is a set of applications which is intended to provide a complete -solution allowing you to capture, analyse, record and monitor any cameras you -have attached to a Linux based machine. It is designed to run on kernels which -support the Video For Linux (V4L) interface and has been tested with cameras -attached to BTTV cards, various USB cameras and IP network cameras. It is -designed to support as many cameras as you can attach to your computer without -too much degradation of performance. - -%prep -%setup -q -n ZoneMinder-%{version} - -# Change the following default values -./utils/zmeditconfigdata.sh ZM_PATH_ZMS /cgi-bin/zm/nph-zms -./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes -./utils/zmeditconfigdata.sh ZM_PATH_SWAP /dev/shm -./utils/zmeditconfigdata.sh ZM_UPLOAD_FTP_LOC_DIR /var/spool/zoneminder-upload -./utils/zmeditconfigdata.sh ZM_OPT_CONTROL yes -./utils/zmeditconfigdata.sh ZM_CHECK_FOR_UPDATES no -./utils/zmeditconfigdata.sh ZM_DYN_SHOW_DONATE_REMINDER no - -%build -%cmake \ - -DZM_TARGET_DISTRO="f21" \ -%{?_without_ffmpeg:-DZM_NO_FFMPEG=ON} \ -%{?_without_x10:-DZM_NO_X10=ON} \ - . - -make %{?_smp_mflags} - -%install -export DESTDIR=%{buildroot} -make install - -%post -if [ $1 -eq 1 ] ; then - # Initial installation - /bin/systemctl daemon-reload >/dev/null 2>&1 || : -fi - -# Allow zoneminder access to local video sources, serial ports, and x10 -/usr/bin/gpasswd -a %{zmuid_final} video -/usr/bin/gpasswd -a %{zmuid_final} dialout - -# Display the README for post installation instructions -/usr/bin/less %{_docdir}/%{name}/README.Fedora - -%preun -if [ $1 -eq 0 ] ; then - # Package removal, not upgrade - /bin/systemctl --no-reload disable zoneminder.service > /dev/null 2>&1 || : - /bin/systemctl stop zoneminder.service > /dev/null 2>&1 || : -fi - -%postun -/bin/systemctl daemon-reload >/dev/null 2>&1 || : -if [ $1 -ge 1 ] ; then - # Package upgrade, not uninstall - /bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || : -fi - -%triggerun -- zoneminder < 1.25.0-4 -# Save the current service runlevel info -# User must manually run systemd-sysv-convert --apply zoneminder -# to migrate them to systemd targets -/usr/bin/systemd-sysv-convert --save zoneminder >/dev/null 2>&1 ||: - -# Run these because the SysV package being removed won't do them -/sbin/chkconfig --del zoneminder >/dev/null 2>&1 || : -/bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || : - - -%files -%defattr(-,root,root,-) -%doc AUTHORS COPYING README.md distros/fedora/README.Fedora distros/fedora/jscalendar-doc -%config %attr(640,root,%{zmgid_final}) /etc/zm/zm.conf -%config(noreplace) %attr(644,root,root) /etc/httpd/conf.d/zoneminder.conf -%config(noreplace) /etc/tmpfiles.d/zoneminder.conf -%config(noreplace) /etc/logrotate.d/zoneminder - -%{_unitdir}/zoneminder.service - -%{_bindir}/zma -%{_bindir}/zmaudit.pl -%{_bindir}/zmc -%{_bindir}/zmcontrol.pl -%{_bindir}/zmdc.pl -%{_bindir}/zmf -%{_bindir}/zmfilter.pl -%{_bindir}/zmpkg.pl -%{_bindir}/zmtrack.pl -%{_bindir}/zmtrigger.pl -%{_bindir}/zmu -%{_bindir}/zmupdate.pl -%{_bindir}/zmvideo.pl -%{_bindir}/zmwatch.pl -%{_bindir}/zmcamtool.pl -%{_bindir}/zmsystemctl.pl -%{!?_without_x10:%{_bindir}/zmx10.pl} - -%{perl_vendorlib}/ZoneMinder* -%{_mandir}/man*/* -%dir %{_libexecdir}/zoneminder -%{_libexecdir}/zoneminder/cgi-bin -%dir %{_datadir}/zoneminder -%{_datadir}/zoneminder/db -%{_datadir}/zoneminder/www - -%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy -%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules - -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/events -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/images -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/sock -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/swap -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/temp -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/log/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/spool/zoneminder-upload -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /run/zoneminder - - -%changelog -* Sat Feb 14 2015 Andrew Bauer - 1.28.1 -- Bump version for 1.28.1 release on Fedora 21. - -* Sun Oct 5 2014 Andrew Bauer - 1.28.0 -- Bump version for 1.28.0 release. - -* Fri Mar 14 2014 Andrew Bauer - 1.27 -- Tweak build requirements for cmake - -* Sat Feb 01 2014 Andrew Bauer - 1.27 -- Add zmcamtool.pl. Bump version for 1.27 release. - -* Mon Dec 16 2013 Andrew Bauer - 1.26.5 -- This is a bug fixe release -- RTSP fixes, cmake enhancements, couple other misc fixes - -* Mon Oct 07 2013 Andrew Bauer - 1.26.4 -- Initial cmake build. - -* Sat Oct 05 2013 Andrew Bauer - 1.26.4 -- Fedora specific path changes have been moved to zoneminder-1.26.0-defaults.patch -- All files are now part of the zoneminder source tree. Update specfile accordingly. - -* Sat Sep 21 2013 Andrew Bauer - 1.26.3 -- Initial rebuild for ZoneMinder 1.26.3 release. - -* Fri Feb 15 2013 Fedora Release Engineering - 1.25.0-13 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Mon Jan 21 2013 Adam Tkac - 1.25.0-12 -- rebuild due to "jpeg8-ABI" feature drop - -* Mon Jan 7 2013 Remi Collet - 1.25.0-11 -- fix configuration file for httpd 2.4, #871502 - -* Fri Dec 21 2012 Adam Tkac - 1.25.0-10 -- rebuild against new libjpeg - -* Thu Aug 09 2012 Jason L Tibbitts III - 1.25.0-9 -- Add patch to work around v4l2 api breakage in 3.5 kernel. - -* Sun Jul 22 2012 Fedora Release Engineering - 1.25.0-8 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild - -* Sat Jun 23 2012 Petr Pisar - 1.25.0-7 -- Perl 5.16 rebuild - -* Wed Mar 21 2012 Jason L Tibbitts III - 1.25.0-6 -- Fix stupid thinko in sql modifications. - -* Sat Feb 25 2012 Jason L Tibbitts III - 1.25.0-5 -- Clean up macro usage. - -* Sat Feb 25 2012 Jason L Tibbitts III - 1.25.0-4 -- Convert to systemd. -- Add tmpfiles.d configuration since the initscript isn't around to create - /run/zoneminder. -- Remove some pointless executable permissions. -- Add logrotate file. - -* Wed Feb 22 2012 Jason L Tibbitts III - 1.25.0-3 -- Update README.Fedora to reference systemctl and mention timezone info in - php.ini. -- Add proper default for EYEZM_LOG_TO_FILE. - - -* Thu Feb 09 2012 Jason L Tibbitts III - 1.25.0-2 -- Rebuild for new pcre. - -* Thu Jan 19 2012 Jason L Tibbitts III - 1.25.0-1 -- Update to 1.25.0 -- Fix gcc4.7 build problems. -- Drop gcc4.4 build fixes; for whatever reason they now break the build. -- Clean up old patches. -- Force setting of ZM_TMPDIR and ZM_RUNDIR. - -* Sat Jan 14 2012 Fedora Release Engineering - 1.24.4-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild - -* Thu Sep 15 2011 Jason L Tibbitts III - 1.24.4-3 -- Re-add the dist-tag that somehow got lost. - -* Thu Sep 15 2011 Jason L Tibbitts III - 1.24.4-2 -- Add patch for bug 711780 - fix syntax issue in Mapped.pm. -- Undo that patch, and undo another which was the cause of the whole mess. -- Fix up other patches so ZM_PATH_BUILD is both defined and useful. -- Make sure database creation mods actually take. -- Update Fedora-specific docs with some additional info. -- Use bundled mootools (javascript, so no guideline violation). -- Update download location. -- Update the gcrypt patch to actually work. -- Upstream changed the tarball without changing the version to patch a - vulnerability, so redownload. - -* Sun Aug 14 2011 Jason L Tibbitts III - 1.24.4-1 -- Initial attempt to upgrade to 1.24.4. -- Add patch from BZ 460310 to build against libgcrypt instead of requiring the - gnutls openssl libs. - -* Thu Jul 21 2011 Petr Sabata - 1.24.3-7.20110324svn3310 -- Perl mass rebuild - -* Wed Jul 20 2011 Petr Sabata - 1.24.3-6.20110324svn3310 -- Perl mass rebuild - -* Mon May 09 2011 Jason L Tibbitts III - 1.24.3-5.20110324svn3310 -- Bump for gnutls update. - -* Thu Mar 24 2011 Jason L Tibbitts III - 1.24.3-4.20110324svn3310 -- Update to latest 1.24.3 subversion. Turns out that what upstream was calling - 1.24.3 is really just an occasionally updated devel snapshot. -- Rebase various patches. - -* Wed Mar 23 2011 Dan Horák - 1.24.3-3 -- rebuilt for mysql 5.5.10 (soname bump in libmysqlclient) - -* Tue Feb 08 2011 Fedora Release Engineering - 1.24.3-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild - -* Tue Jan 25 2011 Jason L Tibbitts III - 1.24.3-1 -- Update to latest upstream version. -- Rebase patches. -- Initial incomplete attempt to disable v4l1 support. - -* Fri Jan 21 2011 Jason L Tibbitts III - 1.24.2-6 -- Unbundle cambozola; instead link to the separately pacakged copy. -- Remove BuildRoot:, %%clean and buildroot cleaning in %%install. -- Git rid of mixed space/tab usage by removing all tabs. -- Remove unnecessary Conflicts: line. -- Attempt to force short_open_tag on for the code directories. -- Move default location of sockets, swaps, logfiles and some temporary files to - make more sense and allow things to work better with a future selinux policy. -- Fix errors in README.Fedora. - -* Wed Jun 02 2010 Marcela Maslanova - 1.24.2-5 -- Mass rebuild with perl-5.12.0 - -* Fri Dec 4 2009 Stepan Kasal - 1.24.2-4 -- rebuild against perl 5.10.1 -- use Perl vendorarch and archlib variables correctly - -* Mon Jul 27 2009 Fedora Release Engineering - 1.24.2-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-2 -- Bump release since 1.24.2-1 was mistakenly tagged a few months ago. - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-1 -- Initial update to 1.24.2. -- Rebase patches. -- Update mootools download location. -- Update to mootools 1.2.3. -- Add additional dependencies for some optional features. - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-3 -- Remove unused Sys::Mmap perl dependency RPM is finding - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-2 -- Update gcc44 patch to disable -frepo, seems to be broken with gcc44 -- Added noffmpeg patch to make building outside mock easier - -* Sat Mar 21 2009 Martin Ebourne - 1.24.1-1 -- Patch for gcc 4.4 compilation errors -- Upgrade to 1.24.1 - -* Wed Feb 25 2009 Fedora Release Engineering - 1.23.3-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild - -* Sat Jan 24 2009 Caolán McNamara - 1.23.3-3 -- rebuild for dependencies - -* Mon Dec 15 2008 Martin Ebourne - 1.23.3-2 -- Fix permissions on zm.conf - -* Fri Jul 11 2008 Jason L Tibbitts III - 1.23.3-1 -- Initial attempt at packaging 1.23. - -* Tue Jul 1 2008 Martin Ebourne - 1.22.3-15 -- Add perl module compat dependency, bz #453590 - -* Tue May 6 2008 Martin Ebourne - 1.22.3-14 -- Remove default runlevel, bz #441315 - -* Mon Apr 28 2008 Jason L Tibbitts III - 1.22.3-13 -- Backport patch for CVE-2008-1381 from 1.23.3 to 1.22.3. - -* Tue Feb 19 2008 Fedora Release Engineering - 1.22.3-12 -- Autorebuild for GCC 4.3 - -* Thu Jan 3 2008 Martin Ebourne - 1.22.3-11 -- Fix compilation on gcc 4.3 - -* Thu Dec 6 2007 Martin Ebourne - 1.22.3-10 -- Rebuild for new openssl - -* Thu Aug 2 2007 Martin Ebourne - 1.22.3-8 -- Fix licence tag - -* Thu Jul 12 2007 Martin Ebourne - 1.22.3-7 -- Fixes from testing by Jitz including missing dependencies and database creation - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-6 -- Disable crashtrace on ppc - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-5 -- Fix uid for directories in /var/lib/zoneminder - -* Tue Jun 26 2007 Martin Ebourne - 1.22.3-4 -- Added perl Archive::Tar dependency -- Disabled web interface due to lack of access control on the event images - -* Sun Jun 10 2007 Martin Ebourne - 1.22.3-3 -- Changes recommended in review by Jason Tibbitts - -* Mon Apr 2 2007 Martin Ebourne - 1.22.3-2 -- Standardised on package name of zoneminder - -* Thu Dec 28 2006 Martin Ebourne - 1.22.3-1 -- First version. Uses some parts from zm-1.20.1 by Corey DeLasaux and Serg Oskin diff --git a/distros/fedora/archive/zoneminder.logrotate b/distros/fedora/archive/zoneminder.logrotate deleted file mode 100644 index 7bd1d189b..000000000 --- a/distros/fedora/archive/zoneminder.logrotate +++ /dev/null @@ -1,8 +0,0 @@ -/var/log/zoneminder/*.log { - missingok - notifempty - sharedscripts - postrotate - /usr/bin/zmpkg.pl logrot 2> /dev/null > /dev/null || : - endscript -} diff --git a/distros/fedora/archive/zoneminder.service b/distros/fedora/archive/zoneminder.service deleted file mode 100644 index fdd9b3af2..000000000 --- a/distros/fedora/archive/zoneminder.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=Video security and surveillance system -After=mysqld.service - -[Service] -Type=forking -ExecStart=/usr/bin/zmpkg.pl start -ExecReload=/usr/bin/zmpkg.pl reload -PIDFile=/run/zoneminder/zm.pid - -[Install] -WantedBy=multi-user.target - diff --git a/distros/fedora/archive/zoneminder.tmpfiles b/distros/fedora/archive/zoneminder.tmpfiles deleted file mode 100644 index a56fa54ba..000000000 --- a/distros/fedora/archive/zoneminder.tmpfiles +++ /dev/null @@ -1 +0,0 @@ -d /run/zoneminder 0755 apache apache diff --git a/distros/fedora/jscalendar.sh b/distros/fedora/jscalendar.sh deleted file mode 120000 index 0d12509bb..000000000 --- a/distros/fedora/jscalendar.sh +++ /dev/null @@ -1 +0,0 @@ -../redhat/jscalendar.sh \ No newline at end of file diff --git a/distros/fedora/redalert.wav b/distros/fedora/redalert.wav deleted file mode 120000 index eec3dce64..000000000 --- a/distros/fedora/redalert.wav +++ /dev/null @@ -1 +0,0 @@ -../redhat/redalert.wav \ No newline at end of file diff --git a/distros/fedora/zoneminder-1.28.0-defaults.patch b/distros/fedora/zoneminder-1.28.0-defaults.patch deleted file mode 100644 index 30d4d87e4..000000000 --- a/distros/fedora/zoneminder-1.28.0-defaults.patch +++ /dev/null @@ -1,47 +0,0 @@ ---- scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in 2013-08-01 18:14:45.175241378 -0500 -+++ scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in.defaults 2013-08-07 18:57:42.525006149 -0500 -@@ -187,7 +187,7 @@ - }, - { - name => "ZM_PATH_ZMS", -- default => "/cgi-bin/nph-zms", -+ default => "/cgi-bin/zm/nph-zms", - description => "Web path to zms streaming server", - help => "The ZoneMinder streaming server is required to send streamed images to your browser. It will be installed into the cgi-bin path given at configuration time. This option determines what the web path to the server is rather than the local path on your machine. Ordinarily the streaming server runs in parser-header mode however if you experience problems with streaming you can change this to non-parsed-header (nph) mode by changing 'zms' to 'nph-zms'.", - type => $types{rel_path}, -@@ -276,7 +276,7 @@ - }, - { - name => "ZM_OPT_CAMBOZOLA", -- default => "no", -+ default => "yes", - description => "Is the (optional) cambozola java streaming client installed", - help => "Cambozola is a handy low fat cheese flavoured Java applet that ZoneMinder uses to view image streams on browsers such as Internet Explorer that don't natively support this format. If you use this browser it is highly recommended to install this from http://www.charliemouse.com/code/cambozola/ however if it is not installed still images at a lower refresh rate can still be viewed.", - type => $types{boolean}, -@@ -639,7 +639,7 @@ - }, - { - name => "ZM_PATH_SWAP", -- default => "@ZM_TMPDIR@", -+ default => "/dev/shm", - description => "Path to location for temporary swap images used in streaming", - help => "Buffered playback requires temporary swap images to be stored for each instance of the streaming daemons. This option determines where these images will be stored. The images will actually be stored in sub directories beneath this location and will be automatically cleaned up after a period of time.", - type => $types{abs_path}, -@@ -902,7 +902,7 @@ - }, - { - name => "ZM_UPLOAD_FTP_LOC_DIR", -- default => "@ZM_TMPDIR@", -+ default => "/var/spool/zoneminder-upload", - description => "The local directory in which to create upload files", - help => "You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the local directory that ZoneMinder should use for temporary upload files. These are files that are created from events, uploaded and then deleted.", - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], -@@ -1258,7 +1258,7 @@ - }, - { - name => "ZM_OPT_CONTROL", -- default => "no", -+ default => "yes", - description => "Support controllable (e.g. PTZ) cameras", - help => "ZoneMinder includes limited support for controllable cameras. A number of sample protocols are included and others can easily be added. If you wish to control your cameras via ZoneMinder then select this option otherwise if you only have static cameras or use other control methods then leave this option off.", - type => $types{boolean}, diff --git a/distros/fedora/zoneminder.conf.in b/distros/fedora/zoneminder.conf.in deleted file mode 120000 index fd0098cf5..000000000 --- a/distros/fedora/zoneminder.conf.in +++ /dev/null @@ -1 +0,0 @@ -../redhat/zoneminder.el7.conf.in \ No newline at end of file diff --git a/distros/fedora/zoneminder.f22.spec b/distros/fedora/zoneminder.f22.spec deleted file mode 120000 index 808e7cb9f..000000000 --- a/distros/fedora/zoneminder.f22.spec +++ /dev/null @@ -1 +0,0 @@ -zoneminder.f23.spec \ No newline at end of file diff --git a/distros/fedora/zoneminder.logrotate.in b/distros/fedora/zoneminder.logrotate.in deleted file mode 120000 index c7e334ca4..000000000 --- a/distros/fedora/zoneminder.logrotate.in +++ /dev/null @@ -1 +0,0 @@ -../redhat/zoneminder.el7.logrotate.in \ No newline at end of file diff --git a/distros/fedora/zoneminder.tmpfiles.in b/distros/fedora/zoneminder.tmpfiles.in deleted file mode 120000 index 0e4f721f6..000000000 --- a/distros/fedora/zoneminder.tmpfiles.in +++ /dev/null @@ -1 +0,0 @@ -../redhat/zoneminder.tmpfiles.in \ No newline at end of file diff --git a/distros/redhat/CMakeLists.txt b/distros/redhat/CMakeLists.txt index 5de834109..63159b1c7 100644 --- a/distros/redhat/CMakeLists.txt +++ b/distros/redhat/CMakeLists.txt @@ -1,50 +1,57 @@ # CMakeLists.txt for the Redhat/CentOS Target Distro. # Display a message to show the RHEL build options are being processed. -message([STATUS] "Starting RHEL Build Options" ...) +if(ZM_TARGET_DISTRO MATCHES "^el") + message([STATUS] "Starting RHEL Build Options" ...) +elseif(ZM_TARGET_DISTRO MATCHES "^fc") + message([STATUS] "Starting Fedora Build Options" ...) +else(ZM_TARGET_DISTRO MATCHES "^el") + message([WARNING] "Unknown Build Option Detected" ...) +endif(ZM_TARGET_DISTRO MATCHES "^el") -# Create the zoneminder service file -if(ZM_TARGET_DISTRO STREQUAL "el7") - configure_file(zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY) - configure_file(zoneminder.el7.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.el7.conf @ONLY) - configure_file(zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY) - configure_file(zoneminder.el7.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.el7.logrotate @ONLY) -else(ZM_TARGET_DISTRO STREQUAL "el7") - configure_file(zoneminder.sysvinit.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.sysvinit @ONLY) - configure_file(zoneminder.el6.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.el6.logrotate @ONLY) - configure_file(zoneminder.el6.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.el6.conf @ONLY) -endif(ZM_TARGET_DISTRO STREQUAL "el7") +if((ZM_TARGET_DISTRO STREQUAL "el6") AND (ZM_WEB_USER STREQUAL "nginx")) + message([FATAL_ERROR] "Nginx is Not a Supported Build Option on EL6 Target Distro" ...) +endif((ZM_TARGET_DISTRO STREQUAL "el6") AND (ZM_WEB_USER STREQUAL "nginx")) -# Download jscalendar & move files into position -file(DOWNLOAD http://iweb.dl.sourceforge.net/project/jscalendar/jscalendar/1.0/jscalendar-1.0.zip ${CMAKE_CURRENT_SOURCE_DIR}/jscalendar-1.0.zip LOG jsc_log STATUS download_jsc) -#message(STATUS "Log of jscalender script was: ${jsc_log}") -if(download_jsc EQUAL 0) - message(STATUS "Jscalander successfully downloaded. Installing...") - execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/jscalendar.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ERROR_VARIABLE unzip_jsc) - message(STATUS "Status of jscalender script was: ${unzip_jsc}") -else(download_jsc EQUAL 0) - message(STATUS "Unable to download optional jscalander. Skipping...") -endif(download_jsc EQUAL 0) +# Configure the zoneminder service files +if(ZM_TARGET_DISTRO STREQUAL "el6") + configure_file(sysvinit/zoneminder.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.sysvinit @ONLY) + configure_file(sysvinit/zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY) + configure_file(sysvinit/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY) +else(ZM_TARGET_DISTRO STREQUAL "el6") + configure_file(systemd/zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY) + if(ZM_WEB_USER STREQUAL "nginx") + configure_file(nginx/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY) + configure_file(nginx/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY) + configure_file(nginx/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY) + configure_file(nginx/zoneminder.php-fpm.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.php-fpm.conf @ONLY) + configure_file(nginx/README.Fedora ${CMAKE_CURRENT_SOURCE_DIR}/readme/README.Fedora COPYONLY) + else(ZM_WEB_USER STREQUAL "nginx") + configure_file(systemd/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY) + configure_file(systemd/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY) + configure_file(systemd/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY) + endif(ZM_WEB_USER STREQUAL "nginx") +endif(ZM_TARGET_DISTRO STREQUAL "el6") -# Cambozola is now packaged in zmrepo -# Download cambozola & move files into position -#file(DOWNLOAD http://www.andywilcock.com/code/cambozola/cambozola-0.931.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/cambozola-0.931.tar.gz STATUS download_camb) -#if(download_camb EQUAL 0) -# message(STATUS "Cambozola successfully downloaded. Installing...") -# execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/cambozola.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ERROR_VARIABLE untar_camb) -# message(STATUS "Status of cambozola script was: ${untar_camb}") -#else(download_camb EQUAL 0) -# message(STATUS "Unable to download optional Cambozola. Skipping...") -#endif(download_camb EQUAL 0) +# Unpack jscalendar & move files into position +message(STATUS "Unpacking and Installing jscalendar...") +execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/misc/jscalendar.sh + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ERROR_VARIABLE unzip_jsc + ) +if("${unzip_jsc}" STREQUAL "") + message(STATUS "jscalendar successfully installed.") +else("${unzip_jsc}" STREQUAL "") + message(FATAL_ERROR "\nAn error occurred while jscalendar was being processed:\n${unzip_jsc}") +endif("${unzip_jsc}" STREQUAL "") # Create several empty folders file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp) # Install the empty folders -#install(DIRECTORY run DESTINATION /var DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_WRITE GROUP_READ GROUP_EXECUTE WORLD_WRITE WORLD_READ WORLD_EXECUTE) install(DIRECTORY sock swap DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder DESTINATION /var/log DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(DIRECTORY zoneminder DESTINATION /run DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(DIRECTORY zoneminder DESTINATION /var/run DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder-upload DESTINATION /var/spool DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY events images temp DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) @@ -54,28 +61,26 @@ install(CODE "execute_process(COMMAND ln -sf ../../../../var/lib/zoneminder/imag install(CODE "execute_process(COMMAND ln -sf ../../../../var/lib/zoneminder/temp \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/temp\")") install(CODE "execute_process(COMMAND ln -sf ../../../../../../var/lib/zoneminder/temp \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/api/app/tmp\")") -# Link to Cambozola, which is now packaged in zmrepo +# Link to Cambozola install(CODE "execute_process(COMMAND ln -sf ../../java/cambozola.jar \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/cambozola.jar\")") # Install auxiliary files required to run zoneminder on CentOS -install(FILES redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/sounds PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -if(ZM_TARGET_DISTRO STREQUAL "el7") - install(FILES zoneminder.el7.conf DESTINATION /etc/httpd/conf.d RENAME zoneminder.conf PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) - install(FILES zoneminder.el7.logrotate DESTINATION /etc/logrotate.d RENAME zoneminder PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) - install(FILES zoneminder.service DESTINATION /usr/lib/systemd/system PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) - install(FILES ../../misc/zoneminder-tmpfiles.conf DESTINATION /etc/tmpfiles.d RENAME zoneminder.conf PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) -else(ZM_TARGET_DISTRO STREQUAL "el7") - install(FILES zoneminder.el6.conf DESTINATION /etc/httpd/conf.d RENAME zoneminder.conf PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) - install(FILES zoneminder.el6.logrotate DESTINATION /etc/logrotate.d RENAME zoneminder PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) +install(FILES misc/redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/sounds PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(DIRECTORY jscalendar-1.0/ DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/tools/jscalendar) + +install(FILES zoneminder.logrotate DESTINATION /etc/logrotate.d RENAME zoneminder PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) + +if(ZM_WEB_USER STREQUAL "nginx") + install(FILES zoneminder.conf DESTINATION /etc/nginx/default.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) + install(FILES zoneminder.php-fpm.conf DESTINATION /etc/php-fpm.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ RENAME zoneminder.conf) +else(ZM_WEB_USER STREQUAL "nginx") + install(FILES zoneminder.conf DESTINATION /etc/httpd/conf.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) +endif(ZM_WEB_USER STREQUAL "nginx") + +if(ZM_TARGET_DISTRO STREQUAL "el6") install(FILES zoneminder.sysvinit DESTINATION /etc/rc.d/init.d RENAME zoneminder PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) -endif(ZM_TARGET_DISTRO STREQUAL "el7") +else(ZM_TARGET_DISTRO STREQUAL "el6") + install(FILES zoneminder.service DESTINATION /usr/lib/systemd/system PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) + install(FILES zoneminder.tmpfiles DESTINATION /usr/lib/tmpfiles.d RENAME zoneminder.conf PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ) +endif(ZM_TARGET_DISTRO STREQUAL "el6") -# Install jscalendar -if(unzip_jsc STREQUAL "") - install(DIRECTORY jscalendar-1.0/ DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/tools/jscalendar) -endif(unzip_jsc STREQUAL "") - -# Install cambozola -if(untar_camb STREQUAL "") - install(FILES cambozola-0.931/dist/cambozola.jar DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www) -endif(untar_camb STREQUAL "") diff --git a/distros/redhat/archive/zm-init b/distros/redhat/archive/zm-init deleted file mode 100644 index 90e645f43..000000000 --- a/distros/redhat/archive/zm-init +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/sh -#$Id: zm-init,v 1.1 2005/04/19 00:49:53 hunter Exp $ - -# -# Copyright (C) 2005 Serg Oskin -# - -ZM_VERSION= -ZM_CONFIG=/etc/zm.conf -ZM_PATH=/usr/lib/zm - -if [ -f $ZM_CONFIG ]; then - . $ZM_CONFIG -else - echo "ERROR: $ZM_CONFIG not found." - exit 1 -fi -for n in ZM_DB_SERVER ZM_DB_NAME ZM_DB_USER ZM_DB_PASS; do - eval "val=\$$n" - if [ "$val" = "" ]; then - echo "ERROR($ZM_CONFIG): $n should exist and be not empty." - exit 1 - fi -done - -if [ "$ZM_DB_SERVER" = "localhost" ]; then - ClientHost=localhost -else - ClientHost=`hostname` -fi - - -sql=/tmp/zm.crdb.sql -echo "" >$sql -chmod 600 $sql - -echo "CREATE DATABASE /*!32312 IF NOT EXISTS*/ $ZM_DB_NAME;" >>$sql -echo "USE $ZM_DB_NAME;" >>$sql - -echo "GRANT all on $ZM_DB_NAME.* TO '$ZM_DB_USER'@'$ClientHost' IDENTIFIED BY '$ZM_DB_PASS';" >>$sql - -echo -n "Enter MySQL Administrator username: " -read admin -cat $sql | mysql -B -h $ZM_DB_SERVER -u $admin -p -rm -f $sql - -cat /usr/lib/zm/init/zmschema.sql | mysql -h $ZM_DB_SERVER -u $ZM_DB_USER -p$ZM_DB_PASS $ZM_DB_NAME -( cd $ZM_PATH/init; perl $ZM_PATH/init/zmoptions -f $ZM_PATH/init/zmconfig.txt ) diff --git a/distros/redhat/archive/zoneminder-1.26.0-dbinstall.patch b/distros/redhat/archive/zoneminder-1.26.0-dbinstall.patch deleted file mode 100644 index 5b688a75d..000000000 --- a/distros/redhat/archive/zoneminder-1.26.0-dbinstall.patch +++ /dev/null @@ -1,72 +0,0 @@ ---- configure.ac 2013-09-05 10:33:08.000000000 -0500 -+++ configure.ac.dbinstall 2013-09-05 17:23:28.555553447 -0500 -@@ -1,13 +1,11 @@ - AC_PREREQ(2.59) --AC_INIT(zm,1.26.0,[http://www.zoneminder.com/forums/ - Please check FAQ first],ZoneMinder,http://www.zoneminder.com/downloads.html) -+AC_INIT(zm,1.26.0,[http://www.zoneminder.com/forums/ - Please check FAQ first],zoneminder,http://www.zoneminder.com/downloads.html) - AM_INIT_AUTOMAKE - AC_CONFIG_SRCDIR(src/zm.h) - AC_CONFIG_HEADERS(config.h) - - AC_SUBST([AM_CXXFLAGS], [-D__STDC_CONSTANT_MACROS]) - --PATH_BUILD=`pwd` --AC_SUBST(PATH_BUILD) - TIME_BUILD=`date +'%s'` - AC_SUBST(TIME_BUILD) - -@@ -354,6 +352,8 @@ AC_PROG_PERL_MODULES(X10::ActiveHome,,AC - - AC_DEFINE_DIR([BINDIR],[bindir],[Expanded binary directory]) - AC_DEFINE_DIR([LIBDIR],[libdir],[Expanded library directory]) -+AC_DEFINE_DIR([DATADIR],[datadir],[Expanded data directory]) -+AC_SUBST(PKGDATADIR,"$DATADIR/$PACKAGE") - AC_SUBST(ZM_PID,"$ZM_RUNDIR/zm.pid") - AC_DEFINE_DIR([SYSCONFDIR],[sysconfdir],[Expanded configuration directory]) - AC_SUBST(ZM_CONFIG,"$SYSCONFDIR/zm.conf") -diff -up ./db/Makefile.am.dbinstall ./db/Makefile.am ---- ./db/Makefile.am.dbinstall 2009-10-14 04:42:46.000000000 -0500 -+++ ./db/Makefile.am 2011-03-24 22:50:14.173912137 -0500 -@@ -1,7 +1,16 @@ - AUTOMAKE_OPTIONS = gnu - -+zmdbdatadir = $(pkgdatadir)/db -+ - EXTRA_DIST = \ - zm_create.sql.in \ -+ $(dbupgrade_scripts) -+ -+dist_zmdbdata_DATA = \ -+ zm_create.sql \ -+ $(dbupgrade_scripts) -+ -+dbupgrade_scripts = \ - zm_update-0.0.1.sql \ - zm_update-0.9.7.sql \ - zm_update-0.9.8.sql \ -diff -up ./scripts/zmupdate.pl.in.dbinstall ./scripts/zmupdate.pl.in ---- ./scripts/zmupdate.pl.in.dbinstall 2011-08-27 15:44:05.335602405 -0500 -+++ ./scripts/zmupdate.pl.in 2011-08-26 02:51:37.000000000 -0500 -@@ -424,7 +424,7 @@ if ( $version ) - } - else - { -- $command .= ZM_PATH_BUILD."/db"; -+ $command .= ZM_PATH_DATA."/db"; - } - $command .= "/zm_update-".$version.".sql"; - -diff -up ./zm.conf.in.dbinstall ./zm.conf.in ---- ./zm.conf.in.dbinstall 2008-07-25 04:48:16.000000000 -0500 -+++ ./zm.conf.in 2011-03-24 22:50:14.175912077 -0500 -@@ -12,8 +12,8 @@ - # Current version of ZoneMinder - ZM_VERSION=@VERSION@ - --# Path to build directory, used mostly for finding DB upgrade scripts --ZM_PATH_BUILD=@PATH_BUILD@ -+# Path to installed data directory, used mostly for finding DB upgrade scripts -+ZM_PATH_DATA=@PKGDATADIR@ - - # Build time, used to record when to trigger various checks - ZM_TIME_BUILD=@TIME_BUILD@ diff --git a/distros/redhat/archive/zoneminder-1.26.0-defaults.patch b/distros/redhat/archive/zoneminder-1.26.0-defaults.patch deleted file mode 100644 index 3e5dda67c..000000000 --- a/distros/redhat/archive/zoneminder-1.26.0-defaults.patch +++ /dev/null @@ -1,76 +0,0 @@ ---- configure.ac 2013-08-15 11:44:10.000000000 -0500 -+++ configure.ac.logdir 2013-08-17 09:20:07.326053328 -0500 -@@ -46,7 +46,7 @@ - AC_SUBST(ZM_TMPDIR,[/tmp/zm]) - fi - if test "$ZM_LOGDIR" == ""; then -- AC_SUBST(ZM_LOGDIR,[/var/log/zm]) -+ AC_SUBST(ZM_LOGDIR,[/var/log/zoneminder]) - fi - - LIB_ARCH=lib ---- scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in 2013-08-01 18:14:45.175241378 -0500 -+++ scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in.defaults 2013-08-07 18:57:42.525006149 -0500 -@@ -187,7 +187,7 @@ - }, - { - name => "ZM_PATH_ZMS", -- default => "/cgi-bin/nph-zms", -+ default => "/cgi-bin/zm/nph-zms", - description => "Web path to zms streaming server", - help => "The ZoneMinder streaming server is required to send streamed images to your browser. It will be installed into the cgi-bin path given at configuration time. This option determines what the web path to the server is rather than the local path on your machine. Ordinarily the streaming server runs in parser-header mode however if you experience problems with streaming you can change this to non-parsed-header (nph) mode by changing 'zms' to 'nph-zms'.", - type => $types{rel_path}, -@@ -276,7 +276,7 @@ - }, - { - name => "ZM_OPT_CAMBOZOLA", -- default => "no", -+ default => "yes", - description => "Is the (optional) cambozola java streaming client installed", - help => "Cambozola is a handy low fat cheese flavoured Java applet that ZoneMinder uses to view image streams on browsers such as Internet Explorer that don't natively support this format. If you use this browser it is highly recommended to install this from http://www.charliemouse.com/code/cambozola/ however if it is not installed still images at a lower refresh rate can still be viewed.", - type => $types{boolean}, -@@ -526,7 +526,7 @@ - }, - { - name => "ZM_LOG_DEBUG_FILE", -- default => "@ZM_TMPDIR@/zm_debug.log+", -+ default => "/var/log/zoneminder/zm_debug_log+", - description => "Where extra debug is output to", - help => "This option allows you to specify a different target for debug output. All components have a default log file which will norally be in /tmp or /var/log and this is where debug will be written to if this value is empty. Adding a path here will temporarily redirect debug, and other logging output, to this file. This option is a simple filename and you are debugging several components then they will all try and write to the same file with undesirable consequences. Appending a '+' to the filename will cause the file to be created with a '.' suffix containing your process id. In this way debug from each run of a component is kept separate. This is the recommended setting as it will also prevent subsequent runs from overwriting the same log. You should ensure that permissions are set up to allow writing to the file and directory specified here.", - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], -@@ -623,7 +623,7 @@ - }, - { - name => "ZM_PATH_SOCKS", -- default => "@ZM_TMPDIR@", -+ default => "/var/lib/zoneminder/sock", - description => "Path to the various Unix domain socket files that ZoneMinder uses", - help => "ZoneMinder generally uses Unix domain sockets where possible. This reduces the need for port assignments and prevents external applications from possibly compromising the daemons. However each Unix socket requires a .sock file to be created. This option indicates where those socket files go.", - type => $types{abs_path}, -@@ -639,7 +639,7 @@ - }, - { - name => "ZM_PATH_SWAP", -- default => "@ZM_TMPDIR@", -+ default => "/dev/shm", - description => "Path to location for temporary swap images used in streaming", - help => "Buffered playback requires temporary swap images to be stored for each instance of the streaming daemons. This option determines where these images will be stored. The images will actually be stored in sub directories beneath this location and will be automatically cleaned up after a period of time.", - type => $types{abs_path}, -@@ -902,7 +902,7 @@ - }, - { - name => "ZM_UPLOAD_FTP_LOC_DIR", -- default => "@ZM_TMPDIR@", -+ default => "/var/spool/zoneminder-upload", - description => "The local directory in which to create upload files", - help => "You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the local directory that ZoneMinder should use for temporary upload files. These are files that are created from events, uploaded and then deleted.", - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], -@@ -1258,7 +1258,7 @@ - }, - { - name => "ZM_OPT_CONTROL", -- default => "no", -+ default => "yes", - description => "Support controllable (e.g. PTZ) cameras", - help => "ZoneMinder includes limited support for controllable cameras. A number of sample protocols are included and others can easily be added. If you wish to control your cameras via ZoneMinder then select this option otherwise if you only have static cameras or use other control methods then leave this option off.", - type => $types{boolean}, diff --git a/distros/redhat/archive/zoneminder-1.28.0-defaults.patch b/distros/redhat/archive/zoneminder-1.28.0-defaults.patch deleted file mode 100644 index 30d4d87e4..000000000 --- a/distros/redhat/archive/zoneminder-1.28.0-defaults.patch +++ /dev/null @@ -1,47 +0,0 @@ ---- scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in 2013-08-01 18:14:45.175241378 -0500 -+++ scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in.defaults 2013-08-07 18:57:42.525006149 -0500 -@@ -187,7 +187,7 @@ - }, - { - name => "ZM_PATH_ZMS", -- default => "/cgi-bin/nph-zms", -+ default => "/cgi-bin/zm/nph-zms", - description => "Web path to zms streaming server", - help => "The ZoneMinder streaming server is required to send streamed images to your browser. It will be installed into the cgi-bin path given at configuration time. This option determines what the web path to the server is rather than the local path on your machine. Ordinarily the streaming server runs in parser-header mode however if you experience problems with streaming you can change this to non-parsed-header (nph) mode by changing 'zms' to 'nph-zms'.", - type => $types{rel_path}, -@@ -276,7 +276,7 @@ - }, - { - name => "ZM_OPT_CAMBOZOLA", -- default => "no", -+ default => "yes", - description => "Is the (optional) cambozola java streaming client installed", - help => "Cambozola is a handy low fat cheese flavoured Java applet that ZoneMinder uses to view image streams on browsers such as Internet Explorer that don't natively support this format. If you use this browser it is highly recommended to install this from http://www.charliemouse.com/code/cambozola/ however if it is not installed still images at a lower refresh rate can still be viewed.", - type => $types{boolean}, -@@ -639,7 +639,7 @@ - }, - { - name => "ZM_PATH_SWAP", -- default => "@ZM_TMPDIR@", -+ default => "/dev/shm", - description => "Path to location for temporary swap images used in streaming", - help => "Buffered playback requires temporary swap images to be stored for each instance of the streaming daemons. This option determines where these images will be stored. The images will actually be stored in sub directories beneath this location and will be automatically cleaned up after a period of time.", - type => $types{abs_path}, -@@ -902,7 +902,7 @@ - }, - { - name => "ZM_UPLOAD_FTP_LOC_DIR", -- default => "@ZM_TMPDIR@", -+ default => "/var/spool/zoneminder-upload", - description => "The local directory in which to create upload files", - help => "You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the local directory that ZoneMinder should use for temporary upload files. These are files that are created from events, uploaded and then deleted.", - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], -@@ -1258,7 +1258,7 @@ - }, - { - name => "ZM_OPT_CONTROL", -- default => "no", -+ default => "yes", - description => "Support controllable (e.g. PTZ) cameras", - help => "ZoneMinder includes limited support for controllable cameras. A number of sample protocols are included and others can easily be added. If you wish to control your cameras via ZoneMinder then select this option otherwise if you only have static cameras or use other control methods then leave this option off.", - type => $types{boolean}, diff --git a/distros/redhat/archive/zoneminder-runlevel.patch b/distros/redhat/archive/zoneminder-runlevel.patch deleted file mode 100644 index de7b49b0b..000000000 --- a/distros/redhat/archive/zoneminder-runlevel.patch +++ /dev/null @@ -1,11 +0,0 @@ -diff -up ./scripts/zm.in.runlevel ./scripts/zm.in ---- ./scripts/zm.in.runlevel 2010-11-28 15:22:05.000000000 -0600 -+++ ./scripts/zm.in 2011-03-24 21:39:01.973010160 -0500 -@@ -1,6 +1,6 @@ - #!/bin/sh - # description: ZoneMinder is the top Linux video camera security and surveillance solution. ZoneMinder is intended for use in single or multi-camera video security applications.Copyright: Philip Coombes, Corey DeLasaux 2003-2008 --# chkconfig: 2345 99 00 -+# chkconfig: - 99 00 - # processname: zmpkg.pl - - # Source function library. diff --git a/distros/redhat/archive/zoneminder.el6.spec b/distros/redhat/archive/zoneminder.el6.spec deleted file mode 100644 index 46bce099a..000000000 --- a/distros/redhat/archive/zoneminder.el6.spec +++ /dev/null @@ -1,434 +0,0 @@ -%define cambrev 0.931 -%define moorev 1.3.2 -%define jscrev 1.0 - -%define zmuid $(id -un) -%define zmgid $(id -gn) -%define zmuid_final apache -%define zmgid_final apache - -Name: zoneminder -Version: 1.27 -Release: 1%{?dist} -Summary: A camera monitoring and analysis tool -Group: System Environment/Daemons -# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/ -# Mootools is inder the MIT license: http://mootools.net/ -# Cambozola is GPL: http://www.charliemouse.com/code/cambozola/ -License: GPLv2+ and LGPLv2+ and MIT -URL: http://www.zoneminder.com/ - -#Source0: https://github.com/ZoneMinder/ZoneMinder/archive/v%{version}.tar.gz -Source0: ZoneMinder-%{version}.tar.gz -Source1: jscalendar-%{jscrev}.zip -#Source1: http://downloads.sourceforge.net/jscalendar/jscalendar-%{jscrev}.zip - -# Mootools is currently bundled in the zoneminder tarball -#Source2: mootools-core-%{moorev}-full-compat-yc.js -#Source2: http://mootools.net/download/get/mootools-core-%{moorev}-full-compat-yc.js - -Source3: cambozola-%{cambrev}.tar.gz -#Source3: http://www.andywilcock.com/code/cambozola/cambozola-%{cambrev}.tar.gz - -#Patch1: zoneminder-1.26.4-dbinstall.patch -Patch2: zoneminder-runlevel.patch -#Patch3: zoneminder-1.25.0-installfix.patch -Patch4: zoneminder-1.26.0-defaults.patch - -# BuildRoot is depreciated and ignored in EPEL6 -#BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) - -BuildRequires: automake gnutls-devel bzip2-devel libtool -BuildRequires: mysql-devel pcre-devel libjpeg-turbo-devel -BuildRequires: perl(Archive::Tar) perl(Archive::Zip) -BuildRequires: perl(Date::Manip) perl(DBD::mysql) -BuildRequires: perl(ExtUtils::MakeMaker) perl(LWP::UserAgent) -BuildRequires: perl(MIME::Entity) perl(MIME::Lite) -BuildRequires: perl(PHP::Serialization) perl(Sys::Mmap) -BuildRequires: perl(Time::HiRes) perl(Net::SFTP::Foreign) -BuildRequires: perl(Expect) perl(X10::ActiveHome) perl(Astro::SunTime) -BuildRequires: libcurl-devel vlc-devel polkit-devel ffmpeg-devel >= 0.4.9 - -Requires: httpd php php-mysql mysql-server libjpeg-turbo polkit -Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) -Requires: perl(DBD::mysql) perl(Archive::Tar) perl(Archive::Zip) -Requires: perl(MIME::Entity) perl(MIME::Lite) perl(Net::SMTP) perl(Net::FTP) -Requires: libcurl vlc-core ffmpeg >= 0.4.9 - -Requires(post): /sbin/chkconfig -Requires(post): /usr/bin/checkmodule -Requires(post): /usr/bin/semodule_package -Requires(post): /usr/sbin/semodule -Requires(post): /usr/bin/gpasswd -Requires(post): /usr/bin/less -Requires(preun): /sbin/chkconfig -Requires(preun): /sbin/service -Requires(preun): /usr/sbin/semodule -Requires(postun): /sbin/service - - -%description -ZoneMinder is a set of applications which is intended to provide a complete -solution allowing you to capture, analyse, record and monitor any cameras you -have attached to a Linux based machine. It is designed to run on kernels which -support the Video For Linux (V4L) interface and has been tested with cameras -attached to BTTV cards, various USB cameras and IP network cameras. It is -designed to support as many cameras as you can attach to your computer without -too much degradation of performance. - - -%prep -%setup -q -n ZoneMinder-%{version} - -# Unpack jscalendar and move some files around -%setup -q -D -T -a 1 -n ZoneMinder-%{version} -mkdir jscalendar-doc -pushd jscalendar-%{jscrev} -mv *html *php doc/* README ../jscalendar-doc -rmdir doc -popd - -# Unpack Cambozola and move some files around -%setup -q -D -T -a 3 -n ZoneMinder-%{version} -mkdir cambozola-doc -pushd cambozola-%{cambrev} -mv application.properties build.xml dist.sh *html LICENSE testPages/* ../cambozola-doc -rmdir testPages -popd - -#%patch1 -p0 -b .dbinstall -%patch2 -p0 -b .runlevel -#%patch3 -p0 -b .installfix -%patch4 -p0 - -%build -libtoolize --force -aclocal -autoheader -automake --force-missing --add-missing -autoconf - -OPTS="" -%ifnarch %{ix86} x86_64 - OPTS="$OPTS --disable-crashtrace" -%endif - -%configure \ - --with-libarch=%{_lib} \ -%ifarch %{ix86} %{x8664} - --enable-crashtrace \ -%else - --disable-crashtrace \ -%endif - --with-mysql=%{_prefix} \ - --with-ffmpeg=%{_prefix} \ - --with-webdir=%{_datadir}/%{name}/www \ - --with-cgidir=%{_libexecdir}/%{name}/cgi-bin \ - --with-webuser=%{zmuid} \ - --with-webgroup=%{zmgid} \ - --enable-mmap=yes \ - --disable-debug \ - --with-webhost=zm.local \ - ZM_SSL_LIB="gnutls" \ - ZM_RUNDIR=/var/run/zoneminder \ - ZM_TMPDIR=/var/lib/zoneminder/temp \ -%ifarch x86_64 - CXXFLAGS="-D__STDC_CONSTANT_MACROS -msse2" \ -%else - CXXFLAGS="-D__STDC_CONSTANT_MACROS" \ -%endif - --with-extralibs="" - -make %{?_smp_mflags} -%{__perl} -pi -e 's/(ZM_WEB_USER=).*$/${1}%{zmuid_final}/;' \ - -e 's/(ZM_WEB_GROUP=).*$/${1}%{zmgid_final}/;' zm.conf - -%install -install -d %{buildroot}/%{_localstatedir}/run -install -d %{buildroot}/etc/logrotate.d - -make install DESTDIR=%{buildroot} \ - INSTALLDIRS=vendor - -rm -rf %{buildroot}/%{perl_vendorarch} %{buildroot}/%{perl_archlib} - -install -m 755 -d %{buildroot}/%{_localstatedir}/log/zoneminder -for dir in events images temp -do - install -m 755 -d %{buildroot}/%{_localstatedir}/lib/zoneminder/$dir - if [ -d %{buildroot}/%{_datadir}/zoneminder/www/$dir ]; then - rmdir %{buildroot}/%{_datadir}/%{name}/www/$dir - fi - ln -sf ../../../..%{_localstatedir}/lib/zoneminder/$dir %{buildroot}/%{_datadir}/%{name}/www/$dir -done -install -m 755 -d %{buildroot}/%{_localstatedir}/lib/zoneminder/sock -install -m 755 -d %{buildroot}/%{_localstatedir}/lib/zoneminder/swap -install -m 755 -d %{buildroot}/%{_localstatedir}/spool/zoneminder-upload - -install -D -m 755 scripts/zm %{buildroot}/%{_initrddir}/zoneminder -install -D -m 644 distros/redhat/zoneminder.conf %{buildroot}/%{_sysconfdir}/httpd/conf.d/zoneminder.conf -install -D -m 755 distros/redhat/redalert.wav %{buildroot}/%{_datadir}/%{name}/www/sounds/redalert.wav -install distros/redhat/zm-logrotate_d $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/%{name} - -# Install jscalendar -install -d -m 755 %{buildroot}/%{_datadir}/%{name}/www/jscalendar -cp -rp jscalendar-%{jscrev}/* %{buildroot}/%{_datadir}/%{name}/www/jscalendar - -# Install Cambozola -cp -rp cambozola-%{cambrev}/dist/cambozola.jar %{buildroot}/%{_datadir}/%{name}/www/ -rm -rf cambozola-%{cambrev} - -# Install mootools -pushd %{buildroot}/%{_datadir}/%{name}/www -#install -m 644 %{Source2} mootools-core-%{moorev}-full-compat-yc.js -#ln -s mootools-core-%{moorev}-full-compat-yc.js mootools.js -ln -f -s tools/mootools/mootools-core-%{moorev}-yc.js mootools-core.js -ln -f -s tools/mootools/mootools-more-%{moorev}.1-yc.js mootools-more.js -popd - -%post -/sbin/chkconfig --add zoneminder -/sbin/chkconfig zoneminder on - -# Allow zoneminder access to local video sources, serial ports, and x10 -echo -/usr/bin/gpasswd -a %{zmuid_final} video -/usr/bin/gpasswd -a %{zmuid_final} dialout - -# Create and load zoneminder selinux policy module -echo -e "\nCreating and installing a ZoneMinder SELinux policy module. Please wait.\n" -/usr/bin/checkmodule -M -m -o %{_docdir}/%{name}-%{version}/local_zoneminder.mod %{_docdir}/%{name}-%{version}/local_zoneminder.te > /dev/null -/usr/bin/semodule_package -o %{_docdir}/%{name}-%{version}/local_zoneminder.pp -m %{_docdir}/%{name}-%{version}/local_zoneminder.mod > /dev/null -/usr/sbin/semodule -i %{_docdir}/%{name}-%{version}/local_zoneminder.pp > /dev/null - -# Display the README for post installation instructions -/usr/bin/less %{_docdir}/%{name}-%{version}/README.CentOS - -%preun -if [ $1 -eq 0 ]; then - /sbin/service zoneminder stop > /dev/null 2>&1 || : - /sbin/chkconfig --del zoneminder - echo -e "\nRemoving ZoneMinder SELinux policy module. Please wait.\n" - /usr/sbin/semodule -r local_zoneminder.pp -fi - - -%postun -if [ $1 -ge 1 ]; then - /sbin/service zoneminder condrestart > /dev/null 2>&1 || : -fi - - -%files -%defattr(-,root,root,-) -%doc AUTHORS BUGS ChangeLog COPYING LICENSE NEWS README.md distros/redhat/README.CentOS jscalendar-doc cambozola-doc distros/redhat/local_zoneminder.te -%config %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm.conf -%config(noreplace) %attr(644,root,root) %{_sysconfdir}/httpd/conf.d/zoneminder.conf -%config(noreplace) /etc/logrotate.d/%{name} -%attr(755,root,root) %{_initrddir}/zoneminder - -%{_bindir}/zma -%{_bindir}/zmaudit.pl -%{_bindir}/zmc -%{_bindir}/zmcontrol.pl -%{_bindir}/zmdc.pl -%{_bindir}/zmf -%{_bindir}/zmfilter.pl -# zmfix removed from zoneminder 1.26.6 -#%attr(4755,root,root) %{_bindir}/zmfix -%{_bindir}/zmpkg.pl -%{_bindir}/zmtrack.pl -%{_bindir}/zmtrigger.pl -%{_bindir}/zmu -%{_bindir}/zmupdate.pl -%{_bindir}/zmvideo.pl -%{_bindir}/zmwatch.pl -%{_bindir}/zmcamtool.pl -%{_bindir}/zmsystemctl.pl -%{_bindir}/zmx10.pl -%{_bindir}/zmonvif-probe.pl - -%{perl_vendorlib}/ZoneMinder* -%{perl_vendorlib}/%{_arch}-linux-thread-multi/auto/ZoneMinder* -#%{perl_vendorlib}/ONVIF* -#%{perl_vendorlib}/WSDiscovery* -#%{perl_vendorlib}/WSSecurity* -#%{perl_vendorlib}/%{_arch}-linux-thread-multi/auto/ONVIF* -%{_mandir}/man*/* -%dir %{_libexecdir}/%{name} -%{_libexecdir}/%{name}/cgi-bin -%dir %{_datadir}/%{name} -%{_datadir}/%{name}/db -%{_datadir}/%{name}/www - -%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy -%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules - -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/lib/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/lib/zoneminder/events -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/lib/zoneminder/images -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/lib/zoneminder/sock -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/lib/zoneminder/swap -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/lib/zoneminder/temp -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/log/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/spool/zoneminder-upload - - -%changelog -* Sun Aug 03 2014 Andrew Bauer - 1.27 -- Include ONVIF support files - -* Fri Mar 14 2014 Andrew Bauer - 1.27 -- Tweak build requirements for cmake - -* Sat Feb 01 2014 Andrew Bauer - 1.27 -- Add zmcamtool.pl. Bump version for 1.27 release. - -* Mon Dec 16 2013 Andrew Bauer - 1.26.5 -- This is a bug fixe release -- RTSP fixes, cmake enhancements, couple other misc fixes - -* Sun Oct 06 2013 Andrew Bauer - 1.26.4 -- All files are now part of the zoneminder source tree. Update specfile accordingly. - -* Thu Sep 05 2013 Andrew Bauer - 1.26.0 -- 1.26.0 Release -- https://github.com/ZoneMinder/ZoneMinder/archive/v1.26.0.tar.gz - -* Sun Sep 01 2013 Andrew Bauer - 1.26.0-beta -- Update SELinux policy module - -* Thu Aug 29 2013 Andrew Bauer - 1.26.0-beta -- Third Beta release -- https://github.com/ZoneMinder/ZoneMinder/tree/release-1.26 -- Reduce number of uneeded dependencies by integrating cambozola into spec file - -* Thu Aug 15 2013 Andrew Bauer - 1.26.0-beta -- Initial Beta release -- https://github.com/ZoneMinder/ZoneMinder/tree/release-1.26 - -* Sun Aug 11 2013 Andrew Bauer - 1.25.0-kfirproper -- Modified specfile to work with kfir-proper branch -- https://github.com/ZoneMinder/ZoneMinder/tree/kfir-proper - -* Wed Aug 07 2013 Andrew Bauer - 1.25.0-2svn3827 -- Move RHEL/CentOS specific defaults to a patch file -- Add bzip2-devel as a build dependency -- Default ZM_SSL_LIB back to gnutls. AUTH_RELAY = hashed didn't work with openssl. - -* Fri Aug 02 2013 Andrew Bauer - 1.25.0-1svn3827 -- Update to latest 1.25.0 subversion. -- Does not compile with modern versions of ffmpeg. Configure to work only with older versions. -- Does not compile with gcc 4.7. Configure to build with gcc less than 4.7. - -* Thu Mar 24 2011 Jason L Tibbitts III - 1.24.3-4.20110324svn3310 -- Update to latest 1.24.3 subversion. Turns out that what upstream was calling - 1.24.3 is really just an occasionally updated devel snapshot. -- Rebase various patches. - -* Wed Mar 23 2011 Dan Horák - 1.24.3-3 -- rebuilt for mysql 5.5.10 (soname bump in libmysqlclient) - -* Tue Feb 08 2011 Fedora Release Engineering - 1.24.3-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild - -* Tue Jan 25 2011 Jason L Tibbitts III - 1.24.3-1 -- Update to latest upstream version. -- Rebase patches. -- Initial incomplete attempt to disable v4l1 support. - -* Fri Jan 21 2011 Jason L Tibbitts III - 1.24.2-6 -- Unbundle cambozola; instead link to the separately pacakged copy. -- Remove BuildRoot:, %%clean and buildroot cleaning in %%install. -- Git rid of mixed space/tab usage by removing all tabs. -- Remove unnecessary Conflicts: line. -- Attempt to force short_open_tag on for the code directories. -- Move default location of sockets, swaps, logfiles and some temporary files to - make more sense and allow things to work better with a future selinux policy. -- Fix errors in README.CentOS. - -* Wed Jun 02 2010 Marcela Maslanova - 1.24.2-5 -- Mass rebuild with perl-5.12.0 - -* Fri Dec 4 2009 Stepan Kasal - 1.24.2-4 -- rebuild against perl 5.10.1 -- use Perl vendorarch and archlib variables correctly - -* Mon Jul 27 2009 Fedora Release Engineering - 1.24.2-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-2 -- Bump release since 1.24.2-1 was mistakenly tagged a few months ago. - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-1 -- Initial update to 1.24.2. -- Rebase patches. -- Update mootools download location. -- Update to mootools 1.2.3. -- Add additional dependencies for some optional features. - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-3 -- Remove unused Sys::Mmap perl dependency RPM is finding - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-2 -- Update gcc44 patch to disable -frepo, seems to be broken with gcc44 -- Added noffmpeg patch to make building outside mock easier - -* Sat Mar 21 2009 Martin Ebourne - 1.24.1-1 -- Patch for gcc 4.4 compilation errors -- Upgrade to 1.24.1 - -* Wed Feb 25 2009 Fedora Release Engineering - 1.23.3-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild - -* Sat Jan 24 2009 Caolán McNamara - 1.23.3-3 -- rebuild for dependencies - -* Mon Dec 15 2008 Martin Ebourne - 1.23.3-2 -- Fix permissions on zm.conf - -* Fri Jul 11 2008 Jason L Tibbitts III - 1.23.3-1 -- Initial attempt at packaging 1.23. - -* Tue Jul 1 2008 Martin Ebourne - 1.22.3-15 -- Add perl module compat dependency, bz #453590 - -* Tue May 6 2008 Martin Ebourne - 1.22.3-14 -- Remove default runlevel, bz #441315 - -* Mon Apr 28 2008 Jason L Tibbitts III - 1.22.3-13 -- Backport patch for CVE-2008-1381 from 1.23.3 to 1.22.3. - -* Tue Feb 19 2008 Fedora Release Engineering - 1.22.3-12 -- Autorebuild for GCC 4.3 - -* Thu Jan 3 2008 Martin Ebourne - 1.22.3-11 -- Fix compilation on gcc 4.3 - -* Thu Dec 6 2007 Martin Ebourne - 1.22.3-10 -- Rebuild for new openssl - -* Thu Aug 2 2007 Martin Ebourne - 1.22.3-8 -- Fix licence tag - -* Thu Jul 12 2007 Martin Ebourne - 1.22.3-7 -- Fixes from testing by Jitz including missing dependencies and database creation - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-6 -- Disable crashtrace on ppc - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-5 -- Fix uid for directories in /var/lib/zoneminder - -* Tue Jun 26 2007 Martin Ebourne - 1.22.3-4 -- Added perl Archive::Tar dependency -- Disabled web interface due to lack of access control on the event images - -* Sun Jun 10 2007 Martin Ebourne - 1.22.3-3 -- Changes recommended in review by Jason Tibbitts - -* Mon Apr 2 2007 Martin Ebourne - 1.22.3-2 -- Standardised on package name of zoneminder - -* Thu Dec 28 2006 Martin Ebourne - 1.22.3-1 -- First version. Uses some parts from zm-1.20.1 by Corey DeLasaux and Serg Oskin diff --git a/distros/redhat/cambozola.sh b/distros/redhat/cambozola.sh deleted file mode 100755 index 645c5aa77..000000000 --- a/distros/redhat/cambozola.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -tar -xvzf cambozola-0.931.tar.gz -mkdir -v cambozola-doc -cd cambozola-0.931 -mv -v application.properties build.xml dist.sh *html LICENSE testPages/* ../cambozola-doc -rmdir -v testPages diff --git a/distros/redhat/misc/jscalendar-1.0.zip b/distros/redhat/misc/jscalendar-1.0.zip new file mode 100644 index 000000000..f33dc072c Binary files /dev/null and b/distros/redhat/misc/jscalendar-1.0.zip differ diff --git a/distros/redhat/jscalendar.sh b/distros/redhat/misc/jscalendar.sh similarity index 77% rename from distros/redhat/jscalendar.sh rename to distros/redhat/misc/jscalendar.sh index 80acaafec..068e47bf9 100755 --- a/distros/redhat/jscalendar.sh +++ b/distros/redhat/misc/jscalendar.sh @@ -1,6 +1,6 @@ #!/bin/bash -unzip -o jscalendar-1.0.zip +unzip -o misc/jscalendar-1.0.zip mkdir -v jscalendar-doc cd jscalendar-1.0 mv -v *html *php doc/* README ../jscalendar-doc diff --git a/distros/redhat/local_zoneminder.te b/distros/redhat/misc/local_zoneminder.te similarity index 100% rename from distros/redhat/local_zoneminder.te rename to distros/redhat/misc/local_zoneminder.te diff --git a/distros/redhat/redalert.wav b/distros/redhat/misc/redalert.wav similarity index 100% rename from distros/redhat/redalert.wav rename to distros/redhat/misc/redalert.wav diff --git a/distros/fedora/README.Fedora b/distros/redhat/nginx/README.Fedora similarity index 76% rename from distros/fedora/README.Fedora rename to distros/redhat/nginx/README.Fedora index 025699933..0a5168231 100644 --- a/distros/fedora/README.Fedora +++ b/distros/redhat/nginx/README.Fedora @@ -1,7 +1,10 @@ What's New ========== -1. The Apache ScriptAlias has been changed from "/cgi-bin/zm/zms" to +1. This is an *experimental* build of zoneminder which uses the + nginx web server. + +2. The Apache ScriptAlias has been changed from "/cgi-bin/zm/zms" to "/cgi-bin-zm/zms". This has been to done to avoid this bug: https://bugzilla.redhat.com/show_bug.cgi?id=973067 @@ -9,15 +12,15 @@ What's New and verify it is set to "/cgi-bin-zm/nph-zms". Failure to do so will result in a broken system. You have been warned. -2. Due to the active state of the ZoneMinder project, we now recommend granting +3. Due to the active state of the ZoneMinder project, we now recommend granting ALL permission to the ZoneMinder mysql account. This change must be done manually before ZoneMinder will run. See the installation steps below. -3. This package uses the HTTPS protocol by default to access the web portal. +4. This package uses the HTTPS protocol by default to access the web portal. Requests using HTTP will auto-redirect to HTTPS. See README.https for more information. -4. This package ships with the new ZoneMinder API enabled. +5. This package ships with the new ZoneMinder API enabled. New installs ============ @@ -27,7 +30,7 @@ New installs Mysql server, you need to ensure that the server is configured to start during boot and properly secured by running: - sudo yum install mariadb-server + sudo dnf install mariadb-server sudo systemctl enable mariadb sudo systemctl start mariadb.service mysql_secure_installation @@ -77,26 +80,36 @@ New installs SELINUX line from "enforcing" to "disabled". This change will take effect after a reboot. -6. Install mod_ssl or configure /etc/httpd/conf.d/zoneminder.conf to meet your - needs. This package comes preconfigured for HTTPS using the default self - signed certificate on your system. The recommended way to complete this step - is to simply install mod_ssl: - - sudo yum install mod_ssl +6. This package comes preconfigured for HTTPS using the default self signed + certificate on your system. We recommend you keep this configuration. If this does not meet your needs, then read README.https to - learn about alternatives. When in doubt, install mod_ssl. + learn about alternatives. -7. Now start the web server: +7. Edit /etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of + simulatneous streams the server should support. Generally, a good minimum + value for this equals the total number of cameras you expect to view at the + same time. - sudo systemctl enable httpd - sudo systemctl start httpd +8. Now start the web server: -8. Now start zoneminder: + sudo systemctl enable nginx + sudo systemctl start nginx + +9. Now start zoneminder: sudo systemctl enable zoneminder sudo systemctl start zoneminder +10.The Fedora repos have a ZoneMinder package available, but it does not + support ffmpeg or libvlc, which many modern IP cameras require. Most users + will want to prevent the ZoneMinder package in the Fedora repos from + overwriting the ZoneMinder package in zmrepo, during a future dnf update. To + prevent that from happening you must edit /etc/yum.repos.d/fedora.repo + and /etc/yum.repos.d/fedora-updates.repo. Add the line "exclude=zoneminder*" + without the quotes under the [fedora] and [fedora-updates] blocks, + respectively. + Upgrades ======== @@ -144,7 +157,9 @@ Upgrades sudo zmupdate.pl --user=root --pass= --version= -5. Now start zoneminder: +5. Now restart nginx and php-fpm then start and zoneminder: + sudo systemctl restart nginx + sudo systemctl restart php-fpm sudo systemctl start zoneminder diff --git a/distros/redhat/nginx/zoneminder.conf.in b/distros/redhat/nginx/zoneminder.conf.in new file mode 100644 index 000000000..b8ffd816a --- /dev/null +++ b/distros/redhat/nginx/zoneminder.conf.in @@ -0,0 +1,49 @@ +listen 443 ssl; +listen [::]:443 ssl; + +ssl_certificate "/etc/pki/tls/certs/localhost.crt"; +ssl_certificate_key "/etc/pki/tls/private/localhost.key"; +ssl_session_cache shared:SSL:1m; +ssl_session_timeout 10m; +ssl_ciphers PROFILE=SYSTEM; +ssl_prefer_server_ciphers on; + +# Auto-redirect HTTP requests to HTTPS +if ($scheme != "https") { + rewrite ^/?(zm)(.*)$ https://$host/$1$2 permanent; +} + +location /cgi-bin-zm { + gzip off; + alias "@ZM_CGIDIR@"; + + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_pass unix:/run/fcgiwrap.sock; +} + +location /zm { + gzip off; + alias "@ZM_WEBDIR@"; + index index.php; + + location ~ \.php$ { + if (!-f $request_filename) { return 404; } + expires epoch; + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_index index.php; + fastcgi_pass unix:/run/php-fpm/www.sock; + } + + location ~ \.(jpg|jpeg|gif|png|ico)$ { + access_log off; + expires 33d; + } + + location /zm/api/ { + alias "@ZM_WEBDIR@"; + rewrite ^/zm/api(.+)$ /zm/api/index.php?p=$1 last; + } +} + diff --git a/distros/redhat/nginx/zoneminder.php-fpm.conf.in b/distros/redhat/nginx/zoneminder.php-fpm.conf.in new file mode 100644 index 000000000..26e8c62cf --- /dev/null +++ b/distros/redhat/nginx/zoneminder.php-fpm.conf.in @@ -0,0 +1,10 @@ +# Change the user and group of the default pool to the web server account +[www] + +user = @WEB_USER@ +group = @WEB_GROUP@ + +# Uncomment these on machines with little memory +#pm = ondemand +#pm.max_children = 10 +#pm.process_idle_timeout = 10s diff --git a/distros/fedora/zoneminder.service.in b/distros/redhat/nginx/zoneminder.service.in similarity index 56% rename from distros/fedora/zoneminder.service.in rename to distros/redhat/nginx/zoneminder.service.in index 8c6214b42..7e2e36585 100644 --- a/distros/fedora/zoneminder.service.in +++ b/distros/redhat/nginx/zoneminder.service.in @@ -3,16 +3,20 @@ [Unit] Description=ZoneMinder CCTV recording and security system -After=network.target mariadb.service httpd.service -Requires=mariadb.service httpd.service +After=network.target mariadb.service nginx.service php-fpm.service fcgiwrap.service +Requires=mariadb.service nginx.service php-fpm.service fcgiwrap.service [Service] User=@WEB_USER@ +Group=@WEB_GROUP@ Type=forking ExecStart=@BINDIR@/zmpkg.pl start ExecReload=@BINDIR@/zmpkg.pl restart ExecStop=@BINDIR@/zmpkg.pl stop -PIDFile="@ZM_RUNDIR@/zm.pid" +PIDFile=@ZM_RUNDIR@/zm.pid +Environment=TZ=/etc/localtime +RuntimeDirectory=zoneminder +RuntimeDirectoryMode=0755 [Install] WantedBy=multi-user.target diff --git a/distros/redhat/nginx/zoneminder.tmpfiles.in b/distros/redhat/nginx/zoneminder.tmpfiles.in new file mode 100644 index 000000000..8040a7877 --- /dev/null +++ b/distros/redhat/nginx/zoneminder.tmpfiles.in @@ -0,0 +1,5 @@ +D @ZM_TMPDIR@ 0755 @WEB_USER@ @WEB_GROUP@ +D @ZM_SOCKDIR@ 0755 @WEB_USER@ @WEB_GROUP@ +D /var/lib/php/session 770 root @WEB_GROUP@ +D /var/lib/php/wsdlcache 770 root @WEB_GROUP@ + diff --git a/distros/redhat/readme/README.Fedora b/distros/redhat/readme/README.Fedora new file mode 100644 index 000000000..e3cca1f8c --- /dev/null +++ b/distros/redhat/readme/README.Fedora @@ -0,0 +1,155 @@ +What's New +========== + +1. ZoneMinder now uses a conf.d subfolder to process custom changes to + variables found in zm.conf. Changes to zm.conf will be overwritten + during an upgrade. Instead, create a file with a ".conf" extension under + the conf.d folder and make your changes there. + +2. ZoneMinder now supports recording directly to video container! This feature + is new and should be treated as experimental. Refer to the documentation + regarding how to use this feature. + +3. The Apache ScriptAlias has been changed from "/cgi-bin/zm/zms" to + "/cgi-bin-zm/zms". This has been to done to avoid this bug: + https://bugzilla.redhat.com/show_bug.cgi?id=973067 + + IMPORTANT: You must manually inspect the value for PATH_ZMS under Options + and verify it is set to "/cgi-bin-zm/nph-zms". Failure to do so will result + in a broken system. You have been warned. + +4. This package uses the HTTPS protocol by default to access the web portal. + Requests using HTTP will auto-redirect to HTTPS. See README.https for + more information. + +New installs +============ + +1. Unless you are already using MariaDB server, you need to ensure that the + server is configured to start during boot and properly secured by running: + + sudo dnf install mariadb-server + sudo systemctl enable mariadb + sudo systemctl start mariadb.service + mysql_secure_installation + +2. Assuming the database is local and using the password for the root account + set during the previous step, you will need to create the ZoneMinder + database and configure a database account for ZoneMinder to use: + + mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql + mysql -uroot -p -e "grant all on zm.* to \ + 'zmuser'@localhost identified by 'zmpass';" + mysqladmin -uroot -p reload + + The database account credentials, zmuser/zmpass, are arbitrary. Set them to + anything that suits your environment. + +3. If you have chosen to change the zoneminder database account credentials to + something other than zmuser/zmpass, you must now create a config file under + /etc/zm/conf.d and set your credentials there. For example, create the file + /etc/zm/conf.d/zm-db-user.conf and add the following content to it: + + ZM_DB_USER = {username of the sql account you want to use} + ZM_DB_PASS = {password of the sql account you want to use} + + Once the file has been saved, set proper file & ownership permissions on it: + + sudo chown root:apache *.conf + sudo chmod 640 *.conf + +4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local + timezone. PHP will complain loudly if this is not set, or if it is set + incorrectly, and these complaints will show up in the zoneminder logging + system as errors. + + If you are not sure of the proper timezone specification to use, look at + http://php.net/date.timezone + +5. Disable SELinux + + We currently do not have the resources to create and maintain an accurate + SELinux policy for ZoneMinder on Fedora. We will gladly accept pull + reqeusts from anyone who wishes to do the work. In the meantime, SELinux + will need to be disabled or put into permissive mode. + + To immediately disbale SELinux for the current seesion, issue the following + from the command line: + + sudo setenforce 0 + + To permanently disable SELinux, edit /etc/selinux/config and change the + SELINUX line from "enforcing" to "disabled". This change will take + effect after a reboot. + +6. Install mod_ssl or configure /etc/httpd/conf.d/zoneminder.conf to meet your + needs. This package comes preconfigured for HTTPS using the default self + signed certificate on your system. The recommended way to complete this step + is to simply install mod_ssl: + + sudo dnf install mod_ssl + + If this does not meet your needs, then read README.https to + learn about alternatives. When in doubt, install mod_ssl. + +7. Now start the web server: + + sudo systemctl enable httpd + sudo systemctl start httpd + +8. Now start zoneminder: + + sudo systemctl enable zoneminder + sudo systemctl start zoneminder + +9. The Fedora repos have a ZoneMinder package available, but it does not + support ffmpeg or libvlc, which many modern IP cameras require. Most users + will want to prevent the ZoneMinder package in the Fedora repos from + overwriting the ZoneMinder package in zmrepo, during a future dnf update. To + prevent that from happening you must edit /etc/yum.repos.d/fedora.repo + and /etc/yum.repos.d/fedora-updates.repo. Add the line "exclude=zoneminder*" + without the quotes under the [fedora] and [fedora-updates] blocks, + respectively. + +Upgrades +======== + +1. Conf.d folder support has been added to ZoneMinder 1.31.0. Any custom + changes previously made to zm.conf must now be made in one or more custom + config files, created under the conf.d folder. Do this now. See + /etc/zm/conf.d/README for details. Once you recreate any custom config changes + under the conf.d folder, they will remain in place indefinitely. + +2. Verify permissions of the zmuser account. + + Over time, the database account permissions required for normal operation + have increased. Verify the zmuser database account has been granted all + permission to the ZoneMinder database: + + mysql -uroot -p -e "show grants for zmuser@localhost;" + + See step 2 of the Installation section to add missing permissions. + +3. Verify the ZoneMinder Apache configuration file in the folder + /etc/httpd/conf.d. You will have a file called "zoneminder.conf" and there + may also be a file called "zoneminder.conf.rpmnew". If the rpmnew file + exists, inspect it and merge anything new in that file with zoneminder.conf. + Verify the SSL REquirements meet your needs. Read README.https if necessary. + +4. Upgrade the database before starting ZoneMinder. + + Most upgrades can be performed by executing the following command: + + sudo zmupdate.pl + + Recent versions of ZoneMinder don't require any parameters added to the + zmupdate command. However, if ZoneMinder complains, you may need to call + zmupdate in the following manner: + + sudo zmupdate.pl --user=root --pass= --version= + +5. Now restart the web server then start zoneminder: + + sudo systemctl restart httpd + sudo systemctl start zoneminder + diff --git a/distros/redhat/README.CentOS b/distros/redhat/readme/README.Redhat6 similarity index 59% rename from distros/redhat/README.CentOS rename to distros/redhat/readme/README.Redhat6 index f9c7ea1fb..939300d96 100644 --- a/distros/redhat/README.CentOS +++ b/distros/redhat/readme/README.Redhat6 @@ -1,15 +1,38 @@ What's New ========== -1. Due to the active state of the ZoneMinder project, we now recommend granting - ALL permission to the ZoneMinder mysql account. This change must be done - manually before ZoneMinder will run. See the installation steps below. +1. ***EOL NOTICE*** + It has become increasingly difficult to maintain the ZoneMinder project such + that it remains compatible with EL6 distros. The version of php shipped with + EL6 distros and the version of ffmpeg which will build against EL6 are too + old. It is with regret that I must announce our plans to stop supporting + ZoneMinder on EL6 distros soon. Your best option is to upgrade to an EL7 + distro or another distro with newer php & ffmpeg packages. Please note that + replacing core packages, such as php, will not be supported by us. You are + on your own should you choose to go down that path. -2. This package uses the HTTPS protocol by default to access the web portal. +2. ZoneMinder now uses a conf.d subfolder to process custom changes to + variables found in zm.conf. Changes to zm.conf will be overwritten + during an upgrade. Instead, create a file with a ".conf" extension under + th2 conf.d folder and make your changes there. + +3. ZoneMinder now supports recording directly to video container! This feature + is new and should be treated as experimental. Refer to the documentation + regarding how to use this feature. + +4. The Apache ScriptAlias has been changed from "/cgi-bin/zm/zms" to + "/cgi-bin-zm/zms". This has been to done match the configuration of + CentOS7/Fedora and simplify the build process. + + IMPORTANT: You must manually verify the value of PATH_ZMS under Options. + Make sure it is set to "/cgi-bin-zm/nph-zms". Failure to do so will result + in a broken system. You have been warned. + +5. This package uses the HTTPS protocol by default to access the web portal. Requests using HTTP will auto-redirect to HTTPS. See README.https for more information. -3. The php package that ships with CentOS 6 does not support the new ZoneMinder +6. The php package that ships with CentOS 6 does not support the new ZoneMinder API. If you require API functionality (such as using a mobile app) then you should consider an upgrade to CentOS 7 or use Fedora. @@ -20,6 +43,7 @@ New installs the server is confired to start during boot and properly secured by running: + sudo yum install mysql-server sudo service mysqld start /usr/bin/mysql_secure_installation sudo chkconfig mysqld on @@ -36,9 +60,18 @@ New installs The database account credentials, zmuser/zmpass, are arbitrary. Set them to anything that suits your environment. -3. If you have chosen to change the zoneminder mysql credentials to something - other than zmuser/zmpass then you must now edit /etc/zm.conf. Change - ZM_DB_USER and ZM_DB_PASS to the values you created in the previous step. +3. If you have chosen to change the zoneminder database account credentials to + something other than zmuser/zmpass, you must now create a config file under + /etc/zm/conf.d and set your credentials there. For example, create the file + /etc/zm/conf.d/zm-db-user.conf and add the following content to it: + + ZM_DB_USER = {username of the sql account you want to use} + ZM_DB_PASS = {password of the sql account you want to use} + + Once the file has been saved, set proper file & ownership permissions on it: + + sudo chown root:apache *.conf + sudo chmod 640 *.conf 4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local timezone. PHP will complain loudly if this is not set, or if it is set @@ -87,17 +120,11 @@ New installs Upgrades ======== -1. Verify /etc/zm.conf. - - If zm.conf was manually edited before running the upgrade, the installation - may not overwrite it. In this case, it will create the file - /etc/zm.conf.rpmnew. - - For example, this will happen if you are using database account credentials - other than zmuser/zmpass. - - Compare /etc/zm.conf to /etc/zm.conf.rpmnew. Verify that zm.conf - contains any new config settings that may be in zm.conf.rpmnew. +1. Conf.d folder support has been added to ZoneMinder 1.31.0. Any custom + changes previously made to zm.conf must now be made in one or more custom + config files, created under the conf.d folder. Do this now. See + /etc/zm/conf.d/README for details. Once you recreate any custom config changes + under the conf.d folder, they will remain in place indefinitely. 2. Verify permissions of the zmuser account. @@ -127,7 +154,8 @@ Upgrades sudo zmupdate.pl --user=root --pass= --version= -5. Now start zoneminder: +5. Now restart the web server then start zoneminder: + sudo service httpd restart sudo service zoneminder start diff --git a/distros/redhat/README.Centos7 b/distros/redhat/readme/README.Redhat7 similarity index 66% rename from distros/redhat/README.Centos7 rename to distros/redhat/readme/README.Redhat7 index 1cd26a1af..8817b925c 100644 --- a/distros/redhat/README.Centos7 +++ b/distros/redhat/readme/README.Redhat7 @@ -1,34 +1,37 @@ What's New ========== -1. The Apache ScriptAlias has been changed from "/cgi-bin/zm/zms" to +1. ZoneMinder now uses a conf.d subfolder to process custom changes to + variables found in zm.conf. Changes to zm.conf will be overwritten + during an upgrade. Instead, create a file with a ".conf" extension under + the conf.d folder and make your changes there. + +2. ZoneMinder now supports recording directly to video container! This feature + is new and should be treated as experimental. Refer to the documentation + regarding how to use this feature. + +3. The Apache ScriptAlias has been changed from "/cgi-bin/zm/zms" to "/cgi-bin-zm/zms". This has been to done to avoid this bug: https://bugzilla.redhat.com/show_bug.cgi?id=973067 - IMPORTANT: You must manually verify the value of PATH_ZMS under Options. - Make sure it is set to "/cgi-bin-zm/nph-zms". Failure to do so will result + IMPORTANT: You must manually inspect the value for PATH_ZMS under Options + and verify it is set to "/cgi-bin-zm/nph-zms". Failure to do so will result in a broken system. You have been warned. -2. Due to the active state of the ZoneMinder project, we now recommend granting - ALL permission to the ZoneMinder mysql account. This change must be done - manually before ZoneMinder will run. See the installation steps below. - -3. This package uses the HTTPS protocol by default to access the web portal. +4. This package uses the HTTPS protocol by default to access the web portal. Requests using HTTP will auto-redirect to HTTPS. See README.https for more information. - -4. This package ships with the new ZoneMinder API enabled. New installs ============ -1. Unless you are already using MariaDB server, you need to ensure that - the server is configured to start during boot and properly secured - by running: +1. Unless you are already using MariaDB server, you need to ensure that the + server is configured to start during boot and properly secured by running: + sudo dnf install mariadb-server sudo systemctl enable mariadb - sudo systemctl start mariadb - sudo mysql_secure_installation + sudo systemctl start mariadb.service + mysql_secure_installation 2. Using the password for the root account set during the previous step, you will need to create the ZoneMinder database and configure a database @@ -43,13 +46,17 @@ New installs anything that suits your environment. 3. If you have chosen to change the zoneminder database account credentials to - something other than zmuser/zmpass, you must now edit /etc/zm/zm.conf. - Change ZM_DB_USER and ZM_DB_PASS to the values you created in the previous - step. + something other than zmuser/zmpass, you must now create a config file under + /etc/zm/conf.d and set your credentials there. For example, create the file + /etc/zm/conf.d/zm-db-user.conf and add the following content to it: + + ZM_DB_USER = {username of the sql account you want to use} + ZM_DB_PASS = {password of the sql account you want to use} - This version of zoneminder no longer requires you to make a similar change - to the credentials in /usr/share/zoneminder/www/api/app/Config/database.php - This now happens dynamically. Do *not* make any changes to this file. + Once the file has been saved, set proper file & ownership permissions on it: + + sudo chown root:apache *.conf + sudo chmod 640 *.conf 4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local timezone. PHP will complain loudly if this is not set, or if it is set @@ -98,21 +105,11 @@ New installs Upgrades ======== -1. Verify /etc/zm/zm.conf. - - If zm.conf was manually edited before running the upgrade, the installation - may not overwrite it. In this case, it will create the file - /etc/zm/zm.conf.rpmnew. - - For example, this will happen if you are using database account credentials - other than zmuser/zmpass. - - Compare /etc/zm/zm.conf to /etc/zm/zm.conf.rpmnew. Verify that zm.conf - contains any new config settings that may be in zm.conf.rpmnew. - - This version of zoneminder no longer requires you to make a similar change - to the credentials in /usr/share/zoneminder/www/api/app/Config/database.php - This now happens dynamically. Do *not* make any changes to this file. +1. Conf.d folder support has been added to ZoneMinder 1.31.0. Any custom + changes previously made to zm.conf must now be made in one or more custom + config files, created under the conf.d folder. Do this now. See + /etc/zm/conf.d/README for details. Once you recreate any custom config changes + under the conf.d folder, they will remain in place indefinitely. 2. Verify permissions of the zmuser account. @@ -142,7 +139,9 @@ Upgrades sudo zmupdate.pl --user=root --pass= --version= -5. Now start zoneminder: +5. Now restart the web server then start zoneminder: + sudo systemctl restart httpd sudo systemctl start zoneminder + diff --git a/distros/redhat/README.https b/distros/redhat/readme/README.https similarity index 83% rename from distros/redhat/README.https rename to distros/redhat/readme/README.https index 23affeb96..7e4132a4a 100644 --- a/distros/redhat/README.https +++ b/distros/redhat/readme/README.https @@ -1,7 +1,7 @@ HTTPS is now a requirement ========================== -This package now depends on Apache's mod_ssl pacakge. This will automatically +This package now depends on Apache's mod_ssl package. This will automatically be installed along with ZoneMinder. Upon installation, the mod_ssl package will create a default, self-signed certificate. This is the certificate that ZoneMinder will use out of the box. @@ -23,3 +23,7 @@ here are a couple of considerations you may want to take. directives found in /etc/httpd/conf.d/zoneminder.conf. You should also comment out the HTTP -> HTTPS Rewrite rule. +3. Install a fully signed certificate from letsencrypt. See the Letsencrypt + site for more information. https://letsencrypt.org/ + This service is totally free! + diff --git a/distros/redhat/zoneminder.el7.conf.in b/distros/redhat/systemd/zoneminder.conf.in similarity index 89% rename from distros/redhat/zoneminder.el7.conf.in rename to distros/redhat/systemd/zoneminder.conf.in index 564e4ccbd..005c959a6 100644 --- a/distros/redhat/zoneminder.el7.conf.in +++ b/distros/redhat/systemd/zoneminder.conf.in @@ -11,6 +11,9 @@ RewriteRule ^/?(zm)(.*) https://%{SERVER_NAME}/$1$2 [R,L] Alias /zm "@ZM_WEBDIR@" + # explicitly set index.php as the only directoryindex + DirectoryIndex disabled + DirectoryIndex index.php SSLRequireSSL Options -Indexes +MultiViews +FollowSymLinks AllowOverride All diff --git a/distros/redhat/zoneminder.el7.logrotate.in b/distros/redhat/systemd/zoneminder.logrotate.in similarity index 100% rename from distros/redhat/zoneminder.el7.logrotate.in rename to distros/redhat/systemd/zoneminder.logrotate.in diff --git a/distros/redhat/zoneminder.service.in b/distros/redhat/systemd/zoneminder.service.in similarity index 74% rename from distros/redhat/zoneminder.service.in rename to distros/redhat/systemd/zoneminder.service.in index 7afe8473a..2234af036 100644 --- a/distros/redhat/zoneminder.service.in +++ b/distros/redhat/systemd/zoneminder.service.in @@ -7,11 +7,15 @@ Requires=mariadb.service httpd.service [Service] User=@WEB_USER@ +Group=@WEB_GROUP@ Type=forking ExecStart=@BINDIR@/zmpkg.pl start ExecReload=@BINDIR@/zmpkg.pl restart ExecStop=@BINDIR@/zmpkg.pl stop -PIDFile="@ZM_RUNDIR@/zm.pid" +PIDFile=@ZM_RUNDIR@/zm.pid +Environment=TZ=/etc/localtime +RuntimeDirectory=zoneminder +RuntimeDirectoryMode=0755 [Install] WantedBy=multi-user.target diff --git a/distros/redhat/zoneminder.tmpfiles.in b/distros/redhat/systemd/zoneminder.tmpfiles.in similarity index 66% rename from distros/redhat/zoneminder.tmpfiles.in rename to distros/redhat/systemd/zoneminder.tmpfiles.in index f655a9c9f..f3acd0af7 100644 --- a/distros/redhat/zoneminder.tmpfiles.in +++ b/distros/redhat/systemd/zoneminder.tmpfiles.in @@ -1,3 +1,2 @@ -D @ZM_RUNDIR@ 0755 @WEB_USER@ @WEB_GROUP@ D @ZM_TMPDIR@ 0755 @WEB_USER@ @WEB_GROUP@ D @ZM_SOCKDIR@ 0755 @WEB_USER@ @WEB_GROUP@ diff --git a/distros/redhat/zoneminder.el6.conf.in b/distros/redhat/sysvinit/zoneminder.conf.in similarity index 100% rename from distros/redhat/zoneminder.el6.conf.in rename to distros/redhat/sysvinit/zoneminder.conf.in diff --git a/distros/redhat/zoneminder.sysvinit.in b/distros/redhat/sysvinit/zoneminder.in similarity index 100% rename from distros/redhat/zoneminder.sysvinit.in rename to distros/redhat/sysvinit/zoneminder.in diff --git a/distros/redhat/zoneminder.el6.logrotate.in b/distros/redhat/sysvinit/zoneminder.logrotate.in similarity index 59% rename from distros/redhat/zoneminder.el6.logrotate.in rename to distros/redhat/sysvinit/zoneminder.logrotate.in index 5b852cba7..daf0b908f 100644 --- a/distros/redhat/zoneminder.el6.logrotate.in +++ b/distros/redhat/sysvinit/zoneminder.logrotate.in @@ -3,5 +3,5 @@ weekly notifempty missingok - create 660 http http + create 660 @WEB_USER@ @WEB_GROUP@ } diff --git a/distros/redhat/zoneminder.el6.spec b/distros/redhat/zoneminder.el6.spec deleted file mode 100644 index 0b7770b50..000000000 --- a/distros/redhat/zoneminder.el6.spec +++ /dev/null @@ -1,363 +0,0 @@ -%define zmuid $(id -un) -%define zmgid $(id -gn) -%define zmuid_final apache -%define zmgid_final apache - -Name: zoneminder -Version: 1.30.0 -Release: 1%{?dist} -Summary: A camera monitoring and analysis tool -Group: System Environment/Daemons -# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/ -# Mootools is inder the MIT license: http://mootools.net/ -# Cambozola is GPL: http://www.charliemouse.com/code/cambozola/ -License: GPLv2+ and LGPLv2+ and MIT -URL: http://www.zoneminder.com/ - -#Source0: https://github.com/ZoneMinder/ZoneMinder/archive/v%{version}.tar.gz -Source0: ZoneMinder-%{version}.tar.gz - -BuildRequires: cmake gnutls-devel bzip2-devel -BuildRequires: mysql-devel pcre-devel libjpeg-turbo-devel -BuildRequires: perl(Archive::Tar) perl(Archive::Zip) -BuildRequires: perl(Date::Manip) perl(DBD::mysql) -BuildRequires: perl(ExtUtils::MakeMaker) perl(LWP::UserAgent) -BuildRequires: perl(MIME::Entity) perl(MIME::Lite) -BuildRequires: perl(PHP::Serialization) perl(Sys::Mmap) -BuildRequires: perl(Time::HiRes) perl(Net::SFTP::Foreign) -BuildRequires: perl(Expect) perl(X10::ActiveHome) perl(Astro::SunTime) -BuildRequires: libcurl-devel vlc-devel ffmpeg-devel polkit-devel -# cmake needs the following installed at build time due to the way it auto-detects certain parameters -BuildRequires: httpd ffmpeg - -Requires: httpd php php-gd php-mysql mysql-server libjpeg-turbo cambozola polkit net-tools -Requires: psmisc perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) -Requires: perl(DBD::mysql) perl(Archive::Tar) perl(Archive::Zip) -Requires: perl(MIME::Entity) perl(MIME::Lite) perl(Net::SMTP) perl(Net::FTP) -Requires: libcurl vlc-core ffmpeg - -Requires(post): /sbin/chkconfig -Requires(post): /usr/bin/checkmodule -Requires(post): /usr/bin/semodule_package -Requires(post): /usr/sbin/semodule -Requires(post): /usr/bin/gpasswd -Requires(post): /usr/bin/less -Requires(preun): /sbin/chkconfig -Requires(preun): /sbin/service -Requires(preun): /usr/sbin/semodule -Requires(postun): /sbin/service - - -%description -ZoneMinder is a set of applications which is intended to provide a complete -solution allowing you to capture, analyse, record and monitor any cameras you -have attached to a Linux based machine. It is designed to run on kernels which -support the Video For Linux (V4L) interface and has been tested with cameras -attached to BTTV cards, various USB cameras and IP network cameras. It is -designed to support as many cameras as you can attach to your computer without -too much degradation of performance. - - -%prep -%setup -q -n ZoneMinder-%{version} - -# Change the following default values -./utils/zmeditconfigdata.sh ZM_PATH_ZMS /cgi-bin/zm/nph-zms -./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes -./utils/zmeditconfigdata.sh ZM_PATH_SWAP /dev/shm -./utils/zmeditconfigdata.sh ZM_UPLOAD_FTP_LOC_DIR /var/spool/zoneminder-upload -./utils/zmeditconfigdata.sh ZM_OPT_CONTROL yes -./utils/zmeditconfigdata.sh ZM_CHECK_FOR_UPDATES no -./utils/zmeditconfigdata.sh ZM_DYN_SHOW_DONATE_REMINDER no -./utils/zmeditconfigdata.sh ZM_OPT_FAST_DELETE no - -%build -# Have to override CMAKE_INSTALL_LIBDIR for cmake < 2.8.7 due to this bug: -# https://bugzilla.redhat.com/show_bug.cgi?id=795542 -%cmake -DZM_TARGET_DISTRO="el6" -DCMAKE_INSTALL_LIBDIR:PATH=%{_lib} . - -make %{?_smp_mflags} - -%install -export DESTDIR=%{buildroot} -make install - -%post -/sbin/chkconfig --add zoneminder -/sbin/chkconfig zoneminder on - -# Allow zoneminder access to local video sources, serial ports, and x10 -echo -/usr/bin/gpasswd -a %{zmuid_final} video -/usr/bin/gpasswd -a %{zmuid_final} dialout - -# Create and load zoneminder selinux policy module -echo -e "\nCreating and installing a ZoneMinder SELinux policy module. Please wait.\n" -/usr/bin/checkmodule -M -m -o %{_docdir}/%{name}-%{version}/local_zoneminder.mod %{_docdir}/%{name}-%{version}/local_zoneminder.te > /dev/null -/usr/bin/semodule_package -o %{_docdir}/%{name}-%{version}/local_zoneminder.pp -m %{_docdir}/%{name}-%{version}/local_zoneminder.mod > /dev/null -/usr/sbin/semodule -i %{_docdir}/%{name}-%{version}/local_zoneminder.pp > /dev/null - -# Upgrade from a previous version of zoneminder -if [ $1 -eq 2 ] ; then - - # Add any new PTZ control configurations to the database (will not overwrite) - %{_bindir}/zmcamtool.pl --import >/dev/null 2>&1 || : - - # Freshen the database - %{_bindir}/zmupdate.pl -f >/dev/null 2>&1 || : - - # We can't run this automatically when new sql account permissions need to - # be manually added first - # Run zmupdate non-interactively - #/usr/bin/zmupdate.pl --nointeractive -fi - -# Warn the end user to read the README file -echo -e "\nVERY IMPORTANT: Before starting ZoneMinder, read README.Centos to finish the\ninstallation or upgrade!\n" -echo -e "\nThe README file is located here: %{_docdir}/%{name}-%{version}.\n" - -%preun -if [ $1 -eq 0 ]; then - /sbin/service zoneminder stop > /dev/null 2>&1 || : - /sbin/chkconfig --del zoneminder - echo -e "\nRemoving ZoneMinder SELinux policy module. Please wait.\n" - /usr/sbin/semodule -r local_zoneminder.pp -fi - - -%postun -if [ $1 -ge 1 ]; then - /sbin/service zoneminder condrestart > /dev/null 2>&1 || : -fi - -# Remove the doc folder. -rm -rf %{_docdir}/%{name}-%{version} - -%files -%defattr(-,root,root,-) -%doc AUTHORS BUGS ChangeLog COPYING LICENSE NEWS README.md distros/redhat/README.CentOS distros/redhat/README.https distros/redhat/jscalendar-doc -%doc distros/redhat/local_zoneminder.te -%config %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm.conf -%config(noreplace) %attr(644,root,root) %{_sysconfdir}/httpd/conf.d/zoneminder.conf -%config(noreplace) /etc/logrotate.d/%{name} -%attr(755,root,root) %{_initrddir}/zoneminder - -%{_bindir}/zma -%{_bindir}/zmaudit.pl -%{_bindir}/zmc -%{_bindir}/zmcontrol.pl -%{_bindir}/zmdc.pl -%{_bindir}/zmf -%{_bindir}/zmfilter.pl -%{_bindir}/zmpkg.pl -%{_bindir}/zmtrack.pl -%{_bindir}/zmtrigger.pl -%{_bindir}/zmu -%{_bindir}/zmupdate.pl -%{_bindir}/zmvideo.pl -%{_bindir}/zmwatch.pl -%{_bindir}/zmcamtool.pl -%{_bindir}/zmsystemctl.pl -%{_bindir}/zmtelemetry.pl -%{_bindir}/zmx10.pl -%{_bindir}/zmonvif-probe.pl - -%{perl_vendorlib}/ZoneMinder* -%{perl_vendorarch}/auto/ZoneMinder/.packlist -%{perl_vendorarch}/auto/ONVIF/.packlist -%{perl_vendorlib}/ONVIF* -%{perl_vendorlib}/WSDiscovery* -%{perl_vendorlib}/WSSecurity* -%{perl_vendorlib}/WSNotification* -%{_mandir}/man*/* -%dir %{_libexecdir}/%{name} -%{_libexecdir}/%{name}/cgi-bin -%dir %{_datadir}/%{name} -%{_datadir}/%{name}/db -%{_datadir}/%{name}/www - -%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy -%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules - -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/lib/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/lib/zoneminder/events -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/lib/zoneminder/images -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/lib/zoneminder/sock -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/lib/zoneminder/swap -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/lib/zoneminder/temp -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/log/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/spool/zoneminder-upload - -%changelog -* Thu Mar 3 2016 Andrew Bauer - 1.30.0 -- Bump version fo 1.30.0 release. - -* Tue Sep 8 2015 Andrew Bauer - 1.28.1 -- Require https, freshen dB on updates. - -* Wed Feb 18 2015 Andrew Bauer - 1.28.1 -- Include ONVIF support files - -* Sun Oct 5 2014 Andrew Bauer - 1.28.0 -- Bump version for 1.28.0 release. - -* Fri Mar 14 2014 Andrew Bauer - 1.27 -- Tweak build requirements for cmake - -* Sat Feb 01 2014 Andrew Bauer - 1.27 -- Add zmcamtool.pl. Bump version for 1.27 release. - -* Mon Dec 16 2013 Andrew Bauer - 1.26.5 -- This is a bug fixe release -- RTSP fixes, cmake enhancements, couple other misc fixes - -* Sat Oct 19 2013 Andrew Bauer - 1.26.4 -- Streamline the cmake build. Move much code into cmakelist.txt file. - -* Mon Oct 07 2013 Andrew Bauer - 1.26.4 -- Initial cmake build. - -* Sun Oct 06 2013 Andrew Bauer - 1.26.4 -- All files are now part of the zoneminder source tree. Update specfile accordingly. - -* Thu Sep 05 2013 Andrew Bauer - 1.26.0 -- 1.26.0 Release -- https://github.com/ZoneMinder/ZoneMinder/archive/v1.26.0.tar.gz - -* Sun Sep 01 2013 Andrew Bauer - 1.26.0-beta -- Update SELinux policy module - -* Thu Aug 29 2013 Andrew Bauer - 1.26.0-beta -- Third Beta release -- https://github.com/ZoneMinder/ZoneMinder/tree/release-1.26 -- Reduce number of uneeded dependencies by integrating cambozola into spec file - -* Thu Aug 15 2013 Andrew Bauer - 1.26.0-beta -- Initial Beta release -- https://github.com/ZoneMinder/ZoneMinder/tree/release-1.26 - -* Sun Aug 11 2013 Andrew Bauer - 1.25.0-kfirproper -- Modified specfile to work with kfir-proper branch -- https://github.com/ZoneMinder/ZoneMinder/tree/kfir-proper - -* Wed Aug 07 2013 Andrew Bauer - 1.25.0-2svn3827 -- Move RHEL/CentOS specific defaults to a patch file -- Add bzip2-devel as a build dependency -- Default ZM_SSL_LIB back to gnutls. AUTH_RELAY = hashed didn't work with openssl. - -* Fri Aug 02 2013 Andrew Bauer - 1.25.0-1svn3827 -- Update to latest 1.25.0 subversion. -- Does not compile with modern versions of ffmpeg. Configure to work only with older versions. -- Does not compile with gcc 4.7. Configure to build with gcc less than 4.7. - -* Thu Mar 24 2011 Jason L Tibbitts III - 1.24.3-4.20110324svn3310 -- Update to latest 1.24.3 subversion. Turns out that what upstream was calling - 1.24.3 is really just an occasionally updated devel snapshot. -- Rebase various patches. - -* Wed Mar 23 2011 Dan Horák - 1.24.3-3 -- rebuilt for mysql 5.5.10 (soname bump in libmysqlclient) - -* Tue Feb 08 2011 Fedora Release Engineering - 1.24.3-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild - -* Tue Jan 25 2011 Jason L Tibbitts III - 1.24.3-1 -- Update to latest upstream version. -- Rebase patches. -- Initial incomplete attempt to disable v4l1 support. - -* Fri Jan 21 2011 Jason L Tibbitts III - 1.24.2-6 -- Unbundle cambozola; instead link to the separately pacakged copy. -- Remove BuildRoot:, %%clean and buildroot cleaning in %%install. -- Git rid of mixed space/tab usage by removing all tabs. -- Remove unnecessary Conflicts: line. -- Attempt to force short_open_tag on for the code directories. -- Move default location of sockets, swaps, logfiles and some temporary files to - make more sense and allow things to work better with a future selinux policy. -- Fix errors in README.CentOS. - -* Wed Jun 02 2010 Marcela Maslanova - 1.24.2-5 -- Mass rebuild with perl-5.12.0 - -* Fri Dec 4 2009 Stepan Kasal - 1.24.2-4 -- rebuild against perl 5.10.1 -- use Perl vendorarch and archlib variables correctly - -* Mon Jul 27 2009 Fedora Release Engineering - 1.24.2-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-2 -- Bump release since 1.24.2-1 was mistakenly tagged a few months ago. - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-1 -- Initial update to 1.24.2. -- Rebase patches. -- Update mootools download location. -- Update to mootools 1.2.3. -- Add additional dependencies for some optional features. - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-3 -- Remove unused Sys::Mmap perl dependency RPM is finding - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-2 -- Update gcc44 patch to disable -frepo, seems to be broken with gcc44 -- Added noffmpeg patch to make building outside mock easier - -* Sat Mar 21 2009 Martin Ebourne - 1.24.1-1 -- Patch for gcc 4.4 compilation errors -- Upgrade to 1.24.1 - -* Wed Feb 25 2009 Fedora Release Engineering - 1.23.3-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild - -* Sat Jan 24 2009 Caolán McNamara - 1.23.3-3 -- rebuild for dependencies - -* Mon Dec 15 2008 Martin Ebourne - 1.23.3-2 -- Fix permissions on zm.conf - -* Fri Jul 11 2008 Jason L Tibbitts III - 1.23.3-1 -- Initial attempt at packaging 1.23. - -* Tue Jul 1 2008 Martin Ebourne - 1.22.3-15 -- Add perl module compat dependency, bz #453590 - -* Tue May 6 2008 Martin Ebourne - 1.22.3-14 -- Remove default runlevel, bz #441315 - -* Mon Apr 28 2008 Jason L Tibbitts III - 1.22.3-13 -- Backport patch for CVE-2008-1381 from 1.23.3 to 1.22.3. - -* Tue Feb 19 2008 Fedora Release Engineering - 1.22.3-12 -- Autorebuild for GCC 4.3 - -* Thu Jan 3 2008 Martin Ebourne - 1.22.3-11 -- Fix compilation on gcc 4.3 - -* Thu Dec 6 2007 Martin Ebourne - 1.22.3-10 -- Rebuild for new openssl - -* Thu Aug 2 2007 Martin Ebourne - 1.22.3-8 -- Fix licence tag - -* Thu Jul 12 2007 Martin Ebourne - 1.22.3-7 -- Fixes from testing by Jitz including missing dependencies and database creation - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-6 -- Disable crashtrace on ppc - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-5 -- Fix uid for directories in /var/lib/zoneminder - -* Tue Jun 26 2007 Martin Ebourne - 1.22.3-4 -- Added perl Archive::Tar dependency -- Disabled web interface due to lack of access control on the event images - -* Sun Jun 10 2007 Martin Ebourne - 1.22.3-3 -- Changes recommended in review by Jason Tibbitts - -* Mon Apr 2 2007 Martin Ebourne - 1.22.3-2 -- Standardised on package name of zoneminder - -* Thu Dec 28 2006 Martin Ebourne - 1.22.3-1 -- First version. Uses some parts from zm-1.20.1 by Corey DeLasaux and Serg Oskin diff --git a/distros/redhat/zoneminder.el7.spec b/distros/redhat/zoneminder.el7.spec deleted file mode 100644 index b72e0bbe7..000000000 --- a/distros/redhat/zoneminder.el7.spec +++ /dev/null @@ -1,427 +0,0 @@ -%define zmuid $(id -un) -%define zmgid $(id -gn) -%define zmuid_final apache -%define zmgid_final apache - -%global _hardened_build 1 - -Name: zoneminder -Version: 1.30.0 -Release: 1%{?dist} -Summary: A camera monitoring and analysis tool -Group: System Environment/Daemons -# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/ -# Mootools is inder the MIT license: http://mootools.net/ -License: GPLv2+ and LGPLv2+ and MIT -URL: http://www.zoneminder.com/ - -#Source: https://github.com/ZoneMinder/ZoneMinder/archive/v%{version}.tar.gz -Source: ZoneMinder-%{version}.tar.gz - -BuildRequires: cmake gnutls-devel systemd-units bzip2-devel -BuildRequires: mariadb-devel pcre-devel libjpeg-turbo-devel -BuildRequires: perl(Archive::Tar) perl(Archive::Zip) perl-podlators -BuildRequires: perl(Date::Manip) perl(DBD::mysql) -BuildRequires: perl(ExtUtils::MakeMaker) perl(LWP::UserAgent) -BuildRequires: perl(MIME::Entity) perl(MIME::Lite) -BuildRequires: perl(PHP::Serialization) perl(Sys::Mmap) -BuildRequires: perl(Time::HiRes) perl(Net::SFTP::Foreign) -BuildRequires: perl(Expect) perl(Sys::Syslog) -BuildRequires: gcc gcc-c++ vlc-devel libcurl-devel libv4l-devel -BuildRequires: ffmpeg ffmpeg-devel perl(X10::ActiveHome) perl(Astro::SunTime) -# cmake needs the following installed at build time due to the way it auto-detects certain parameters -BuildRequires: httpd polkit-devel - -Requires: httpd php php-gd php-mysql mariadb-server cambozola polkit net-tools -Requires: psmisc libjpeg-turbo vlc-core libcurl -Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) -Requires: perl(DBD::mysql) perl(Archive::Tar) perl(Archive::Zip) -Requires: perl(MIME::Entity) perl(MIME::Lite) perl(Net::SMTP) perl(Net::FTP) -Requires: perl(LWP::Protocol::https) ffmpeg - -Requires(post): systemd-units systemd-sysv -Requires(post): /usr/bin/gpasswd -Requires(post): /usr/bin/less -Requires(preun): systemd-units -Requires(postun): systemd-units - -%description -ZoneMinder is a set of applications which is intended to provide a complete -solution allowing you to capture, analyse, record and monitor any cameras you -have attached to a Linux based machine. It is designed to run on kernels which -support the Video For Linux (V4L) interface and has been tested with cameras -attached to BTTV cards, various USB cameras and IP network cameras. It is -designed to support as many cameras as you can attach to your computer without -too much degradation of performance. - -%prep -%setup -q -n ZoneMinder-%{version} - -# Change the following default values -./utils/zmeditconfigdata.sh ZM_PATH_ZMS /cgi-bin-zm/nph-zms -./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes -./utils/zmeditconfigdata.sh ZM_PATH_SWAP /dev/shm -./utils/zmeditconfigdata.sh ZM_UPLOAD_FTP_LOC_DIR /var/spool/zoneminder-upload -./utils/zmeditconfigdata.sh ZM_OPT_CONTROL yes -./utils/zmeditconfigdata.sh ZM_CHECK_FOR_UPDATES no -./utils/zmeditconfigdata.sh ZM_DYN_SHOW_DONATE_REMINDER no -./utils/zmeditconfigdata.sh ZM_OPT_FAST_DELETE no - -%build -%cmake \ - -DZM_TARGET_DISTRO="el7" \ - . - -make %{?_smp_mflags} - -%install -export DESTDIR=%{buildroot} -make install - -%post -if [ $1 -eq 1 ] ; then - # Initial installation - /bin/systemctl daemon-reload >/dev/null 2>&1 || : -fi - -# Allow zoneminder access to local video sources, serial ports, and x10 -/usr/bin/gpasswd -a %{zmuid_final} video -/usr/bin/gpasswd -a %{zmuid_final} dialout - -# Disabled. SELinux policy does not work for RHEL 7. -# Create and load zoneminder selinux policy module -#echo -e "\nCreating and installing a ZoneMinder SELinux policy module. Please wait.\n" -#/usr/bin/checkmodule -M -m -o %{_docdir}/%{name}-%{version}/local_zoneminder.mod %{_docdir}/%{name}-%{version}/local_zoneminder.te > /dev/null -#/usr/bin/semodule_package -o %{_docdir}/%{name}-%{version}/local_zoneminder.pp -m %{_docdir}/%{name}-%{version}/local_zoneminder.mod > /dev/null -#/usr/sbin/semodule -i %{_docdir}/%{name}-%{version}/local_zoneminder.pp > /dev/null - -# Upgrade from a previous version of zoneminder -if [ $1 -eq 2 ] ; then - - # Add any new PTZ control configurations to the database (will not overwrite) - %{_bindir}/zmcamtool.pl --import >/dev/null 2>&1 || : - - # Freshen the database - %{_bindir}/zmupdate.pl -f >/dev/null 2>&1 || : - - # We can't run this automatically when new sql account permissions need to - # be manually added first - # Run zmupdate non-interactively - #/usr/bin/zmupdate.pl --nointeractive -fi - -# Warn the end user to read the README file -echo -e "\nVERY IMPORTANT: Before starting ZoneMinder, read README.Centos7 to finish the\ninstallation or upgrade!\n" -echo -e "\nThe README file is located here: %{_docdir}/%{name}-%{version}.\n" - -%preun -if [ $1 -eq 0 ] ; then - # Package removal, not upgrade - /bin/systemctl --no-reload disable zoneminder.service > /dev/null 2>&1 || : - /bin/systemctl stop zoneminder.service > /dev/null 2>&1 || : -# echo -e "\nRemoving ZoneMinder SELinux policy module. Please wait.\n" -# /usr/sbin/semodule -r local_zoneminder.pp -fi - -%postun -/bin/systemctl daemon-reload >/dev/null 2>&1 || : -if [ $1 -ge 1 ] ; then - # Package upgrade, not uninstall - /bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || : -fi - -%triggerun -- zoneminder < 1.25.0-4 -# Save the current service runlevel info -# User must manually run systemd-sysv-convert --apply zoneminder -# to migrate them to systemd targets -/usr/bin/systemd-sysv-convert --save zoneminder >/dev/null 2>&1 ||: - -# Run these because the SysV package being removed won't do them -/sbin/chkconfig --del zoneminder >/dev/null 2>&1 || : -/bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || : - -%files -%defattr(-,root,root,-) -%doc AUTHORS BUGS ChangeLog COPYING LICENSE NEWS README.md distros/redhat/README.Centos7 distros/redhat/README.https distros/redhat/jscalendar-doc -%doc distros/redhat/local_zoneminder.te -%config %attr(640,root,%{zmgid_final}) /etc/zm/zm.conf -%config(noreplace) %attr(644,root,root) /etc/httpd/conf.d/zoneminder.conf -%config(noreplace) /etc/tmpfiles.d/zoneminder.conf -%config(noreplace) /etc/logrotate.d/zoneminder - -%{_unitdir}/zoneminder.service - -%{_bindir}/zma -%{_bindir}/zmaudit.pl -%{_bindir}/zmc -%{_bindir}/zmcontrol.pl -%{_bindir}/zmdc.pl -%{_bindir}/zmf -%{_bindir}/zmfilter.pl -%{_bindir}/zmpkg.pl -%{_bindir}/zmtrack.pl -%{_bindir}/zmtrigger.pl -%{_bindir}/zmu -%{_bindir}/zmupdate.pl -%{_bindir}/zmvideo.pl -%{_bindir}/zmwatch.pl -%{_bindir}/zmcamtool.pl -%{_bindir}/zmsystemctl.pl -%{_bindir}/zmtelemetry.pl -%{_bindir}/zmx10.pl -%{_bindir}/zmonvif-probe.pl - -%{perl_vendorlib}/ZoneMinder* -%{perl_vendorarch}/auto/ZoneMinder/.packlist -%{perl_vendorarch}/auto/ONVIF/.packlist -%{perl_vendorlib}/ONVIF* -%{perl_vendorlib}/WSDiscovery* -%{perl_vendorlib}/WSSecurity* -%{perl_vendorlib}/WSNotification* -%{_mandir}/man*/* -%dir %{_libexecdir}/zoneminder -%{_libexecdir}/zoneminder/cgi-bin -%dir %{_datadir}/zoneminder -%{_datadir}/zoneminder/db -%{_datadir}/zoneminder/www - -%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy -%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules - -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/events -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/images -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/sock -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/swap -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/temp -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/log/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/spool/zoneminder-upload -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /run/zoneminder - - -%changelog -* Thu Mar 3 2016 Andrew Bauer - 1.30.0 -- Bump version fo 1.30.0 release. - -* Mon Sep 7 2015 Andrew Bauer - 1.28.1 -- Require https, disable selinux module, freshen dB on updates. - -* Sun Feb 8 2015 Andrew Bauer - 1.28.1 -- Initial release for CentOS 7. - -* Sun Oct 5 2014 Andrew Bauer - 1.28.0 -- Bump version for 1.28.0 release. - -* Fri Mar 14 2014 Andrew Bauer - 1.27 -- Tweak build requirements for cmake - -* Sat Feb 01 2014 Andrew Bauer - 1.27 -- Add zmcamtool.pl. Bump version for 1.27 release. - -* Mon Dec 16 2013 Andrew Bauer - 1.26.5 -- This is a bug fixe release -- RTSP fixes, cmake enhancements, couple other misc fixes - -* Mon Oct 07 2013 Andrew Bauer - 1.26.4 -- Initial cmake build. - -* Sat Oct 05 2013 Andrew Bauer - 1.26.4 -- Fedora specific path changes have been moved to zoneminder-1.26.0-defaults.patch -- All files are now part of the zoneminder source tree. Update specfile accordingly. - -* Sat Sep 21 2013 Andrew Bauer - 1.26.3 -- Initial rebuild for ZoneMinder 1.26.3 release. - -* Fri Feb 15 2013 Fedora Release Engineering - 1.25.0-13 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Mon Jan 21 2013 Adam Tkac - 1.25.0-12 -- rebuild due to "jpeg8-ABI" feature drop - -* Mon Jan 7 2013 Remi Collet - 1.25.0-11 -- fix configuration file for httpd 2.4, #871502 - -* Fri Dec 21 2012 Adam Tkac - 1.25.0-10 -- rebuild against new libjpeg - -* Thu Aug 09 2012 Jason L Tibbitts III - 1.25.0-9 -- Add patch to work around v4l2 api breakage in 3.5 kernel. - -* Sun Jul 22 2012 Fedora Release Engineering - 1.25.0-8 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild - -* Sat Jun 23 2012 Petr Pisar - 1.25.0-7 -- Perl 5.16 rebuild - -* Wed Mar 21 2012 Jason L Tibbitts III - 1.25.0-6 -- Fix stupid thinko in sql modifications. - -* Sat Feb 25 2012 Jason L Tibbitts III - 1.25.0-5 -- Clean up macro usage. - -* Sat Feb 25 2012 Jason L Tibbitts III - 1.25.0-4 -- Convert to systemd. -- Add tmpfiles.d configuration since the initscript isn't around to create - /run/zoneminder. -- Remove some pointless executable permissions. -- Add logrotate file. - -* Wed Feb 22 2012 Jason L Tibbitts III - 1.25.0-3 -- Update README.Fedora to reference systemctl and mention timezone info in - php.ini. -- Add proper default for EYEZM_LOG_TO_FILE. - - -* Thu Feb 09 2012 Jason L Tibbitts III - 1.25.0-2 -- Rebuild for new pcre. - -* Thu Jan 19 2012 Jason L Tibbitts III - 1.25.0-1 -- Update to 1.25.0 -- Fix gcc4.7 build problems. -- Drop gcc4.4 build fixes; for whatever reason they now break the build. -- Clean up old patches. -- Force setting of ZM_TMPDIR and ZM_RUNDIR. - -* Sat Jan 14 2012 Fedora Release Engineering - 1.24.4-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild - -* Thu Sep 15 2011 Jason L Tibbitts III - 1.24.4-3 -- Re-add the dist-tag that somehow got lost. - -* Thu Sep 15 2011 Jason L Tibbitts III - 1.24.4-2 -- Add patch for bug 711780 - fix syntax issue in Mapped.pm. -- Undo that patch, and undo another which was the cause of the whole mess. -- Fix up other patches so ZM_PATH_BUILD is both defined and useful. -- Make sure database creation mods actually take. -- Update Fedora-specific docs with some additional info. -- Use bundled mootools (javascript, so no guideline violation). -- Update download location. -- Update the gcrypt patch to actually work. -- Upstream changed the tarball without changing the version to patch a - vulnerability, so redownload. - -* Sun Aug 14 2011 Jason L Tibbitts III - 1.24.4-1 -- Initial attempt to upgrade to 1.24.4. -- Add patch from BZ 460310 to build against libgcrypt instead of requiring the - gnutls openssl libs. - -* Thu Jul 21 2011 Petr Sabata - 1.24.3-7.20110324svn3310 -- Perl mass rebuild - -* Wed Jul 20 2011 Petr Sabata - 1.24.3-6.20110324svn3310 -- Perl mass rebuild - -* Mon May 09 2011 Jason L Tibbitts III - 1.24.3-5.20110324svn3310 -- Bump for gnutls update. - -* Thu Mar 24 2011 Jason L Tibbitts III - 1.24.3-4.20110324svn3310 -- Update to latest 1.24.3 subversion. Turns out that what upstream was calling - 1.24.3 is really just an occasionally updated devel snapshot. -- Rebase various patches. - -* Wed Mar 23 2011 Dan Horák - 1.24.3-3 -- rebuilt for mysql 5.5.10 (soname bump in libmysqlclient) - -* Tue Feb 08 2011 Fedora Release Engineering - 1.24.3-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild - -* Tue Jan 25 2011 Jason L Tibbitts III - 1.24.3-1 -- Update to latest upstream version. -- Rebase patches. -- Initial incomplete attempt to disable v4l1 support. - -* Fri Jan 21 2011 Jason L Tibbitts III - 1.24.2-6 -- Unbundle cambozola; instead link to the separately pacakged copy. -- Remove BuildRoot:, %%clean and buildroot cleaning in %%install. -- Git rid of mixed space/tab usage by removing all tabs. -- Remove unnecessary Conflicts: line. -- Attempt to force short_open_tag on for the code directories. -- Move default location of sockets, swaps, logfiles and some temporary files to - make more sense and allow things to work better with a future selinux policy. -- Fix errors in README.Fedora. - -* Wed Jun 02 2010 Marcela Maslanova - 1.24.2-5 -- Mass rebuild with perl-5.12.0 - -* Fri Dec 4 2009 Stepan Kasal - 1.24.2-4 -- rebuild against perl 5.10.1 -- use Perl vendorarch and archlib variables correctly - -* Mon Jul 27 2009 Fedora Release Engineering - 1.24.2-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-2 -- Bump release since 1.24.2-1 was mistakenly tagged a few months ago. - -* Wed Jul 22 2009 Jason L Tibbitts III - 1.24.2-1 -- Initial update to 1.24.2. -- Rebase patches. -- Update mootools download location. -- Update to mootools 1.2.3. -- Add additional dependencies for some optional features. - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-3 -- Remove unused Sys::Mmap perl dependency RPM is finding - -* Sat Apr 11 2009 Martin Ebourne - 1.24.1-2 -- Update gcc44 patch to disable -frepo, seems to be broken with gcc44 -- Added noffmpeg patch to make building outside mock easier - -* Sat Mar 21 2009 Martin Ebourne - 1.24.1-1 -- Patch for gcc 4.4 compilation errors -- Upgrade to 1.24.1 - -* Wed Feb 25 2009 Fedora Release Engineering - 1.23.3-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild - -* Sat Jan 24 2009 Caolán McNamara - 1.23.3-3 -- rebuild for dependencies - -* Mon Dec 15 2008 Martin Ebourne - 1.23.3-2 -- Fix permissions on zm.conf - -* Fri Jul 11 2008 Jason L Tibbitts III - 1.23.3-1 -- Initial attempt at packaging 1.23. - -* Tue Jul 1 2008 Martin Ebourne - 1.22.3-15 -- Add perl module compat dependency, bz #453590 - -* Tue May 6 2008 Martin Ebourne - 1.22.3-14 -- Remove default runlevel, bz #441315 - -* Mon Apr 28 2008 Jason L Tibbitts III - 1.22.3-13 -- Backport patch for CVE-2008-1381 from 1.23.3 to 1.22.3. - -* Tue Feb 19 2008 Fedora Release Engineering - 1.22.3-12 -- Autorebuild for GCC 4.3 - -* Thu Jan 3 2008 Martin Ebourne - 1.22.3-11 -- Fix compilation on gcc 4.3 - -* Thu Dec 6 2007 Martin Ebourne - 1.22.3-10 -- Rebuild for new openssl - -* Thu Aug 2 2007 Martin Ebourne - 1.22.3-8 -- Fix licence tag - -* Thu Jul 12 2007 Martin Ebourne - 1.22.3-7 -- Fixes from testing by Jitz including missing dependencies and database creation - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-6 -- Disable crashtrace on ppc - -* Sat Jun 30 2007 Martin Ebourne - 1.22.3-5 -- Fix uid for directories in /var/lib/zoneminder - -* Tue Jun 26 2007 Martin Ebourne - 1.22.3-4 -- Added perl Archive::Tar dependency -- Disabled web interface due to lack of access control on the event images - -* Sun Jun 10 2007 Martin Ebourne - 1.22.3-3 -- Changes recommended in review by Jason Tibbitts - -* Mon Apr 2 2007 Martin Ebourne - 1.22.3-2 -- Standardised on package name of zoneminder - -* Thu Dec 28 2006 Martin Ebourne - 1.22.3-1 -- First version. Uses some parts from zm-1.20.1 by Corey DeLasaux and Serg Oskin diff --git a/distros/fedora/zoneminder.f23.spec b/distros/redhat/zoneminder.spec similarity index 56% rename from distros/fedora/zoneminder.f23.spec rename to distros/redhat/zoneminder.spec index 5335b90cc..4eab2a429 100644 --- a/distros/fedora/zoneminder.f23.spec +++ b/distros/redhat/zoneminder.spec @@ -1,60 +1,135 @@ -%define zmuid $(id -un) -%define zmgid $(id -gn) -%define zmuid_final apache -%define zmgid_final apache +%global zmuid_final apache +%global zmgid_final apache +# Crud is configured as a git submodule +%global crud_version 3.0.10 + +%if "%{zmuid_final}" == "nginx" +%global with_nginx 1 +%global wwwconfdir %{_sysconfdir}/nginx/default.d +%else +%global wwwconfdir %{_sysconfdir}/httpd/conf.d +%endif + +%global sslcert %{_sysconfdir}/pki/tls/certs/localhost.crt +%global sslkey %{_sysconfdir}/pki/tls/private/localhost.key + +# This will tell zoneminder's cmake process we are building against a known distro +%global zmtargetdistro %{?rhel:el%{rhel}}%{!?rhel:fc%{fedora}} + +# Fedora >= 25 needs apcu backwards compatibility module +%if 0%{?fedora} >= 25 +%global with_apcu_bc 1 +%endif + +# Include files for SysV init or systemd +%if 0%{?fedora} >= 15 || 0%{?rhel} >= 7 +%global with_init_systemd 1 +%else +%global with_init_sysv 1 +%endif + +%global readme_suffix %{?rhel:Redhat%{?rhel}}%{!?rhel:Fedora} %global _hardened_build 1 -### Delete the lines below to build with ffmpeg and/or x10 -%define _without_ffmpeg 1 -%define _without_x10 1 - Name: zoneminder -Version: 1.30.0 +Version: 1.30.4 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons # jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/ # Mootools is inder the MIT license: http://mootools.net/ +# CakePHP is under the MIT license: https://github.com/cakephp/cakephp +# Crud is under the MIT license: https://github.com/FriendsOfCake/crud License: GPLv2+ and LGPLv2+ and MIT URL: http://www.zoneminder.com/ -#Source: https://github.com/ZoneMinder/ZoneMinder/archive/v%{version}.tar.gz -Source: ZoneMinder-%{version}.tar.gz +Source0: https://github.com/ZoneMinder/ZoneMinder/archive/%{version}.tar.gz#/zoneminder-%{version}.tar.gz +Source1: https://github.com/FriendsOfCake/crud/archive/v%{crud_version}.tar.gz#/crud-%{crud_version}.tar.gz -BuildRequires: cmake gnutls-devel systemd-units bzip2-devel -BuildRequires: mariadb-devel pcre-devel libjpeg-turbo-devel -BuildRequires: perl(Archive::Tar) perl(Archive::Zip) perl-podlators -BuildRequires: perl(Date::Manip) perl(DBD::mysql) -BuildRequires: perl(ExtUtils::MakeMaker) perl(LWP::UserAgent) -BuildRequires: perl(MIME::Entity) perl(MIME::Lite) -BuildRequires: perl(PHP::Serialization) perl(Sys::Mmap) -BuildRequires: perl(Time::HiRes) perl(Net::SFTP::Foreign) -BuildRequires: perl(Expect) perl(Sys::Syslog) -BuildRequires: gcc gcc-c++ vlc-devel libcurl-devel libv4l-devel -%{!?_without_ffmpeg:BuildRequires: ffmpeg-devel} -%{!?_without_x10:BuildRequires: perl(X10::ActiveHome) perl(Astro::SunTime)} -# cmake needs the following installed at build time due to the way it auto-detects certain parameters -BuildRequires: httpd polkit-devel -%{!?_without_ffmpeg:BuildRequires: ffmpeg} +%{?with_init_systemd:BuildRequires: systemd-devel} +%{?with_init_systemd:BuildRequires: mariadb-devel} +%{?with_init_systemd:BuildRequires: perl-podlators} +%{?with_init_systemd:BuildRequires: polkit-devel} +%{?with_init_sysv:BuildRequires: mysql-devel} +%{?el6:BuildRequires: epel-rpm-macros} +BuildRequires: cmake >= 2.8.7 +BuildRequires: gnutls-devel +BuildRequires: bzip2-devel +BuildRequires: pcre-devel +BuildRequires: libjpeg-turbo-devel +BuildRequires: findutils +BuildRequires: coreutils +BuildRequires: perl +BuildRequires: perl-generators +BuildRequires: perl(Archive::Tar) +BuildRequires: perl(Archive::Zip) +BuildRequires: perl(Date::Manip) +BuildRequires: perl(DBD::mysql) +BuildRequires: perl(ExtUtils::MakeMaker) +BuildRequires: perl(LWP::UserAgent) +BuildRequires: perl(MIME::Entity) +BuildRequires: perl(MIME::Lite) +BuildRequires: perl(PHP::Serialization) +BuildRequires: perl(Sys::Mmap) +BuildRequires: perl(Time::HiRes) +BuildRequires: perl(Net::SFTP::Foreign) +BuildRequires: perl(Expect) +BuildRequires: perl(Sys::Syslog) +BuildRequires: gcc +BuildRequires: gcc-c++ +BuildRequires: vlc-devel +BuildRequires: libcurl-devel +BuildRequires: libv4l-devel +BuildRequires: ffmpeg-devel -Requires: httpd php php-gd php-mysql cambozola polkit net-tools psmisc -Requires: libjpeg-turbo vlc-core libcurl +%{?with_nginx:Requires: nginx} +%{?with_nginx:Requires: fcgiwrap} +%{?with_nginx:Requires: php-fpm} +%{!?with_nginx:Requires: httpd} +%{!?with_nginx:Requires: php} +Requires: php-mysqli +Requires: php-common +Requires: php-gd +Requires: php-pecl-apcu +%{?with_apcu_bc:Requires: php-pecl-apcu-bc} +Requires: cambozola +Requires: net-tools +Requires: psmisc +Requires: polkit +Requires: libjpeg-turbo +Requires: vlc-core +Requires: ffmpeg Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) -Requires: perl(DBD::mysql) perl(Archive::Tar) perl(Archive::Zip) -Requires: perl(MIME::Entity) perl(MIME::Lite) perl(Net::SMTP) perl(Net::FTP) +Requires: perl(DBD::mysql) +Requires: perl(Archive::Tar) +Requires: perl(Archive::Zip) +Requires: perl(MIME::Entity) +Requires: perl(MIME::Lite) +Requires: perl(Net::SMTP) +Requires: perl(Net::FTP) Requires: perl(LWP::Protocol::https) -%{!?_without_ffmpeg:Requires: ffmpeg} -Requires(post): systemd-units systemd-sysv -Requires(post): /usr/bin/gpasswd -Requires(post): /usr/bin/less -Requires(preun): systemd-units -Requires(postun): systemd-units +%{?with_init_systemd:Requires(post): systemd} +%{?with_init_systemd:Requires(post): systemd-sysv} +%{?with_init_systemd:Requires(preun): systemd} +%{?with_init_systemd:Requires(postun): systemd} + +%{?with_init_sysv:Requires(post): /sbin/chkconfig} +%{?with_init_sysv:Requires(post): %{_bindir}/checkmodule} +%{?with_init_sysv:Requires(post): %{_bindir}/semodule_package} +%{?with_init_sysv:Requires(post): %{_sbindir}/semodule} +%{?with_init_sysv:Requires(preun): /sbin/chkconfig} +%{?with_init_sysv:Requires(preun): /sbin/service} +%{?with_init_sysv:Requires(preun): %{_sbindir}/semodule} +%{?with_init_sysv:Requires(postun): /sbin/service} + +Requires(post): %{_bindir}/gpasswd +Requires(post): %{_bindir}/less %description ZoneMinder is a set of applications which is intended to provide a complete -solution allowing you to capture, analyse, record and monitor any cameras you +solution allowing you to capture, analyze, record and monitor any cameras you have attached to a Linux based machine. It is designed to run on kernels which support the Video For Linux (V4L) interface and has been tested with cameras attached to BTTV cards, various USB cameras and IP network cameras. It is @@ -62,13 +137,15 @@ designed to support as many cameras as you can attach to your computer without too much degradation of performance. %prep -%setup -q -n ZoneMinder-%{version} +%autosetup -p 1 -a 1 -n ZoneMinder-%{version} +%{__rm} -rf ./web/api/app/Plugin/Crud +%{__mv} -f crud-%{crud_version} ./web/api/app/Plugin/Crud # Change the following default values ./utils/zmeditconfigdata.sh ZM_PATH_ZMS /cgi-bin-zm/nph-zms ./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes ./utils/zmeditconfigdata.sh ZM_PATH_SWAP /dev/shm -./utils/zmeditconfigdata.sh ZM_UPLOAD_FTP_LOC_DIR /var/spool/zoneminder-upload +./utils/zmeditconfigdata.sh ZM_UPLOAD_FTP_LOC_DIR %{_localstatedir}/spool/zoneminder-upload ./utils/zmeditconfigdata.sh ZM_OPT_CONTROL yes ./utils/zmeditconfigdata.sh ZM_CHECK_FOR_UPDATES no ./utils/zmeditconfigdata.sh ZM_DYN_SHOW_DONATE_REMINDER no @@ -76,30 +153,38 @@ too much degradation of performance. %build %cmake \ - -DZM_TARGET_DISTRO="f23" \ -%{?_without_ffmpeg:-DZM_NO_FFMPEG=ON} \ -%{?_without_x10:-DZM_NO_X10=ON} \ - . + -DZM_WEB_USER="%{zmuid_final}" \ + -DZM_WEB_GROUP="%{zmuid_final}" \ + -DZM_TARGET_DISTRO="%{zmtargetdistro}" \ + . -make %{?_smp_mflags} +%make_build %install -export DESTDIR=%{buildroot} -make install +%make_install + +# Remove unwanted files and folders +find %{buildroot} \( -name .packlist -or -name .git -or -name .gitignore -or -name .gitattributes -or -name .travis.yml \) -type f -delete > /dev/null 2>&1 || : %post +%if 0%{?with_init_sysv} +/sbin/chkconfig --add zoneminder +/sbin/chkconfig zoneminder on -# Add any new PTZ control configurations to the database (will not overwrite) -%{_bindir}/zmcamtool.pl --import >/dev/null 2>&1 || : +# Create and load zoneminder selinux policy module +echo -e "\nCreating and installing a ZoneMinder SELinux policy module. Please wait.\n" +%{_bindir}/checkmodule -M -m -o %{_docdir}/%{name}-%{version}/local_zoneminder.mod %{_docdir}/%{name}-%{version}/local_zoneminder.te > /dev/null 2>&1 || : +%{_bindir}/semodule_package -o %{_docdir}/%{name}-%{version}/local_zoneminder.pp -m %{_docdir}/%{name}-%{version}/local_zoneminder.mod > /dev/null 2>&1 || : +%{_sbindir}/semodule -i %{_docdir}/%{name}-%{version}/local_zoneminder.pp > /dev/null 2>&1 || : +%endif + +%if 0%{?with_init_systemd} +# Initial installation if [ $1 -eq 1 ] ; then - # Initial installation - /bin/systemctl daemon-reload >/dev/null 2>&1 || : + %systemd_post %{name}.service fi - -# Allow zoneminder access to local video sources, serial ports, and x10 -/usr/bin/gpasswd -a %{zmuid_final} video -/usr/bin/gpasswd -a %{zmuid_final} dialout +%endif # Upgrade from a previous version of zoneminder if [ $1 -eq 2 ] ; then @@ -113,54 +198,119 @@ if [ $1 -eq 2 ] ; then # We can't run this automatically when new sql account permissions need to # be manually added first # Run zmupdate non-interactively - #/usr/bin/zmupdate.pl --nointeractive + # zmupdate.pl --nointeractive fi +# Allow zoneminder access to local video sources, serial ports, and x10 +%{_bindir}/gpasswd -a %{zmuid_final} video >/dev/null 2>&1 || : +%{_bindir}/gpasswd -a %{zmuid_final} dialout >/dev/null 2>&1 || : + # Warn the end user to read the README file -echo -e "\nVERY IMPORTANT: Before starting ZoneMinder, read README.Fedora to finish the\ninstallation or upgrade!\n" +echo -e "\nVERY IMPORTANT: Before starting ZoneMinder, read README.%{readme_suffix} to finish the\ninstallation or upgrade!\n" echo -e "\nThe README file is located here: %{_docdir}/%{name}\n" -%preun -if [ $1 -eq 0 ] ; then - # Package removal, not upgrade - /bin/systemctl --no-reload disable zoneminder.service > /dev/null 2>&1 || : - /bin/systemctl stop zoneminder.service > /dev/null 2>&1 || : +%if 0%{?with_nginx} +# Nginx does not create an SSL certificate like the apache package does so lets do that here +if [ -f %{sslkey} -o -f %{sslcert} ]; then + exit 0 fi +umask 077 +%{_bindir}/openssl genrsa -rand /proc/apm:/proc/cpuinfo:/proc/dma:/proc/filesystems:/proc/interrupts:/proc/ioports:/proc/pci:/proc/rtc:/proc/uptime 2048 > %{sslkey} 2> /dev/null + +FQDN=`hostname` +# A >59 char FQDN means "root@FQDN" exceeds 64-char max length for emailAddress +if [ "x${FQDN}" = "x" -o ${#FQDN} -gt 59 ]; then + FQDN=localhost.localdomain +fi + +cat << EOF | %{_bindir}/openssl req -new -key %{sslkey} \ + -x509 -sha256 -days 365 -set_serial $RANDOM -extensions v3_req \ + -out %{sslcert} 2>/dev/null +-- +SomeState +SomeCity +SomeOrganization +SomeOrganizationalUnit +${FQDN} +root@${FQDN} +EOF +%endif + +%preun +%if 0%{?with_init_sysv} +if [ $1 -eq 0 ]; then + /sbin/service zoneminder stop > /dev/null 2>&1 || : + /sbin/chkconfig --del zoneminder + echo -e "\nRemoving ZoneMinder SELinux policy module. Please wait.\n" + %{_sbindir}/semodule -r local_zoneminder.pp +fi +%endif + +%if 0%{?with_init_systemd} +%systemd_preun %{name}.service +%endif + %postun -/bin/systemctl daemon-reload >/dev/null 2>&1 || : -if [ $1 -ge 1 ] ; then - # Package upgrade, not uninstall - /bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || : +%if 0%{?with_init_sysv} +if [ $1 -ge 1 ]; then + /sbin/service zoneminder condrestart > /dev/null 2>&1 || : fi +# Remove the doc folder. +rm -rf %{_docdir}/%{name}-%{version} +%endif + +%if 0%{?with_init_systemd} +%systemd_postun_with_restart %{name}.service +%endif + +%if 0%{?with_init_systemd} %triggerun -- zoneminder < 1.25.0-4 # Save the current service runlevel info # User must manually run systemd-sysv-convert --apply zoneminder # to migrate them to systemd targets -/usr/bin/systemd-sysv-convert --save zoneminder >/dev/null 2>&1 ||: +%{_bindir}/systemd-sysv-convert --save zoneminder >/dev/null 2>&1 ||: # Run these because the SysV package being removed won't do them /sbin/chkconfig --del zoneminder >/dev/null 2>&1 || : /bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || : - +%endif %files -%defattr(-,root,root,-) -%doc AUTHORS COPYING README.md distros/fedora/README.Fedora distros/fedora/README.https distros/fedora/jscalendar-doc -%config %attr(640,root,%{zmgid_final}) /etc/zm/zm.conf -%config(noreplace) %attr(644,root,root) /etc/httpd/conf.d/zoneminder.conf -%config(noreplace) /etc/tmpfiles.d/zoneminder.conf -%config(noreplace) /etc/logrotate.d/zoneminder +%license COPYING +%doc AUTHORS README.md distros/redhat/readme/README.%{readme_suffix} distros/redhat/readme/README.https distros/redhat/jscalendar-doc +%dir %{_sysconfdir}/zm +%dir %{_sysconfdir}/zm/conf.d +%{_sysconfdir}/zm/conf.d/README +# Always overwrite zm.conf now that ZoneMinder supports conf.d folder +%attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/zm.conf +%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/*.conf +%config(noreplace) %attr(644,root,root) %{wwwconfdir}/zoneminder.conf +%config(noreplace) %{_sysconfdir}/logrotate.d/zoneminder + +%if 0%{?with_nginx} +%config(noreplace) %{_sysconfdir}/php-fpm.d/zoneminder.conf +%endif + +%if 0%{?with_init_systemd} +%{_tmpfilesdir}/zoneminder.conf %{_unitdir}/zoneminder.service +%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy +%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules +%endif + +%if 0%{?with_init_sysv} +%doc distros/redhat/misc/local_zoneminder.te +%attr(755,root,root) %{_initrddir}/zoneminder +%endif %{_bindir}/zma %{_bindir}/zmaudit.pl %{_bindir}/zmc %{_bindir}/zmcontrol.pl %{_bindir}/zmdc.pl -%{_bindir}/zmf %{_bindir}/zmfilter.pl %{_bindir}/zmpkg.pl %{_bindir}/zmtrack.pl @@ -172,7 +322,7 @@ fi %{_bindir}/zmcamtool.pl %{_bindir}/zmsystemctl.pl %{_bindir}/zmtelemetry.pl -%{!?_without_x10:%{_bindir}/zmx10.pl} +%{_bindir}/zmx10.pl %{_bindir}/zmonvif-probe.pl %{perl_vendorlib}/ZoneMinder* @@ -181,57 +331,73 @@ fi %{perl_vendorlib}/WSSecurity* %{perl_vendorlib}/WSNotification* %{_mandir}/man*/* -%dir %{_libexecdir}/zoneminder -%{_libexecdir}/zoneminder/cgi-bin -%dir %{_datadir}/zoneminder -%{_datadir}/zoneminder/db -%{_datadir}/zoneminder/www -%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy -%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules - -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/events -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/images -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/sock -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/swap -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/lib/zoneminder/temp -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/log/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/spool/zoneminder-upload -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /run/zoneminder +%{_libexecdir}/zoneminder/ +%{_datadir}/zoneminder/ +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/events +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/images +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/sock +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/swap +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/log/zoneminder +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/spool/zoneminder-upload +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/run/zoneminder %changelog -* Thu Mar 3 2016 Andrew Bauer - 1.30.0 +* Tue May 09 2017 Andrew Bauer - 1.30.4-1 +- modify autosetup macro parameters +- modify requirements for php-pecl-acpu-bc package +- 1.30.4 release + +* Tue May 02 2017 Andrew Bauer - 1.30.3-1 +- 1.30.3 release + +* Thu Mar 30 2017 Andrew Bauer - 1.30.2-2 +- 1.30.2 release + +* Wed Feb 08 2017 Andrew Bauer - 1.30.2-1 +- Bump version for 1.30.2 release candidate 1 + +* Wed Dec 28 2016 Andrew Bauer - 1.30.1-2 +- Changes from rpmfusion #4393 + +* Fri Dec 23 2016 Andrew Bauer - 1.30.1-1 +- Consolidate fedora/centos spec files +- Add preliminary nginx support +- New contact email + +* Thu Mar 3 2016 Andrew Bauer - 1.30.0-1 - Bump version fo 1.30.0 release. -* Sat Nov 21 2015 Andrew Bauer - 1.29.0 +* Sat Nov 21 2015 Andrew Bauer - 1.29.0-1 - Bump version for 1.29.0 release on Fedora 23. -* Sat Feb 14 2015 Andrew Bauer - 1.28.1 +* Sat Feb 14 2015 Andrew Bauer - 1.28.1-1 - Bump version for 1.28.1 release on Fedora 21. -* Sun Oct 5 2014 Andrew Bauer - 1.28.0 +* Sun Oct 5 2014 Andrew Bauer - 1.28.0-1 - Bump version for 1.28.0 release. -* Fri Mar 14 2014 Andrew Bauer - 1.27 +* Fri Mar 14 2014 Andrew Bauer - 1.27-1 - Tweak build requirements for cmake -* Sat Feb 01 2014 Andrew Bauer - 1.27 +* Sat Feb 01 2014 Andrew Bauer - 1.27-1 - Add zmcamtool.pl. Bump version for 1.27 release. -* Mon Dec 16 2013 Andrew Bauer - 1.26.5 +* Mon Dec 16 2013 Andrew Bauer - 1.26.5-1 - This is a bug fixe release - RTSP fixes, cmake enhancements, couple other misc fixes -* Mon Oct 07 2013 Andrew Bauer - 1.26.4 +* Mon Oct 07 2013 Andrew Bauer - 1.26.4-1 - Initial cmake build. -* Sat Oct 05 2013 Andrew Bauer - 1.26.4 +* Sat Oct 05 2013 Andrew Bauer - 1.26.4-1 - Fedora specific path changes have been moved to zoneminder-1.26.0-defaults.patch - All files are now part of the zoneminder source tree. Update specfile accordingly. -* Sat Sep 21 2013 Andrew Bauer - 1.26.3 +* Sat Sep 21 2013 Andrew Bauer - 1.26.3-1 - Initial rebuild for ZoneMinder 1.26.3 release. * Fri Feb 15 2013 Fedora Release Engineering - 1.25.0-13 diff --git a/distros/ubuntu1204/NEWS b/distros/ubuntu1204/NEWS index 6200726cf..e69de29bb 100644 --- a/distros/ubuntu1204/NEWS +++ b/distros/ubuntu1204/NEWS @@ -1,10 +0,0 @@ -zoneminder (1.28.1-1) unstable; urgency=low - - This version is no longer automatically initialize or upgrade database. - See README.Debian for details. - - Changed installation paths (please correct your web server configuration): - /usr/share/zoneminder --> /usr/share/zoneminder/www - /usr/lib/cgi-bin --> /usr/lib/zoneminder/cgi-bin - - -- Dmitry Smirnov Tue, 31 Mar 2015 15:12:17 +1100 diff --git a/distros/ubuntu1204/changelog b/distros/ubuntu1204/changelog index 74cf1d0b8..e69de29bb 100644 --- a/distros/ubuntu1204/changelog +++ b/distros/ubuntu1204/changelog @@ -1,573 +0,0 @@ -zoneminder (1.28.1+1-vivid-SNAPSHOT2015081701) vivid; urgency=medium - - * include api, switch to cmake build - - -- Isaac Connor Mon, 17 Aug 2015 10:29:23 -0400 - - -zoneminder (1.28.1-8) unstable; urgency=medium - - * Patchworks: - + New upstream "980-fix-image-size.patch". - + New "default_cgi-path.patch" to correct default ZM_PATH_ZMS. - * postinst: set "root" as group owner for "/var/log/zm" to silence - logrotate warnings. - * Minor correction to README.Debian. - - -- Dmitry Smirnov Sun, 16 Aug 2015 19:19:50 +1000 - -zoneminder (1.28.1-7) unstable; urgency=medium - - * Build-Depends += "cakephp (<< 3.0.0~)"; - Zoneminder is not compatible with latest CakePHP. - * Handle conffile removal from maintscript. - * rules: build man pages reproducibly. - * gbp.conf: renamed old style config section [git-dch] to [dch]. - * README - + added instructions to update owner of the "/etc/zm/zm.conf" - (Closes: #789327). - + zmupdate.pl needs CREATE rights. - + added note about required number of "fcgiwrap" workers. - * New upstream patch: "zmtrigger-plus.patch". - - -- Dmitry Smirnov Mon, 20 Jul 2015 16:30:15 +1000 - -zoneminder (1.28.1-6) unstable; urgency=low - - * New "zoneminder-doc" and "zoneminder-dbg" packages. - - -- Dmitry Smirnov Sun, 19 Apr 2015 14:50:41 +1000 - -zoneminder (1.28.1-5) unstable; urgency=low - - * Move handling of "/var/run/zm" and "/tmp/zm" from .service into .tmpfile. - Let dh_installinit do the job. Thanks, Andrew Bauer. - * Use dh_apache2 to install Apache conf file; remove old conf and symlink. - * Promote "libapache2-mod-php5 | php5-fpm" to Recommends. - * Build-Depends: - + dh-linktree - + cakephp (>= 2.6.3) - + libjs-jquery - + libjs-mootools - * Depends: - - libjs-jquery - - libjs-mootools - * Build-time replace bundled CakePHP with system one using "dh-linktree". - * Use "dh-linktree" to handle mootools and jquery symlinks. - - -- Dmitry Smirnov Sun, 19 Apr 2015 11:45:01 +1000 - -zoneminder (1.28.1-4) unstable; urgency=low - - * New patch to fix HTML export with USE_DEEP_STORAGE (closes: #723706). - * New "783.patch" to describe potential data loss in ZM_USE_DEEP_STORAGE. - * New patch to change default date format to region-neutral ISO notation - with time zone. - * Build sphinx documentation: - + Install "zoneminder.1" man page. - + Build-Depends += "python-sphinx | python3-sphinx" - + Added commented "zoneminder-doc" package. - + Added "docs.patch" to unlink distro-specific installation docs. - * rules: - + set ZM_CONTENTDIR, ZM_SOCKDIR and ZM_TMPDIR. - + remove mistakengly installed Perl module templates. - * Updated startup scripts to create ZM_TMPDIR. - * Hurd improvements: - + New patch to add PATH_MAX definitions. - + Build without MMAP support on Hurd. - + libsys-mmap-perl [!hurd-any]. - - -- Dmitry Smirnov Mon, 06 Apr 2015 18:18:55 +1000 - -zoneminder (1.28.1-3) unstable; urgency=low - - * Updated Apache2 and nginx configuration templates to support CGI. - * Updated README.Debian to document cgi-bin setup. - * Removed "/usr/share/zoneminder/www/cgi-bin" symlink. - * Added "apache2.patch" to correct Apache2 site configuration example. - * control: Suggests += "fcgiwrap". - * rules: added dh_systemd overrides to prevent automatic service - activation and start. - * Added note about manual service activation to README.Debian - (Closes: #781733). - - -- Dmitry Smirnov Thu, 02 Apr 2015 23:20:20 +1100 - -zoneminder (1.28.1-2) unstable; urgency=low - - * Removed word "Linux" from short package description. - * Build-Depends: do not require "libv4l-dev" on Hurd i.e. [!hurd-any]. - * Added run-time Perl Depends: - + libdbd-mysql-perl - + libimage-info-perl - + libmodule-load-conditional-perl - + libnet-sftp-foreign-perl - + liburi-encode-perl - * Prepare for package split: added commented "libzoneminder-perl" - and "zoneminder-dbg" packages to "debian/control". - * rules: do not install worthless ".packlist" file. - * Updated "libv4l1-videodev.h.patch" to fix v4lv1 detection in CMake. - - -- Dmitry Smirnov Thu, 02 Apr 2015 13:25:19 +1100 - -zoneminder (1.28.1-1) unstable; urgency=low - - [ Dmitry Smirnov ] - * New upstream release [February 2015]. - * Upload to unstable. - * Disabled automatic database upgrades: post(inst|rm) scripts no longer - touch database or do unexpected stuff (Closes: #779254). - See README.Debian for details. - * Updated installation paths: - + /usr/share/zoneminder --> /usr/share/zoneminder/www - + /usr/lib/cgi-bin --> /usr/lib/zoneminder/cgi-bin - * Added logrotate config (Closes: #544826). - Thanks, Alberto Reyes. - * Native systemd service; "--with systemd" added to dh. - * Build with CMake instead of autoconf; rules clean-up. - * Build with all hardening. - * Build and install "zmupdate.pl.1" man page. - * Added nginx/php5-fpm configuration example. - * Install upstream "apache.conf" example. - * Described setup of Zoneminer web site and database in README.Debian. - * Install "/etc/zm/zm.conf" with tighter permissions. - * Added TODO.Debian. - * Added "debian/clean"; "debian/gbp.conf"; bug-presubj. - * Remove bundled Cake tests to take ~5 MB off big-usr-share. - * Standards-Version: 3.9.6; compat/debhelper to version 9. - * Vcs links to new git repository at collab-maint. - * Build-Depends: - + dh-systemd - + libgcrypt11-dev --> libgcrypt-dev - + libcurl4-gnutls-dev - + libvlc-dev - + policykit-1 (required by "zmsystemctl.pl") - - dh-autoreconf, autoconf, automake - * Depends: - - apache2 - - libapache2-mod-php5 (moved to Suggests) - - libpcre3 (invalid) - - libmodule-load-perl (obsolete; replaced with perl-modules) - - libarchive-tar-perl (obsolete; replaced with perl-modules) - - mysql-server (moved to Recommends, Closes: #759504). - - php5 - + libav-tools - + libjs-jquery (replaces bundled component) - + libjs-mootool (replaces bundled component) - + libjson-any-perl (Closes: #690803). - + perl-modules (Closes: #745819). - * Recommends: - + apache2 | httpd - + mysql-server | virtual-mysql-server (Closes: #732874). - * Suggests: - + libapache2-mod-php5 | php5-fpm - + logrotate - * Refreshed, renamed and re-ordered patches; added DEP-3 headers. - * Removed "vendor_perl" patch (applied-upstream). - * New patches: - + cmake-fix-confpath.patch - + cmake-gnutls.patch - + cmake-nossl.patch - + cmake.patch - + format-hardening.patch - + pod_man_fixes.patch - + pod_name_fixes.patch - + pod_zmupdate-to-pod2usage.patch - * Lintianisation (incomplete): - - extra-license-file - - init.d-script-missing-lsb-description - - init.d-script-does-not-source-init-functions - - privacy-breach-generic - - package-contains-empty-directory - - manpage-has-errors-from-pod2man - - manpage-has-bad-whatis-entry - - quilt-patch-missing-description - - no-dep5-copyright - * Lintian-overrides: - + unusual-interpreter usr/bin/zmsystemctl.pl #!/usr/bin/pkexec - + script-not-executable usr/share/zoneminder/www/api/* - + script-with-language-extension usr/bin/*.pl - + source-is-missing web/tools/mootools/mootools-*-yc.js - + source-is-missing web/skins/*/js/jquery-1.4.2.min.js - + source-contains-prebuilt-javascript-object - * Renamed files in "debian". - * watch: dfsg repacksuffix and dversionmangle. - * "debian/copyright" to Copyright-Format-1.0. - * Set myself as new Maintainer (Closes: #760314). - - [ Vagrant Cascadian ] - * Removed obsolete DM-Upload-Allowed flag. - * Update debian/watch to use tarballs from github. - * Add Build-Depends on libgcrypt11-dev (Closes: #745819). - * Use canonical alioth Vcs-Hg URL. - * debian/control: Add Build-Depends: libpolkit-gobject-1-dev. - * Removed configure flag "--enable-crashtrace=no", which is no longer - present upstream. - - -- Dmitry Smirnov Tue, 31 Mar 2015 15:11:13 +1100 - -zoneminder (1.26.5-3.1) experimental; urgency=low - - * Non-maintainer upload. - * Add libav10.patch and compile against libav10 (Closes: #739461) - - -- Reinhard Tartler Wed, 19 Mar 2014 00:31:22 +0000 - -zoneminder (1.26.5-3) unstable; urgency=low - - - * Previous release still didn't build on PPC - this has been corrected. - (Closes: #736516) - - -- Peter Howard Tue, 4 Feb 2014 02:02:10 +1000 - -zoneminder (1.26.5-2) unstable; urgency=low - - * Remove dependency on ffmpeg - (Closes: #721161) - - * Builds again on non-x86 target architectures. - - -- Peter Howard Thu, 23 Jan 2014 01:02:10 +1000 - -zoneminder (1.26.5-1) unstable; urgency=low - - * New upstream version - (Closes: #694131) - * Change Build-Depends on libgnutls-dev to libgnutls-openssl-dev - (Closes: #731560) - -- Peter Howard Tue, 17 Dec 2013 01:02:10 +1000 - -zoneminder (1.25.0-4) unstable; urgency=high - - * Add CVE-2013-0232 patch - [SECURITY] CVE-2013-0232: Shell escape commands with untrusted content. - Thanks to James McCoy (Closes: #698910) - Thanks also to Salvatore Bonaccorso - - -- Peter Howard Tue, 12 Jun 2013 12:02:10 +1000 - -zoneminder (1.25.0-3) unstable; urgency=low - - * debian/rules: Export CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS, to ensure - hardening build flags are enabled. - - -- Vagrant Cascadian Tue, 28 Aug 2012 12:10:03 -0700 - -zoneminder (1.25.0-2) unstable; urgency=low - - [ Vagrant Cascadian ] - * Add a patch to disable checking for updated versions by default, as - upgrades should happen through package management. - * Use dpkg-buildflags in debian/rules to set default compiler flags. - * Ensure zoneminder is stopped before starting (Closes: #657407). - - [ Peter Howard ] - * Fix postinst to add permission for table creation during upgrade - (Closes: #657407). - - -- Vagrant Cascadian Thu, 23 Aug 2012 12:40:34 -0700 - -zoneminder (1.25.0-1.1) unstable; urgency=low - - * Non-maintainer upload. - * Fix "ftbfs with GCC-4.7": add patch Fix-FTBFS-with-gcc-4.7 from Cyril - Brulebois: fix missing includes. - (Closes: #667428) - - -- gregor herrmann Sun, 13 May 2012 17:02:21 +0200 - -zoneminder (1.25.0-1) unstable; urgency=low - - * Fix typo in libv4l1-videodev.h patch that caused v4l1 support to be - dropped. - * Fail to build if version in postinst doesn't match upstream version. - * Add Build-Depends: libavdevice-dev to fix MPEG streaming (Closes: #515558). - * debian/rules: Convert to using debhelper overrides. - * Set debian/compat to 7. - * Simplify debian/watch file. - * Refresh debian/patches/use_libjs-mootools. - * Refresh debian/patches/libv4l1-videodev.h. - * Remove dependencies on php4 and related packages. - * Remove build-dependencies on libmysqlclient14-dev and - libmysqlclient15-dev. - * Update Build-Depends to use libjpeg-dev instead of libjpeg62-dev - (Closes: #647114). - * Add patch to fix build by testing for C headers rather than C++ headers. - Thanks to Ryan Niebur. (Closes: #654230) - * Add a patch to fix build problems caused by API changes in libav 0.8. - Thanks again to Ryan Niebur. (Closes: #654230) - - -- Vagrant Cascadian Mon, 16 Jan 2012 11:58:05 -0800 - -zoneminder (1.24.4-1) unstable; urgency=low - - [ Peter Howard ] - * Initial release of 1.24.4 (Closes: #634985). - - Fix 32/64-bit type declarations (Closes: #614404). - * Update patches. - - [ Vagrant Cascadian ] - * Add patch to fix FTBFS by using libv4l1-videodev.h from libv4l-dev. - Thanks to Andreas Metzler for reporting the issue. - (Closes: #619813). - * Document adding the www-data user to the video group in README.Debian. - (Closes: #611324) - * Depend on libsys-mmap-perl to enable mapped memory support. - (Closes: #607331) - * Update libjs-mootools patch to use -nc variants (Closes: #635075). - * Depend on javascript-common, to ensure that /javascript is available in - the web server. - * Set the upstream version in postinst at build time. - * Use dh-autoreconf to properly clean up autogenerated files during build. - * Add Vcs-HG to debian/control. - * Add Build-Depends: libv4l-dev, libbz2-dev, dh-autoreconf, libsys-mmap-perl. - - -- Vagrant Cascadian Sun, 24 Jul 2011 16:44:30 +0200 - -zoneminder (1.24.2-9) unstable; urgency=low - - * Apply patch from Ubuntu to fix FTBFS with ffmpeg 0.6: - - Add -D__STDC_CONSTANT_MACROS to CPPFLAGS (closes: 614080). - * Update Standards-Version to 3.9.1, no changes necessary. - - -- Vagrant Cascadian Sun, 20 Feb 2011 23:43:02 -0800 - -zoneminder (1.24.2-8) unstable; urgency=medium - - [ Vagrant Cascadian ] - * Apply patch to fix V4L2 cameras without crop support (closes: #608790). - Thanks to piratebab. - * Add preinst script which aborts if dangerous symlinks exist. - (closes: #608793) - - [ Peter Howard ] - * Added to README.Debian with info about images and events directories. - (closes: #608793) - - -- Vagrant Cascadian Sat, 15 Jan 2011 19:39:26 -0800 - -zoneminder (1.24.2-7) unstable; urgency=medium - - * Do not set ownership of /var/cache/zoneminder when upgrading, which fixes a - regression causing upgrades to take inordinately long with large - installations (closes: #597040). - - -- Vagrant Cascadian Fri, 17 Sep 2010 11:24:41 -0700 - -zoneminder (1.24.2-6) unstable; urgency=low - - * Only remove database on purge. This requires only creating the database if - it doesn't already exist, and upgrading the database only if the database - is an older version (closes: #497107). - - * Do not prompt the user on database upgrades by using the --nointeractive - flag when calling zmupdate.pl from postinst (closes: #595902). - - -- Vagrant Cascadian Fri, 10 Sep 2010 10:06:06 -0700 - -zoneminder (1.24.2-5) unstable; urgency=low - - [ Peter Howard ] - * Add zip dependency - (closes: #494261) - * Add debian/watch file - (closes: #545552) - * Use packaged libjs-mootools - (closes: #585590) - * Miscellaneous cleanups - - [ Vagrant Cascadian ] - * Add vagrant@debian.org as uploader - * Update Standards-Version to 3.9.0, no changes necessary. - - -- Vagrant Cascadian Fri, 23 Jul 2010 18:12:50 -0500 - -zoneminder (1.24.2-4.1) unstable; urgency=low - - * Non-maintainer upload. - * Fix "package removed, processes still running": apply patch to - debian/postinst by Vagrant Cascadian: use invoke-rc.d and run - mysql-related actions only when mysql is running (closes: #583648). - - -- gregor herrmann Thu, 01 Jul 2010 19:47:10 +0200 - -zoneminder (1.24.2-4) unstable; urgency=high - * Update init.d to list mysql dependency - (closes: #583505) - * Change depenency from libmime-perl to libmime-tools-perl - (closes: #585589) - * Problems in changelog format fixed - (closes: #585592) - * Fix debian-rules-ignores-make-clean-error - (closes: #585593) - -- Peter Howard Mon, 14 jun 2010 15:02:10 +1000 - -zoneminder (1.24.2-3) unstable; urgency=high - * Changes symbols to build with libjpeg8 - (closes: #565326, #568327) - * Note: location of all perl files should have been fixed in previous release - (closes: #553096) - -- Peter Howard Mon, 26 apr 2010 15:02:10 +1000 - -zoneminder (1.24.2-2) unstable; urgency=high - - * Remove custom perl parth from zmpkg.pl, fix location of manpages. - (closes: #551746, #553092) - * Fix GCC4.4 bug - (closes: #531717) - * Fix potential bug in postinst script - - -- Peter Howard Sat, 14 Nov 2009 15:02:10 +1000 - -zoneminder (1.24.2-1) unstable; urgency=high - - * Initial release of zoneminder 1.24.2 - -- Peter Howard Fri, 11 Sep 2009 07:02:50 +1000 - -zoneminder (1.24.1-1) unstable; urgency=high - - * Initial release of zoneminder 1.24.1, closing CVE-2008-3882, - CVE-2008-3881, CVE-2008-3880 - (closes: #497640) - * Change syslog dependency to rsyslog. - (closes: #526918) - * Add missing perl depenency. - * Restore patch to disable "check for updates" by default. - * Removed spurious '$' in init script. - (closes: #486064) - * Change permission of zm.conf from 0600 to 0400 for CVE-2008-6755 - (closes: #528252) - -- Peter Howard Sat, 16 May 2009 07:02:50 +1000 - -zoneminder (1.23.3-4) unstable; urgency=high - - * update to get it building with latest unstable. Thanks to waldi@debian.org - (closes: #517569) - -- Peter Howard Thu, 16 Apr 2009 01:02:50 +1000 - -zoneminder (1.23.3-3) unstable; urgency=high - - * ffmpeg confirmed working - (closes: #475145) - * Fix upgrade problem intrudouced in 1.23.3-1 - (closes: #481637) - * Include libmime-lite-perl in dependencies - (closes: #486312) - -- Peter Howard Thu, 18 Sep 2008 01:02:50 +1000 - -zoneminder (1.23.3-2) unstable; urgency=high - - * ffmpeg finally working? - - -- Peter Howard Wed, 13 Aug 2008 01:02:50 +1000 - -zoneminder (1.23.3-1) unstable; urgency=high - - * Initial version for 1.23.3 - security fix. - (closes: #479034) - - -- Peter Howard Wed, 19 Mar 2008 01:02:50 +1000 - -zoneminder (1.23.2-2) unstable; urgency=low - - * Update to init.d - (closes: #468856) - * Add dependency on logging daemon - (closes: #471277) - - -- Peter Howard Wed, 19 Mar 2008 01:02:50 +1000 - -zoneminder (1.23.2-1) unstable; urgency=low - - * Initial version for 1.23.2 - (closes: #464152) - * Zoneminder 1.23.2 upstream includes fix for GCC 4.3 - (closes: #454980) - * Includes ffmpeg patch by Alexander Kushnirenko - - -- Peter Howard Sat, 01 Mar 2008 16:02:50 +1000 - -zoneminder (1.22.3-10) unstable; urgency=low - - * Fix bug introduced in -9 where perl is put under /usr/local - (closes: #457507) - - -- Peter Howard Mon, 24 Dec 2007 16:02:50 +1000 - -zoneminder (1.22.3-9) unstable; urgency=low - - * Starting zoneminder via init script now invokes "zmfix -a" - (closes: #481637) - * Change apache2-mpm-prefork dependency to apache2 - * Temp dir for export under /var/cache/zoneminder (but linked back to - /usr/share/zoneminder for now) - * Redo use of gnutls rather than openssl for md5 hashes - - -- Peter Howard Mon, 10 Dec 2007 16:02:50 +1000 - -zoneminder (1.22.3-8) unstable; urgency=low - - * Build now includes libpcre3 - (closes: #437533) - * "Monitor Presets" patch now applied to package during build. - - -- Peter Howard Sat, 18 Aug 2007 14:35:23 +1000 - -zoneminder (1.22.3-7) unstable; urgency=low - - * Turn off debug trace and crash dump on build - (closes:#414857,#414891) - * Additional perl libraries added in dependencies - (closes:#416291) - * Change preferred PHP version from 4 to 5 - -- Peter Howard Sun, 29 Jul 2007 15:11:13 +1000 - -zoneminder (1.22.3-6) unstable; urgency=low - - * Removed a similar bash only statement from zmpkg.pl - (closes:414882) - - -- Peter Howard Sat, 14 Apr 2007 11:46:56 +1000 - -zoneminder (1.22.3-5) unstable; urgency=low - - * Installs with "phone home" feature turned off by default, and permissions - on /etc/zm/zm.conf fixed (now the 0600 it s hould be) - (closes:415349) - * Removed "stupid bash-ism" on mysqld check in postinst file. - - -- Peter Howard Fri, 6 Apr 2007 15:50:00 +1000 - -zoneminder (1.22.3-4) unstable; urgency=low - - * Put libmysqlclient-15-dev in front of -14-dev so sbuild works - (closes: #414410) - - -- Peter Howard Mon, 12 Mar 2007 11:38:56 +1100 - -zoneminder (1.22.3-3) unstable; urgency=low - - * Clean up of postinstall, postrm ; user "zm" definitely was a mistake - * Also in postinstall: check and start MySQL if it's not running. - * init.d script now checks if zoneminder isn't running and still returns 0 - (which helps uninstalling) - * Addition of php5 dependency options as well as php4. - - -- Peter Howard Mon, 26 Feb 2007 10:40:52 +1100 - -zoneminder (1.22.3-2) unstable; urgency=low - - * Added zmuser in the mysql creation; this should fix the install problem - for people, but needs to be cleaned up (in -3) - - -- Peter Howard Fri, 16 Feb 2007 14:16:03 +1100 - -zoneminder (1.22.3-1) unstable; urgency=low - - * Initial Version. (closes: #248393) - * Patched out use of openssl; uses gnutls instead for MD5 hashes. - * Removed MakeMaker-inserted Perl licensing (with authors permission) in - various scripts; replaced with GPL. - - -- Peter Howard Wed, 7 Feb 2007 14:09:01 +1100 diff --git a/distros/ubuntu1204/conf/apache2/zoneminder.conf b/distros/ubuntu1204/conf/apache2/zoneminder.conf index fa596d5ab..0dc312769 100644 --- a/distros/ubuntu1204/conf/apache2/zoneminder.conf +++ b/distros/ubuntu1204/conf/apache2/zoneminder.conf @@ -9,7 +9,7 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" Alias /zm /usr/share/zoneminder/www php_flag register_globals off - Options Indexes FollowSymLinks + Options -Indexes +FollowSymLinks DirectoryIndex index.php diff --git a/distros/ubuntu1204/control b/distros/ubuntu1204/control index d7c2232cf..cfd1c81a6 100644 --- a/distros/ubuntu1204/control +++ b/distros/ubuntu1204/control @@ -6,6 +6,7 @@ Uploaders: Vagrant Cascadian Build-Depends: debhelper (>= 9), python-sphinx | python3-sphinx, apache2-dev, dh-linktree ,cmake ,libavcodec-dev, libavformat-dev (>= 3:0.svn20090204), libswscale-dev (>= 3:0.svn20090204), libavutil-dev, libavdevice-dev + ,libboost1.55-dev ,libbz2-dev ,libgcrypt-dev ,libcurl4-gnutls-dev @@ -54,7 +55,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libdata-uuid-perl ,mysql-client | virtual-mysql-client ,perl-modules - ,php5-mysql, php5-gd + ,php5-mysql, php5-gd, php5-apcu, php-apc ,policykit-1 ,rsyslog | system-log-daemon ,zip diff --git a/distros/ubuntu1204/patches/default_cgi-path.patch b/distros/ubuntu1204/patches/default_cgi-path.patch deleted file mode 100644 index 8bfc2ba06..000000000 --- a/distros/ubuntu1204/patches/default_cgi-path.patch +++ /dev/null @@ -1,16 +0,0 @@ -Last-Update: 2015-08-16 -Forwarded: no -Author: Dmitry Smirnov -Description: correct path to CGI app according to default web server configuration. - ---- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in -+++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in -@@ -428,7 +428,7 @@ our @options = - }, - { - name => "ZM_PATH_ZMS", -- default => "/cgi-bin/nph-zms", -+ default => "/zm/cgi-bin/nph-zms", - description => "Web path to zms streaming server", - help => qqq(" - The ZoneMinder streaming server is required to send streamed diff --git a/distros/ubuntu1204/patches/series b/distros/ubuntu1204/patches/series index fc70f4006..e69de29bb 100644 --- a/distros/ubuntu1204/patches/series +++ b/distros/ubuntu1204/patches/series @@ -1,2 +0,0 @@ -default_cgi-path.patch -use_libjs-mootools.patch diff --git a/distros/ubuntu1204/patches/use_libjs-mootools.patch b/distros/ubuntu1204/patches/use_libjs-mootools.patch deleted file mode 100644 index b3925f6d0..000000000 --- a/distros/ubuntu1204/patches/use_libjs-mootools.patch +++ /dev/null @@ -1,18 +0,0 @@ -Last-Update: 2015-03-29 -Forwarded: no -Bug-Debian: http://bugs.debian.org/585590 -Reviewed-By: Dmitry Smirnov -Description: use mootools shipped by debian, rather than the zoneminder included mootools. - ---- a/web/skins/classic/includes/functions.php -+++ b/web/skins/classic/includes/functions.php -@@ -63,9 +63,8 @@ - } - ?> - - -- - - - >/dev/null 2>&1 || : endscript - weekly - rotate 3 + daily + rotate 7 } diff --git a/distros/ubuntu1204/zoneminder.postinst b/distros/ubuntu1204/zoneminder.postinst index 7c01cdde4..9d786e27d 100644 --- a/distros/ubuntu1204/zoneminder.postinst +++ b/distros/ubuntu1204/zoneminder.postinst @@ -38,6 +38,7 @@ if [ "$1" = "configure" ]; then invoke-rc.d zoneminder stop || true zmupdate.pl --nointeractive zmupdate.pl --nointeractive -f + echo "Done Updating, starting ZoneMinder" invoke-rc.d zoneminder start || true else echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.' diff --git a/distros/ubuntu1204/zoneminder.tmpfile b/distros/ubuntu1204/zoneminder.tmpfile index d307c6640..017955900 100644 --- a/distros/ubuntu1204/zoneminder.tmpfile +++ b/distros/ubuntu1204/zoneminder.tmpfile @@ -1,2 +1,3 @@ d /var/run/zm 0755 www-data www-data d /tmp/zm 0755 www-data www-data +d /var/tmp/zm 0755 www-data www-data diff --git a/distros/ubuntu1504_cmake_split_packages/apache.conf b/distros/ubuntu1504_cmake_split_packages/apache.conf index 6f2f30fd9..59efc6248 100644 --- a/distros/ubuntu1504_cmake_split_packages/apache.conf +++ b/distros/ubuntu1504_cmake_split_packages/apache.conf @@ -1,8 +1,19 @@ -Alias /zm /usr/share/zoneminder/www +# Remember to enable cgi mod (i.e. "a2enmod cgi"). +ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" + + Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch + AllowOverride All + Require all granted + +Alias /zm /usr/share/zoneminder/www - Options Indexes FollowSymLinks + Options -Indexes +FollowSymLinks DirectoryIndex index.php + + + AllowOverride All + diff --git a/distros/ubuntu1504_cmake_split_packages/rules b/distros/ubuntu1504_cmake_split_packages/rules index a534e8089..8a2c2643e 100755 --- a/distros/ubuntu1504_cmake_split_packages/rules +++ b/distros/ubuntu1504_cmake_split_packages/rules @@ -62,7 +62,9 @@ override_dh_auto_configure: -DZM_CGIDIR=/usr/lib/cgi-bin \ -DZM_WEB_USER=www-data \ -DZM_WEB_GROUP=www-data \ - -DCMAKE_INSTALL_SYSCONFDIR=etc/zm + -DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \ + -DZM_CONFIG_DIR="/etc/zm" + override_dh_auto_test: # do not run tests... diff --git a/distros/ubuntu1604/changelog b/distros/ubuntu1604/changelog index beef67111..2f42d6d18 100644 --- a/distros/ubuntu1604/changelog +++ b/distros/ubuntu1604/changelog @@ -429,7 +429,7 @@ zoneminder (1.24.2-4.1) unstable; urgency=low zoneminder (1.24.2-4) unstable; urgency=high * Update init.d to list mysql dependency (closes: #583505) - * Change depenency from libmime-perl to libmime-tools-perl + * Change dependency from libmime-perl to libmime-tools-perl (closes: #585589) * Problems in changelog format fixed (closes: #585592) @@ -466,7 +466,7 @@ zoneminder (1.24.1-1) unstable; urgency=high (closes: #497640) * Change syslog dependency to rsyslog. (closes: #526918) - * Add missing perl depenency. + * Add missing perl dependency. * Restore patch to disable "check for updates" by default. * Removed spurious '$' in init script. (closes: #486064) diff --git a/distros/ubuntu1604/conf/apache2/zoneminder.conf b/distros/ubuntu1604/conf/apache2/zoneminder.conf index 40fbf4d7c..59efc6248 100644 --- a/distros/ubuntu1604/conf/apache2/zoneminder.conf +++ b/distros/ubuntu1604/conf/apache2/zoneminder.conf @@ -8,7 +8,7 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" Alias /zm /usr/share/zoneminder/www - Options Indexes FollowSymLinks + Options -Indexes +FollowSymLinks DirectoryIndex index.php diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control index c1ee3cc0d..1adea5494 100644 --- a/distros/ubuntu1604/control +++ b/distros/ubuntu1604/control @@ -5,6 +5,8 @@ Maintainer: Dmitry Smirnov Uploaders: Vagrant Cascadian Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apache2-dev, dh-linktree ,cmake + ,libx264-dev, libmp4v2-dev + ,libboost-dev ,libavdevice-dev (>= 6:10~) ,libavcodec-dev (>= 6:10~) ,libavformat-dev (>= 6:10~) @@ -14,7 +16,7 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa ,libgcrypt-dev ,libcurl4-gnutls-dev ,libgnutls-openssl-dev - ,libjpeg-dev + ,libjpeg8-dev | libjpeg9-dev | libjpeg62-turbo-dev ,libmysqlclient-dev ,libpcre3-dev ,libpolkit-gobject-1-dev @@ -25,11 +27,11 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa ,libphp-serialization-perl ,libsys-mmap-perl [!hurd-any] ,libwww-perl - ,libdata-uuid-perl + ,libdata-uuid-perl # Unbundled (dh_linktree): ,libjs-jquery ,libjs-mootools -Standards-Version: 3.9.6 +Standards-Version: 3.9.8 Homepage: http://www.zoneminder.com/ Vcs-Browser: http://anonscm.debian.org/cgit/collab-maint/zoneminder.git Vcs-Git: git://anonscm.debian.org/collab-maint/zoneminder.git @@ -38,11 +40,10 @@ Package: zoneminder Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,javascript-common - ,libav-tools - ,libdate-manip-perl + ,libmp4v2-2, libx264-142|libx264-148, libswscale-ffmpeg3|libswscale4|libswscale3 + ,ffmpeg | libav-tools + ,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl ,libdbd-mysql-perl - ,libmime-lite-perl - ,libmime-tools-perl ,libphp-serialization-perl ,libmodule-load-conditional-perl ,libnet-sftp-foreign-perl @@ -60,13 +61,14 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libio-socket-multicast-perl ,libdigest-sha-perl ,libsys-cpu-perl, libsys-meminfo-perl - ,libdata-uuid-perl + ,libdata-uuid-perl ,mysql-client | virtual-mysql-client ,perl-modules - ,php5-mysql | php-mysql, php5-gd | php-gd + ,php5-mysql | php-mysql, php5-gd | php-gd , php5-apcu | php-apcu , php-apc | php-apcu-bc ,policykit-1 ,rsyslog | system-log-daemon ,zip + ,libpcre3 Recommends: ${misc:Recommends} ,libapache2-mod-php5 | libapache2-mod-php | php5-fpm | php-fpm ,mysql-server | virtual-mysql-server diff --git a/distros/ubuntu1604/patches/series b/distros/ubuntu1604/patches/series index fc70f4006..e69de29bb 100644 --- a/distros/ubuntu1604/patches/series +++ b/distros/ubuntu1604/patches/series @@ -1,2 +0,0 @@ -default_cgi-path.patch -use_libjs-mootools.patch diff --git a/distros/ubuntu1604/rules b/distros/ubuntu1604/rules index bf8012aa8..c91790d68 100755 --- a/distros/ubuntu1604/rules +++ b/distros/ubuntu1604/rules @@ -20,6 +20,7 @@ override_dh_auto_configure: -DCMAKE_VERBOSE_MAKEFILE=ON \ -DCMAKE_BUILD_TYPE=Release \ -DZM_CONFIG_DIR="/etc/zm" \ + -DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \ -DZM_RUNDIR="/var/run/zm" \ -DZM_SOCKDIR="/var/run/zm" \ -DZM_TMPDIR="/tmp/zm" \ @@ -28,7 +29,7 @@ override_dh_auto_configure: override_dh_clean: dh_clean $(MANPAGES1) - $(RM) -r docs/_build docs/installationguide + $(RM) -r docs/_build build-indep: #$(MAKE) -C docs text @@ -58,8 +59,10 @@ override_dh_auto_install: override_dh_fixperms: dh_fixperms - ## 637685 - chmod -c o-r $(CURDIR)/debian/zoneminder/etc/zm/zm.conf + # + # As requested by the Debian Webapps Policy Manual §3.2.1 + chown root:www-data $(CURDIR)/debian/zoneminder/etc/zm/zm.conf + chmod 640 $(CURDIR)/debian/zoneminder/etc/zm/zm.conf override_dh_installinit: dh_installinit --no-start diff --git a/distros/ubuntu1604/source/format b/distros/ubuntu1604/source/format index 89ae9db8f..163aaf8d8 100644 --- a/distros/ubuntu1604/source/format +++ b/distros/ubuntu1604/source/format @@ -1 +1 @@ -3.0 (native) +3.0 (quilt) diff --git a/distros/ubuntu1604/watch b/distros/ubuntu1604/watch deleted file mode 100644 index 7ee690edb..000000000 --- a/distros/ubuntu1604/watch +++ /dev/null @@ -1,7 +0,0 @@ -version=3 - -opts=\ -repacksuffix=+dfsg,\ -dversionmangle=s{\+dfsg\d*}{},\ - https://github.com/ZoneMinder/ZoneMinder/releases \ - .*/ZoneMinder/archive/v(.*).tar.gz diff --git a/distros/ubuntu1604/zoneminder.links b/distros/ubuntu1604/zoneminder.links index 4299f392a..14a8aee91 100644 --- a/distros/ubuntu1604/zoneminder.links +++ b/distros/ubuntu1604/zoneminder.links @@ -1,3 +1,4 @@ /var/cache/zoneminder/events /usr/share/zoneminder/www/events /var/cache/zoneminder/images /usr/share/zoneminder/www/images /var/cache/zoneminder/temp /usr/share/zoneminder/www/temp +/var/tmp /usr/share/zoneminder/www/api/app/tmp diff --git a/distros/ubuntu1604/zoneminder.logrotate b/distros/ubuntu1604/zoneminder.logrotate index 18fc6b0a0..59238b7fe 100644 --- a/distros/ubuntu1604/zoneminder.logrotate +++ b/distros/ubuntu1604/zoneminder.logrotate @@ -2,6 +2,8 @@ missingok notifempty sharedscripts + delaycompress + compress postrotate /usr/bin/zmpkg.pl logrot >>/dev/null 2>&1 || : endscript diff --git a/distros/ubuntu1604/zoneminder.postinst b/distros/ubuntu1604/zoneminder.postinst index 64699d1ca..a8f93c4d0 100644 --- a/distros/ubuntu1604/zoneminder.postinst +++ b/distros/ubuntu1604/zoneminder.postinst @@ -12,6 +12,10 @@ if [ "$1" = "configure" ]; then if [ -z "$2" ]; then chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/* fi + if [ ! -e "/etc/apache2/mods-enabled/cgi.load" ]; then + echo "The cgi module is not enabled in apache2. I am enabling it using a2enmod cgi." + a2enmod cgi + fi # Do this every time the package is installed or upgraded @@ -41,6 +45,7 @@ if [ "$1" = "configure" ]; then deb-systemd-invoke stop zoneminder.service || exit $? zmupdate.pl --nointeractive zmupdate.pl --nointeractive -f + echo "Done Updating, starting ZoneMinder" deb-systemd-invoke start zoneminder.service || exit $? else diff --git a/distros/ubuntu1604/zoneminder.service b/distros/ubuntu1604/zoneminder.service index e3575c039..a4479c49c 100644 --- a/distros/ubuntu1604/zoneminder.service +++ b/distros/ubuntu1604/zoneminder.service @@ -15,6 +15,7 @@ ExecReload=/usr/bin/zmpkg.pl restart ExecStop=/usr/bin/zmpkg.pl stop PIDFile=/var/run/zm/zm.pid Restart=on-abnormal +Environment=TZ=:/etc/localtime [Install] WantedBy=multi-user.target diff --git a/distros/ubuntu1604/zoneminder.tmpfile b/distros/ubuntu1604/zoneminder.tmpfile index d307c6640..ef68288ba 100644 --- a/distros/ubuntu1604/zoneminder.tmpfile +++ b/distros/ubuntu1604/zoneminder.tmpfile @@ -1,2 +1,3 @@ -d /var/run/zm 0755 www-data www-data -d /tmp/zm 0755 www-data www-data +d /var/run/zm 0755 www-data www-data +d /tmp/zm 0755 www-data www-data +d /var/tmp/zm 0755 www-data www-data diff --git a/docs/api.rst b/docs/api.rst index 9546a279a..36467525a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -89,7 +89,7 @@ This API changes monitor 1 to Modect and Enabled :: - curl -XPOST http://server/zm/api/monitors/1.json -d "Monitor[Function]=Modect&Monitor[Enabled]:true" + curl -XPOST http://server/zm/api/monitors/1.json -d "Monitor[Function]=Modect&Monitor[Enabled]=1" Add a monitor ^^^^^^^^^^^^^^ @@ -209,7 +209,7 @@ Return a list of events for a specific monitor Id =5 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: - curl -XGET http://server/zm/api/events/events/index/MonitorId:5.json`` + curl -XGET http://server/zm/api/events/index/MonitorId:5.json Note that the same pagination logic applies if the list is too long @@ -220,7 +220,7 @@ Return a list of events for a specific monitor within a specific date/time range :: - http://server/zm/api/events/events/index/MonitorId:5/StartTime >=:2015-05-15 18:43:56/EndTime <=:2015-05-16 18:43:56.json + http://server/zm/api/events/index/MonitorId:5/StartTime >=:2015-05-15 18:43:56/EndTime <=:2015-05-16 18:43:56.json To try this in CuRL, you need to URL escape the spaces like so: @@ -272,7 +272,17 @@ This returns the full list of configuration parameters: Each configuration parameter has an Id, Name, Value and other fields. Chances are you are likely only going to focus on these 3. -(Example of changing config TBD) +The edit function of the Configs API is a little quirky at the moment. Its format deviates from the usual edit flow of other APIs. This will be fixed, eventually. For now, to change the "Value" of ZM_X10_HOUSE_CODE from A to B: + +:: + + curl -XPUT http://server/zm/api/configs/edit/ZM_X10_HOUSE_CODE.json -d "Config[Value]=B" + +To validate changes have been made: + +:: + + curl -XGET http://server/zm/api/configs/view/ZM_X10_HOUSE_CODE.json Run State Apis ^^^^^^^^^^^^^^^ @@ -340,5 +350,5 @@ ZM APIs have various APIs that help you in determining host (aka ZM) daemon stat curl -XGET http://server/zm/api/host/daemonCheck.json # 1 = ZM running 0=not running curl -XGET http://server/zm/api/host/getLoad.json # returns current load of ZM - curl -XGET http://server/zm/api/host/getDiskPercent.json # returns in GB (not percentage), disk usage per monitor (that is, space taken to store various event related information,images etc. per monitor) `` + curl -XGET http://server/zm/api/host/getDiskPercent.json # returns in GB (not percentage), disk usage per monitor (that is, space taken to store various event related information,images etc. per monitor) diff --git a/docs/faq.rst b/docs/faq.rst index 1c5c36d09..9228274d7 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -8,7 +8,7 @@ How can I stop ZoneMinder filling up my disk? --------------------------------------------- Recent versions of ZoneMinder come with a filter you can use for this purpose already included. -The filter is called **PurgeWhenFull** and to find it, choose one of the event counts from the console page, for instance events in the last hour, for one of your monitors. **Note** that this filter is automatically enabled if you do a frresh install of ZoneMinder including creating a new Database. If you already have an existing Database and are upgrading Zoneminder, it will retain the settings of the filter (which in earlier releases was disabled by default). So you may want to check if PurgeWhenFull is enabled and if not, enable it. +The filter is called **PurgeWhenFull** and to find it, choose one of the event counts from the console page, for instance events in the last hour, for one of your monitors. **Note** that this filter is automatically enabled if you do a fresh install of ZoneMinder including creating a new database. If you already have an existing database and are upgrading ZoneMinder, it will retain the settings of the filter (which in earlier releases was disabled by default). So you may want to check if PurgeWhenFull is enabled and if not, enable it. To enable it, go to Web Console, click on any of your Events of any of your monitors. This will bring up an event listing and a filter window. @@ -36,7 +36,7 @@ There are two methods for ZM to remove files when they are deleted that can be f ZM_OPT_FAST_DELETE: -Normally an event created as the result of an alarm consists of entries in one or more database tables plus the various files associated with it. When deleting events in the browser it can take a long time to remove all of this if your are trying to do a lot of events at once. It is recommended that you set this option which means that the browser client only deletes the key entries in the events table, which means the events will no longer appear in the listing, and leaves the zmaudit daemon to clear up the rest later. +Normally an event created as the result of an alarm consists of entries in one or more database tables plus the various files associated with it. When deleting events in the browser it can take a long time to remove all of this if you are trying to do a lot of events at once. If you are running on an older or under-powered system, you may want to set this option which means that the browser client only deletes the key entries in the events table, which means the events will no longer appear in the listing, and leaves the zmaudit daemon to clear up the rest later. If you do so, disk space will not be freed immediately so you will need to run zmaudit more frequently. On modern systems, we recommend that you leave this off. @@ -96,22 +96,16 @@ A sample output on Ubuntu: :: - pp@camerapc:~$ df -h + pp@camerapc:~$ df -h|grep "Filesystem\|shm" Filesystem Size Used Avail Use% Mounted on - /dev/sda1 226G 96G 119G 45% / - none 4.0K 0 4.0K 0% /sys/fs/cgroup - udev 1.8G 4.0K 1.8G 1% /dev - tmpfs 371M 816K 370M 1% /run - none 5.0M 0 5.0M 0% /run/lock tmpfs 2.6G 923M 1.7G 36% /run/shm - none 100M 0 100M 0% /run/user -The key item here is tmpfs --> the example above shows we have allocated 1.7G of mapped memory space of which 36% is used which is a healthy number. If you are seeing this to go beyond 70% you should probaby increase mapped memory +The key item here is tmpfs --> the example above shows we have allocated 1.7G of mapped memory space of which 36% is used which is a healthy number. If you are seeing ``Use%`` going beyond 70% you should probaby increase the mapped memory. - -If you want to increase this limit to 70% of your memory, add the following to ``/etc/fstab`` -``tmpfs /run/shm tmpfs defaults,noexec,nosuid,size=70% 0 0`` +For example, if you want to increase this limit to 70% of your memory, add the following to ``/etc/fstab`` +``tmpfs SHMPATH tmpfs defaults,noexec,nosuid,size=70% 0 0`` +where SHMPATH is the ``Mounted on`` path. Here, that would be ``/run/shm``. Other systems may be ``/dev/shm``. What does a 'Can't shmget: Invalid argument' error in my logs mean? (and my camera does not display at higher resolutions) @@ -263,7 +257,7 @@ Why can't I see streamed images when I can see stills in the Zone window etc? This issue is normally down to one of two causes -1) You are using Internet Explorer and are trying to view multi-part jpeg streams. IE does not support these streams directly, unlike most other browsers. You will need to install Cambozola or another multi-part jpeg aware pluging to view them. To do this you will need to obtain the applet from the Downloads page and install the cambozola.jar file in the same directly as the ZoneMinder php files. Then find the ZoneMinder Options->Images page and enable ZM_OPT_CAMBOZOLA and enter the web path to the .jar file in ZM_PATH_CAMBOZOLA. This will ordinarily just be cambozola.jar. Provided (Options / B/W tabs) WEB_H_CAN_STREAM is set to auto and WEB_H_STREAM_METHOD is set to jpeg then Cambozola should be loaded next time you try and view a stream. +1) You are using Internet Explorer and are trying to view multi-part jpeg streams. IE does not support these streams directly, unlike most other browsers. You will need to install Cambozola or another multi-part jpeg aware plugin to view them. To do this you will need to obtain the applet from the Downloads page and install the cambozola.jar file in the same directory as the ZoneMinder php files. Then find the ZoneMinder Options->Images page and enable ZM_OPT_CAMBOZOLA and enter the web path to the .jar file in ZM_PATH_CAMBOZOLA. This will ordinarily just be cambozola.jar. Provided (Options / B/W tabs) WEB_H_CAN_STREAM is set to auto and WEB_H_STREAM_METHOD is set to jpeg then Cambozola should be loaded next time you try and view a stream. '''NOTE''': If you find that the Cambozola applet loads in IE but the applet just displays the version # of Cambozola and the author's name (as opposed to seeing the streaming images), you may need to chmod (''-rwxrwxr-x'') your (''usr/share/zoneminder/'') cambozola.jar: @@ -275,6 +269,10 @@ Once I did this, images started to stream for me. 2) The other common cause for being unable to view streams is that you have installed the ZoneMinder cgi binaries (zms and nph-zms) in a different directory than your web server is expecting. Make sure that the --with-cgidir option you use to the ZoneMinder configure script is the same as the CGI directory configure for your web server. If you are using Apache, which is the most common one, then in your httpd.conf file there should be a line like ``ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"`` where the last directory in the quotes is the one you have specified. If not then change one or the other to match. Be warned that configuring apache can be complex so changing the one passed to the ZoneMinder configure (and then rebuilding and reinstalling) is recommended in the first instance. If you change the apache config you will need to restart apache for the changes to take effect. If you still cannot see stream reliably then try changing Options->Paths->ZM_PATH_ZMS to just use zms if nph-zms is specified, or vice versa. Also check in your apache error logs. +Also, please check the value of the ZM_PATH_ZMS setting under the Paths Options tab. It is where you configure the URL to the zms or nph-zms CGI executable. Under most Debian-based distros this value should be /zm/cgi-bin/nph-zms but in the past may have been /cgi-bin/nph-zms or you may have configured it to be something else. + +Lastly, please look for errors created by the zmc processes. If zmc isn't running, then zms will not be able to get an image from it and will exit. + I have several monitors configured but when I load the Montage view in FireFox why can I only see two? or, Why don't all my cameras display when I use the Montage view in FireFox? -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -311,7 +309,7 @@ The main causes are. * Capture frame rates. Unless there's a compelling reason in your case there is often little benefit in running cameras at 25fps when 5-10fps would often get you results just as good. Try changing your monitor settings to limit your cameras to lower frame rates. You can still configure ZM to ignore these limits and capture as fast as possible when motion is detected. * Run function. Obviously running in Record or Mocord modes or in Modect with lots of events generates a lot of DB and file activity and so CPU and load will increase. * Basic default detection zones. By default when a camera is added one detection zone is added which covers the whole image with a default set of parameters. If your camera covers a view in which various regions are unlikely to generate a valid alarm (ie the sky) then I would experiment with reducing the zone sizes or adding inactive zones to blank out areas you don't want to monitor. Additionally the actual settings of the zone themselves may not be optimal. When doing motion detection the number of changed pixels above a threshold is examined, then this is filter, then contiguous regions are calculated to see if an alarm is generated. If any maximum or minimum threshold is exceeded according to your zone settings at any time the calculation stops. If your settings always result in the calculations going through to the last stage before being failed then additional CPU time is used unnecessarily. Make sure your maximum and minimumzone thresholds are set to sensible values and experiment by switching RECORD_EVENT_STATS on and seeing what the actual values of alarmed pixels etc are during sample events. - * Optimise your settings. After you've got some settings you're happy with then switching off RECORD_EVENT_STATS will prevent the statistics being written to the database which saves some time. Other settings which might make a difference are ZM_FAST_RGB_DIFFS, ZM_OPT_FRAME_SERVER and the JPEG_xxx_QUALITY ones. + * Optimise your settings. After you've got some settings you're happy with then switching off RECORD_EVENT_STATS will prevent the statistics being written to the database which saves some time. Other settings which might make a difference are ZM_FAST_RGB_DIFFS and the JPEG_xxx_QUALITY ones. I'm sure there are other things which might make a difference such as what else you have running on the box and memory sizes (make sure there's no swapping going on). Also speed of disk etc will make some difference during event capture and also if you are watching the whole time then you may have a bunch of zms processes running also. @@ -698,7 +696,7 @@ Now go to http://zoneminder_IP/ and stop the ZM service. Continue to http://zon I upgraded by distribution and ZM stopped working ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Some possibilties (Incomplete list and subject to correction) +Some possibilities (Incomplete list and subject to correction) ``[[/usr/local/bin/zmfix: /usr/lib/libmysqlclient.so.15: version `MYSQL_5.0' not found (required by /usr/local/bin/zmfix)]]`` :: Solution: Recompile and reinstall Zoneminder. Any time you update a major version that ZoneMinder depends on, you need to recompile ZoneMinder. diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index 9294ebeb6..0b926435f 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -1,37 +1,154 @@ Debian ====== -A fresh build based on master branch running Debian 7 (wheezy)\: +.. contents:: + +Easy Way: Debian Jessie +----------------------- + +**Step 1:** Setup Sudo + +By default Debian does not come with sudo. Log in as root or use su command. +N.B. The instructions below are for setting up sudo for your current account, you can +do this as root if you prefer. + :: - root@host:~# aptitude install -y apache2 mysql-server php5 php5-mysql build-essential libmysqlclient-dev libssl-dev libbz2-dev libpcre3-dev libdbi-perl libarchive-zip-perl libdate-manip-perl libdevice-serialport-perl libmime-perl libpcre3 libwww-perl libdbd-mysql-perl libsys-mmap-perl yasm automake autoconf libjpeg8-dev libjpeg8 apache2-mpm-prefork libapache2-mod-php5 php5-cli libphp-serialization-perl libgnutls-dev libjpeg8-dev libavcodec-dev libavformat-dev libswscale-dev libavutil-dev libv4l-dev libtool ffmpeg libnetpbm10-dev libavdevice-dev libmime-lite-perl dh-autoreconf dpatch; + apt-get update + apt-get install sudo + usermod -a -G sudo + exit - root@host:~# git clone https://github.com/ZoneMinder/ZoneMinder.git zoneminder; - root@host:~# cd zoneminder; - root@host:~# ln -s distros/debian; - root@host:~# dpkg-checkbuilddeps; - root@host:~# dpkg-buildpackage; +Logout or try ``newgrp`` to reload user groups -One level above you'll now find a deb package matching the architecture of the build host: +**Step 2:** Run sudo and update + +Now run session using sudo and ensure system is updated. :: - root@host:~# ls -1 ~/zoneminder*; - /root/zoneminder_1.26.4-1_amd64.changes - /root/zoneminder_1.26.4-1_amd64.deb - /root/zoneminder_1.26.4-1.dsc - /root/zoneminder_1.26.4-1.tar.gz + sudo -i + apt-get upgrade -The dpkg command itself does not resolve dependencies. That's what high-level interfaces like aptitude and apt-get are normally for. Unfortunately, unlike RPM, there's no easy way to install a separate deb package not contained with any repository. +**Step 3:** Install Apache and MySQL + +These are not dependencies for the package as they could +be installed elsewhere. -To overcome this "limitation" we'll use dpkg only to install the zoneminder package and apt-get to fetch all needed dependencies afterwards. Running dpkg-reconfigure in the end will ensure that the setup scripts e.g. for database provisioning were executed. :: - root@host:~# dpkg -i /root/zoneminder_1.26.4-1_amd64.deb; apt-get install -f; - root@host:~# dpkg-reconfigure zoneminder; + apt-get install apache2 mysql-server + +**Step 4:** Edit sources.list to add jessie-backports -Alternatively you may also use gdebi to automatically resolve dependencies during installation: :: - root@host:~# aptitude install -y gdebi; - root@host:~# gdebi /root/zoneminder_1.26.4-1_amd64.deb; + nano /etc/apt/sources.list +Add the following to the bottom of the file + +:: + + # Backports repository + deb http://httpredir.debian.org/debian jessie-backports main contrib non-free + +CTRL+o and to save +CTRL+x to exit + +**Step 5:** Install ZoneMinder + +:: + + apt-get update + apt-get install zoneminder + +**Step 6:** Read the Readme + +The rest of the install process is covered in the README.Debian, so feel free to have +a read. + +:: + + gunzip /usr/share/doc/zoneminder/README.Debian.gz + cat /usr/share/doc/zoneminder/README.Debian + +**Step 7:** Setup Database + +Install the zm database and setup the user account. Refer to Hints in Ubuntu install +should you choose to change default database user and password. + +:: + + cat /usr/share/zoneminder/db/zm_create.sql | sudo mysql --defaults-file=/etc/mysql/debian.cnf + echo 'grant lock tables,alter,create,select,insert,update,delete,index on zm.* to 'zmuser'@localhost identified by "zmpass";' | sudo mysql --defaults-file=/etc/mysql/debian.cnf mysql + +** Step 8:** zm.conf Permissions + +Adjust permissions to the zm.conf file to allow web account to access it. + +:: + + chgrp -c www-data /etc/zm/zm.conf + +**Step 9:** Setup ZoneMinder service + +:: + + systemctl enable zoneminder.service + +**Step 10:** Configure Apache + +The following commands will setup the default /zm virtual directory and configure +required apache modules. + +:: + + a2enconf zoneminder + a2enmod cgi + a2enmod rewrite + +**Step 11:** Edit Timezone in PHP + +:: + + nano /etc/php5/apache2/php.ini + +Search for [Date] (Ctrl + w then type Date and press Enter) and change +date.timezone for your time zone. **Don't forget to remove the ; from in front +of date.timezone** + +:: + + [Date] + ; Defines the default timezone used by the date functions + ; http://php.net/date.timezone + date.timezone = America/New_York + +CTRL+o then [Enter] to save + +CTRL+x to exit + +**Step 12:** Start ZoneMinder + +Reload Apache to enable your changes and then start ZoneMinder. + +:: + + systemctl reload apache2 + systemctl start zoneminder + +**Step 13:** Making sure ZoneMinder works + +1. Open up a browser and go to ``http://hostname_or_ip/zm`` - should bring up ZoneMinder Console + +2. (Optional API Check)Open up a tab in the same browser and go to ``http://hostname_or_ip/zm/api/host/getVersion.json`` + + If it is working correctly you should get version information similar to the example below: + + :: + + { + "version": "1.29.0", + "apiversion": "1.29.0.1" + } + +**Congratulations** Your installation is complete diff --git a/docs/installationguide/multiserver.rst b/docs/installationguide/multiserver.rst index e9ebefac2..14e52d29f 100644 --- a/docs/installationguide/multiserver.rst +++ b/docs/installationguide/multiserver.rst @@ -42,8 +42,6 @@ Note that these commands are just an example and might not be secure enough for 7. If you have chosen to change the ZoneMinder database account credentials to something other than zmuser/zmpass, you must now update zm.conf on each ZoneMinder Server. Change ZM_DB_USER and ZM_DB_PASS to the values you created in the previous step. -Additionally, you must also edit /usr/share/zoneminder/www/api/app/Config/database.php in a similar manner on each ZoneMinder Server. Scroll down and change login and password to the values you created in the previous step. - 8. All ZoneMinders Servers must share a common events folder. This can be done in any manner supported by the underlying operating system. From the Storage Server, share/export a folder to be used for ZoneMinder events. 9. From each ZoneMinder Server, mount the shared events folder on the Storage Server to the events folder on the local ZoneMinder Server. diff --git a/docs/installationguide/redhat.rst b/docs/installationguide/redhat.rst index e15d58aa7..d662d1812 100644 --- a/docs/installationguide/redhat.rst +++ b/docs/installationguide/redhat.rst @@ -32,7 +32,7 @@ Zmrepo supports the two most recent, major releases of each Redhat based distro. The following notes are based on real problems which have occurred: -- Zmrepo assumes you have installed the underlying distrubution **using the official installation media for that distro**. Third party "Spins" are not supported and may not work correctly. +- Zmrepo assumes you have installed the underlying distribution **using the official installation media for that distro**. Third party "Spins" are not supported and may not work correctly. - ZoneMinder is intended to be installed in an environment dedicated to ZoneMinder. While ZoneMinder will play well with many applications, some invariably will not. Asterisk is one such example. @@ -169,8 +169,6 @@ Now clone the ZoneMinder git repository: cd git clone https://github.com/ZoneMinder/ZoneMinder cd ZoneMinder - git submodule init - git submodule update This will create a sub-folder called ZoneMinder, which will contain the latest development. @@ -180,21 +178,21 @@ We want to turn this into a tarball, but first we need to figure out what to nam ls ~/rpmbuild/SOURCES -The tarball from the previsouly installed SRPM should be there. This is the name we will use. For this example, the name is ZoneMinder-1.28.1.tar.gz. From one folder above the local ZoneMinder git repository, execute the following: +The tarball from the previsouly installed SRPM should be there. This is the name we will use. For this example, the name is ZoneMinder-1.28.1.tar.gz. From the root folder of the local ZoneMinder git repository, execute the following: :: - mv ZoneMinder ZoneMinder-1.28.1 - tar -cvzf ~/rpmbuild/SOURCES/ZoneMinder-1.28.1.tar.gz ZoneMinder-1.28.1/* + git archive --prefix=ZoneMinder-1.28.1/ -o ~/rpmbuild/SOURCES/zoneminder-1.28.1.tar.gz HEAD -The trailing "/\*" leaves off the hidden dot "." file and folders from the git repo, which is what we want. Note that we are overwriting the original tarball. If you wish to keep the original tarball then create a copy prior to creating the new tarball. -Now build a new src.rpm: +From the root of the local ZoneMinder git repo, execute the following: :: - rpmbuild -bs --nodeps ~/rpmbuild/SPECS/zoneminder.el7.spec + rpmbuild -bs --nodeps distros/redhat/zoneminder.spec + +Notice we used the rpm specfile that is part of the latest master branch you just downloaded, rather than the one that may be in your ~/rpmbbuild/SOURCES folder. This step will overwrite the SRPM you originally downloaded, so you may want to back it up prior to completing this step. Note that the name of the specfile will vary slightly depending on the target distro. diff --git a/docs/installationguide/ubuntu.rst b/docs/installationguide/ubuntu.rst index 80cb27552..e561e7477 100644 --- a/docs/installationguide/ubuntu.rst +++ b/docs/installationguide/ubuntu.rst @@ -1,289 +1,269 @@ -Ubuntu Instruction -=================== +Ubuntu +====== .. contents:: -Easy Way: Install ZoneMinder from a package (Ubuntu 15.x+) ------------------------------------------------------------ -These instructions are for a brand new ubuntu 15.04 system which does not have ZM installed. +Easy Way: Ubuntu 16.04 +---------------------- +These instructions are for a brand new ubuntu 16.04 system which does not have ZM +installed. -**Step 1**: Make sure we add the correct packages + +It is recommended that you use an Ubuntu Server install and select the LAMP option +during install to install Apache, MySQL and PHP. If you failed to do this you can +achieve the same result by running: :: - sudo add-apt-repository ppa:iconnor/zoneminder - sudo apt-get update + tasksel install lamp-server -if you don't have mysql already installed: +During installation it will ask you to set up a master/root password for the MySQL. + +**Step 1:** Either run commands in this install using sudo or use the below to become root +:: + + sudo -i + +**Step 2:** Update Repos + +.. topic :: Latest Release + + ZoneMinder 1.29.0 is now part of the current standard Ubuntu repository. But + if you wish to install the later releases of ZoneMinder you will need + to add the iconnor/zoneminder PPA. + + :: + + add-apt-repository ppa:iconnor/zoneminder + +Update repo and upgrade. :: - sudo apt-get install mysql-server + apt-get update + apt-get upgrade + apt-get dist-upgrade -This will ask you to set up a master password for the DB (you are asked for the mysql root password when installing mysql server). +**Step 3:** Configure MySQL -**Step 2**: Install ZoneMinder +.. sidebar :: Note + + The MySQL default configuration file (/etc/mysql/mysql.cnf)is read through + several symbolic links beginning with /etc/mysql/my.cnf as follows: + + | /etc/mysql/my.cnf -> /etc/alternatives/my.cnf + | /etc/alternatives/my.cnf -> /etc/mysql/mysql.cnf + | /etc/mysql/mysql.cnf is a basic file + +Certain new defaults in MySQL 5.7 are currently causing some issues with ZoneMinder, +the workaround is to modify the sql_mode setting of MySQL. + +To better manage the MySQL server it is recommended to copy the sample config file and +replace the default my.cnf symbolic link. :: - sudo apt-get install zoneminder + rm /etc/mysql/my.cnf (this removes the current symbolic link) + cp /etc/mysql/mysql.conf.d/mysqld.cnf /etc/mysql/my.cnf -**Step 3**: Configure the Database +To change MySQL settings: :: - sudo mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql + nano /etc/mysql/my.cnf + +In the [mysqld] section add the following + +:: + + sql_mode = NO_ENGINE_SUBSTITUTION + +CTRL+o then [Enter] to save + +CTRL+x to exit + +Restart MySQL + +:: + + systemctl restart mysql + + +**Step 4:** Install ZoneMinder + +:: + + apt-get install zoneminder + +**Step 5:** Configure the ZoneMinder Database + +:: + + mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql mysql -uroot -p -e "grant select,insert,update,delete,create,alter,index,lock tables on zm.* to 'zmuser'@localhost identified by 'zmpass';" -You don't really need this, but no harm (needed if you are upgrading) + +**Step 6:** Set permissions + +Set /etc/zm/zm.conf to root:www-data 740 and www-data access to content :: - sudo /usr/bin/zmupdate.pl + chmod 740 /etc/zm/zm.conf + chown root:www-data /etc/zm/zm.conf + chown -R www-data:www-data /usr/share/zoneminder/ -**Step 4**: Configure systemd to recognize ZoneMinder and configure Apache correctly: +**Step 7:** Configure Apache correctly: :: - sudo systemctl enable zoneminder - sudo a2enconf zoneminder - sudo a2enmod cgi - sudo chown -R www-data:www-data /usr/share/zoneminder/ + a2enconf zoneminder + a2enmod cgi + a2enmod rewrite - -We need this for API routing to work: +**Step 8:** Enable and start Zoneminder :: - sudo a2enmod rewrite + systemctl enable zoneminder + systemctl start zoneminder -This is probably a bug with iconnor's PPA as of Oct 3, 2015 with package 1.28.107. After installing, ``zm.conf`` does not have the right read permissions, so we need to fix that. This may go away in future PPA releases: +**Step 9:** Edit Timezone in PHP :: - sudo chown www-data:www-data /etc/zm/zm.conf + nano /etc/php/7.0/apache2/php.ini -We also need to install php5-gd (as of 1.28.107, this is not installed) +Search for [Date] (Ctrl + w then type Date and press Enter) and change +date.timezone for your time zone, see [this](http://php.net/manual/en/timezones.php). +**Don't forget to remove the ; from in front of date.timezone** :: - sudo apt-get install php5-gd + [Date] + ; Defines the default timezone used by the date functions + ; http://php.net/date.timezone + date.timezone = America/New_York -**Step 5**: Edit Timezone in PHP +CTRL+o then [Enter] to save + +CTRL+x to exit + +**Step 10:** Reload Apache service :: - vi /etc/php5/apache2/php.ini + systemctl reload apache2 -Look for [Date] and inside it you will see a date.timezone -that is commented. remove the comment and specific your timezone. -Please make sure the timezone is valid (see this: http://php.net/manual/en/timezones.php) +**Step 11:** Making sure ZoneMinder works -In my case: +1. Open up a browser and go to ``http://hostname_or_ip/zm`` - should bring up ZoneMinder Console -:: +2. (Optional API Check)Open up a tab in the same browser and go to ``http://hostname_or_ip/zm/api/host/getVersion.json`` - date.timezone = America/New_York + If it is working correctly you should get version information similar to the example below: -**Step 6**: Restart services + :: -:: + { + "version": "1.29.0", + "apiversion": "1.29.0.1" + } - sudo service apache2 reload - sudo systemctl restart zoneminder +**Congratulations** Your installation is complete +PPA install may need some tweaking of ZMS_PATH in ZoneMinder options. `Socket_sendto or no live streaming`_ -**Step 7: make sure live streaming works**: Make sure you can view Monitor streams: - -startup ZM console in your browser, go to ``Options->Path`` and make sure ``PATH_ZMS`` is set to ``/zm/cgi-bin/nph-zms`` and restart ZM (you should not need to do this for packages, as this should automatically work) - - -**Step 8**: If you have changed your DB login/password from zmuser/zmpass, the API won't know about it - -If you changed the DB password **after** installing ZM, the APIs will not be able to connect to the DB. - -If you have, go to ``zoneminder/www/api/app/Config`` & Edit ``database.php`` - -There is a class there called ``DATABASE_CONFIG`` - change the ``$default`` array to reflect your new details. Example: - -:: - - public $default = array( - 'datasource' => 'Database/Mysql', - 'persistent' => false, - 'host' => 'localhost', - 'login' => 'mynewDBusername', - 'password' => 'mynewDBpassword' - 'database' => 'zm', - 'prefix' => '', - //'encoding' => 'utf8', - ); - - -You are done. Lets proceed to make sure everything works: - -Making sure ZM and APIs work: - -1. open up a browser and go to ``http://localhost/zm`` - should bring up ZM -2. (OPTIONAL - just for peace of mind) open up a tab and go to ``http://localhost/zm/api`` - should bring up a screen showing CakePHP version with some green color boxes. Green is good. If you see red, or you don't see green, there may be a problem (should not happen). Ignore any warnings in yellow saying "DebugKit" not installed. You don't need it -3. open up a tab in the same browser and go to ``http://localhost/zm/api/host/getVersion.json`` - -If it responds with something like: - -:: - - { - "version": "1.28.107", - "apiversion": "1.28.107.1" - } - - -**Then your APIs are working** - -Make sure ZM and APIs work with security: -1. Enable OPT_AUTH in ZM -2. Log out of ZM in browser -3. Open a NEW tab in the SAME BROWSER (important) and go to ``http://localhost/zm/api/host/getVersion.json`` - should give you "Unauthorized" along with a lot more of text -4. Go to another tab in the SAME BROWSER (important) and log into ZM -5. Repeat step 3 and it should give you the ZM and API version - -**Congrats** your installation is complete - - -Easy Way: Install ZoneMinder from a package (Ubuntu 14.x) ------------------------------------------------------------ +Easy Way: Ubuntu 14.x +--------------------- **These instructions are for a brand new ubuntu 14.x system which does not have ZM installed.** -**Step 1:** Install ZoneMinder +**Step 1:** Either run commands in this install using sudo or use the below to become root :: - sudo add-apt-repository ppa:iconnor/zoneminder - sudo apt-get update - sudo apt-get install zoneminder + sudo -i + +**Step 2:** Install ZoneMinder + +:: + + add-apt-repository ppa:iconnor/zoneminder + apt-get update + apt-get install zoneminder (just press OK for the prompts you get) -**Step 2:** Set up DB +**Step 3:** Set up DB :: - sudo mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql + mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql mysql -uroot -p -e "grant select,insert,update,delete,create,alter,index,lock tables on zm.* to 'zmuser'@localhost identified by 'zmpass';" -**Step 3:** Set up Apache +**Step 4:** Set up Apache :: - sudo a2enconf zoneminder - sudo a2enmod rewrite - sudo a2enmod cgi + a2enconf zoneminder + a2enmod rewrite + a2enmod cgi -**Step 4:**:Some tweaks that will be needed: - -Edit ``/etc/init.d/zoneminder``: - -add a ``sleep 10`` right after line 25 that reads ``echo -n "Starting $prog:"`` -(The reason we need this sleep is to make sure ZM starts after mysqld starts) - -As of Oct 3 2015, zm.conf is not readable by ZM. This is likely a bug and will go away in the next package +**Step 5:** Make zm.conf readable by web user. :: sudo chown www-data:www-data /etc/zm/zm.conf - -**Step 5**: If you have changed your DB login/password - -If you changed the DB password **after** installing ZM, the APIs will not be able to connect to the DB. - -If you have, go to ``/usr/share/zoneminder/www/api/app/Config`` & Edit ``database.php`` - -There is a class there called ``DATABASE_CONFIG`` - change the ``$default`` array to reflect your new details. Example: +**Step 6:** Edit Timezone in PHP :: - public $default = array( - 'datasource' => 'Database/Mysql', - 'persistent' => false, - 'host' => 'localhost', - 'login' => 'mynewDBusername', - 'password' => 'mynewDBpassword' - 'database' => 'zm', - 'prefix' => '', - //'encoding' => 'utf8',` - ); + nano /etc/php5/apache2/php.ini -We also need to install php5-gd (as of 1.28.107, this is not installed) +Search for [Date] (Ctrl + w then type Date and press Enter) and change +date.timezone for your time zone, see [this](http://php.net/manual/en/timezones.php). +**Don't forget to remove the ; from in front of date.timezone** :: - sudo apt-get install php5-gd + [Date] + ; Defines the default timezone used by the date functions + ; http://php.net/date.timezone + date.timezone = America/New_York +CTRL+o then [Enter] to save -**Step 6**: Edit Timezone in PHP +CTRL+x to exit -``sudo vi /etc/php5/apache2/php.ini`` -Look for [Date] and inside it you will see a date.timezone -that is commented. remove the comment and specific your timezone. -Please make sure the timezone is valid (see [this](http://php.net/manual/en/timezones.php)) - -In my case: +**Step 7:** Restart Apache service and start ZoneMinder :: - date.timezone = America/New_York + service apache2 reload + service zoneminder start -**Step 7: make sure live streaming works**: Make sure you can view Monitor streams: +**Step 8:** Making sure ZoneMinder works -startup ZM console in your browser, go to ``Options->Path`` and make sure ``PATH_ZMS`` is set to ``/zm/cgi-bin/nph-zms`` and restart ZM (you should not need to do this for packages, as this should automatically work) +1. Open up a browser and go to ``http://hostname_or_ip/zm`` - should bring up ZoneMinder Console +2. (Optional API Check)Open up a tab in the same browser and go to ``http://hostname_or_ip/zm/api/host/getVersion.json`` + If it is working correctly you should get version information similar to the example below: -restart: - -:: - - sudo service apache2 restart - sudo service zoneminder restart - -**Step 8**: Making sure ZM and APIs work: (optional - only if you need APIs) - -1. open up a browser and go to ``http://localhost/zm`` - should bring up ZM -2. (OPTIONAL - just for peace of mind) open up a tab and go to ``http://localhost/zm/api`` - should bring up a screen showing CakePHP version with some green color boxes. Green is good. If you see red, or you don't see green, there may be a problem (should not happen). Ignore any warnings in yellow saying "DebugKit" not installed. You don't need it -3. open up a tab in the same browser and go to ``http://localhost/zm/api/host/getVersion.json`` - -If it responds with something like: - -:: - - { - "version": "1.28.107", - "apiversion": "1.28.107.1" - } - -Then your APIs are working - -Make sure you can view Monitor View: -1. Open up ZM, configure your monitors and verify you can view Monitor feeds. -2. If not, open up ZM console in your browser, go to ``Options->Path`` and make sure ``PATH_ZMS`` is set to ``/zm/cgi-bin/nph-zms`` and restart ZM (you should not need to do this for packages, as this should automatically work) - -Make sure ZM and APIs work with security: -1. Enable OPT_AUTH in ZM -2. Log out of ZM in browser -3. Open a NEW tab in the SAME BROWSER (important) and go to ``http://localhost/zm/api/host/getVersion.json`` - should give you "Unauthorized" along with a lot more of text -4. Go to another tab in the SAME BROWSER (important) and log into ZM -5. Repeat step 3 and it should give you the ZM and API version - -**Congrats** Your installation is complete - + :: + { + "version": "1.29.0", + "apiversion": "1.29.0.1" + } +**Congratulations** Your installation is complete Harder Way: Build Package From Source -------------------------------------------- +------------------------------------- (These instructions assume installation from source on a ubuntu 15.x+ system) **Step 1:** Grab the package installer script @@ -307,19 +287,28 @@ To build the latest master snapshot: :: - ./do_debian_package.sh `lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'` `date +%Y%m%d`01 local master + ./do_debian_package.sh --snapshot=NOW --branch=master --type=local To build the latest stable release: :: - ./do_debian_package.sh `lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'` `date +%Y%m%d`01 local stable + ./do_debian_package.sh --snapshot=stable --type=local -Note that the ``lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`` part simply extracts your distribution name - like "vivid", "trusty" etc. You can always replace it by your distro name if you know it. As far as the script goes, it checks if your distro is "trusty" in which case it pulls in pre-systemd release configurations and if its not "trusty" it assumes its based on systemd and pulls in systemd related config files. +Note that the distribution will be guessed using ``lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`` +which simply extracts your distribution name - like "vivid", "trusty" etc. You +can always specify it using --distro=your distro name if you know it. As far as the script +goes, it checks if your distro is "trusty" in which case it pulls in pre-systemd +release configurations and if its not "trusty" it assumes its based on systemd +and pulls in systemd related config files. -(At the end the script will ask if you want to retain the checked out version of zoneminder. If you are a developer and are making local changes, make sure you select "y" so that the next time you do the build process mentioned here, it keeps your changes. Selecting any other value than "y" or "Y" will delete the checked out code and only retain the package) +(At the end the script will ask if you want to retain the checked out version of +ZoneMinder. If you are a developer and are making local changes, make sure you +select "y" so that the next time you do the build process mentioned here, it +keeps your changes. Selecting any other value than "y" or "Y" will delete the +checked out code and only retain the package) This should now create a bunch of .deb files @@ -335,57 +324,40 @@ This should now create a bunch of .deb files **Step 5:** Post install configuration -:: +Now that you have installed from your own package you can resume following the +standard install guide for your version, start at the step after Install Zoneminder. - sudo mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql - mysql -uroot -p -e "grant select,insert,update,delete,create,alter,index,lock tables on zm.* to 'zmuser'@localhost identified by 'zmpass';" +Hints +----- +Make sure ZoneMinder and APIs work with security +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - sudo a2enmod cgi rewrite - sudo a2enconf zoneminder +1. Enable OPT_AUTH in ZoneMinder +2. Log out of ZoneMinder in browser +3. Open a new tab in the *same browser* (important) and go to + ``http://localhost/zm/api/host/getVersion.json`` - should give you "Unauthorized" + along with a lot more of text +4. Go to another tab in the SAME BROWSER (important) and log into ZM +5. Repeat step 3 and it should give you the ZM and API version + +Socket_sendto or no live streaming +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +After you have setup your camera make sure you can view Monitor streams, if not +check some of the common causes: + +* Check Apache cgi module is enabled. +* Check Apache /etc/apache2/conf-enabled/zoneminder.conf ScriptAlias matches PATH_ZMS. + + ScriptAlias **/zm/cgi-bin** /usr/lib/zoneminder/cgi-bin + + From console go to ``Options->Path`` and make sure PATH_ZMS is set to **/zm/cgi-bin/**\ nph-zms. +Changed Default DB User +^^^^^^^^^^^^^^^^^^^^^^^ -**Step 6:** Fix PHP TimeZone - -``sudo vi /etc/php5/apache2/php.ini`` - -Look for [Date] and inside it you will see a date.timezone that is commented. remove the comment and specific your timezone. Please make sure the timezone is valid (see http://php.net/manual/en/timezones.php) - -Example: - -``date.timezone = America/New_York`` - -**Step 7:** Fix some key permission issues and make sure API works - -:: - - sudo chown www-data /etc/zm/zm.conf - sudo chown -R www-data /usr/share/zoneminder/www/api/ - - -**Step 8:** Restart all services - -:: - - sudo service apache2 restart - sudo service zoneminder restart - -Check if ZM is running properly - -:: - - sudo service zoneminder status - - -**Step 9:** Make sure streaming works - set PATH_ZMS - -open up ZM console in your browser, go to Options->Path and make sure ``PATH_ZMS`` is set to ``/zm/cgi-bin/nph-zms`` and restart ZM - - -**Step 10:** Make sure everything works - -* point your browser to http://yourzmip/zm - you should see ZM console running -* point your browser to http://yourzmip/zm/api/host/getVersion.json - you should see an API version -* Configure your monitors and make sure its all a-ok - +If you have changed your DB login/password from zmuser/zmpass, you need to +update these values in zm.conf. +1. Edit zm.conf to change ZM_DB_USER and ZM_DB_PASS to the values you used. diff --git a/docs/userguide/definemonitor.rst b/docs/userguide/definemonitor.rst index d69b44425..6fe946c28 100644 --- a/docs/userguide/definemonitor.rst +++ b/docs/userguide/definemonitor.rst @@ -46,10 +46,19 @@ Linked Monitors This field allows you to select other monitors on your system that act as triggers for this monitor. So if you have a camera covering one aspect of your property you can force all cameras to record while that camera detects motion or other events. You can either directly enter a comma separated list of monitor ids or click on ‘Select’ to choose a selection. Be very careful not to create circular dependencies with this feature however you will have infinitely persisting alarms which is almost certainly not what you want! To unlink monitors you can ctrl-click. Maximum FPS - On some occasions you may have one or more cameras capable of high capture rates but find that you generally do not require this performance at all times and would prefer to lighten the load on your server. This option permits you to limit the maximum capture rate to a specified value. This may allow you to have more cameras supported on your system by reducing the CPU load or to allocate video bandwidth unevenly between cameras sharing the same video device. This value is only a rough guide and the lower the value you set the less close the actual FPS may approach it especially on shared devices where it can be difficult to synchronise two or more different capture rates precisely. This option controls the maximum FPS in the circumstance where no alarm is occurring only. (Note for IP cameras: ZoneMinder has no way to set or limit the mjpeg stream the camera passes, some cams you can set this through the url string, others do not. So if you're using mjpeg feeds you must NOT throttle here at the server end, only the cam end. If you want to use this feature, the server to throttle, then you MUST use jpeg instead of mjpeg method to get picture from the camera) - + On some occasions you may have one or more cameras capable of high capture rates but find that you generally do not require this performance at all times and would prefer to lighten the load on your server. This option permits you to limit the maximum capture rate to a specified value. This may allow you to have more cameras supported on your system by reducing the CPU load or to allocate video bandwidth unevenly between cameras sharing the same video device. This value is only a rough guide and the lower the value you set the less close the actual FPS may approach it especially on shared devices where it can be difficult to synchronise two or more different capture rates precisely. This option controls the maximum FPS in the circumstance where no alarm is occurring only. + + This feature is limited and will only work under the following conditions: + + #. Local cameras + #. Remote (IP) cameras in snapshot or jpeg mode **only** + + Using this field for video streams from IP cameras will cause undesirable results when the value is equal to or less than the frame rate from the camera. Note that placing a value higher than the camera's frame rate is allowed and can help prevent cpu spikes when communication from the camera is lost. + Alarm Maximum FPS If you have specified a Maximum FPS it may be that you don’t want this limitation to apply when your monitor is recording motion or other event. This setting allows you to override the Maximum FPS value if this circumstance occurs. As with the Maximum FPS setting leaving this blank implies no limit so if you have set a maximum fps in the previous option then when an alarm occurs this limit would be ignored and ZoneMinder would capture as fast as possible for the duration of the alarm, returning to the limited value after the alarm has concluded. Equally you could set this to the same, or higher (or even lower) value than Maximum FPS for more precise control over the capture rate in the event of an alarm. + + **IMPORTANT:** This field is subject to the same limitations as the Maxium FPS field. Ignoring these limitations will produce undesriable results. Reference Image Blend %ge Each analysed image in ZoneMinder is a composite of previous images and is formed by applying the current image as a certain percentage of the previous reference image. Thus, if we entered the value of 10 here, each image’s part in the reference image will diminish by a factor of 0.9 each time round. So a typical reference image will be 10% the previous image, 9% the one before that and then 8.1%, 7.2%, 6.5% and so on of the rest of the way. An image will effectively vanish around 25 images later than when it was added. This blend value is what is specified here and if higher will make slower progressing events less detectable as the reference image would change more quickly. Similarly events will be deemed to be over much sooner as the reference image adapts to the new images more quickly. In signal processing terms the higher this value the steeper the event attack and decay of the signal. It depends on your particular requirements what the appropriate value would be for you but start with 10 here and adjust it (usually down) later if necessary. @@ -62,11 +71,17 @@ Source Tab FFmpeg ^^^^^^ - + This is the recommended source type for most modern ip cameras. Source Path - Use this field to enter the full URL of the stream or file. Look in Supported Hardware > Network Cameras section, how to obtain these strings that may apply to your camera. RTSP streams may be specified here. + Use this field to enter the full URL of the stream or file your camera supports. This is usually an RTSP url. There are several methods to learn this: + + * Check the documentation that came with your camera + * Look for your camera in the hardware compatibilty list in the wiki http://wiki.zoneminder.com/Hardware_Compatibilty_List + * Try ZoneMinder's new ONVIF probe feature + * Download and install the ONVIF Device Manager onto a Windows machine https://sourceforge.net/projects/onvifdm/ + * Use Google to find third party sites, such as ispy, which document this information Source Colours - Specify the amount of colours in the captured image. Unlike with local cameras changing this has no controlling effect on the remote camera itself so ensure that your camera is actually capturing to this palette beforehand. + Specify the amount of colours in the captured image. 32 bit is the preferred choice here. Unlike with local cameras changing this has no controlling effect on the remote camera itself so ensure that your camera is actually capturing to this palette beforehand. Capture Width/Height Make sure you enter here the same values as they are in the remote camera's internal setting. Keep aspect ratio @@ -76,6 +91,7 @@ Orientation LibVLC ^^^^^^ + The fields for the LibVLC source type are configured the same way as the ffmpeg source type. We recommend only using this source type if issues are experienced with the ffmpeg source type. cURL ^^^^ @@ -88,7 +104,7 @@ Device Path/Channel Device Format Enter the video format of the video stream. This is defined in various system files (e.g. /usr/include/linux/videodev.h) but the two most common are 0 for PAL and 1 for NTSC. Capture Palette - Finally for the video part of the configuration enter the colour depth. ZoneMinder supports a handful of the most common palettes, so choose one here. If in doubt try grey first, and then 24 bit colour. If neither of these work very well then YUV420P or one of the others probably will. There is a slight performance penalty when using palettes other than grey or 24 bit colour as an internal conversion is involved. These other formats are intended to be supported natively in a future version but for now if you have the choice choose one of grey or 24 bit colour. + Finally for the video part of the configuration enter the colour depth. ZoneMinder supports a handful of the most common palettes, so choose one here. If in doubt try 32 bit colour first, then 24 bit colour, then grey. If none of these work very well, and your camera is local, then YUV420P or one of the others probably will. There is a slight performance penalty when using palettes other than 32, 24, or grey palettes as an internal conversion is involved. Recent versions of ZoneMinder support 32bit colour. This capture palette provides a performance boost when used on all modern Intel-based processors. Capture Width/Height The dimensions of the video stream your camera will supply. If your camera supports several just enter the one you'll want to use for this application, you can always change it later. However I would recommend starting with no larger than 320x240 or 384x288 and then perhaps increasing and seeing how performance is affected. This size should be adequate in most cases. Some cameras are quite choosy about the sizes you can use here so unusual sizes such as 197x333 should be avoided initially. Keep aspect ratio @@ -99,8 +115,12 @@ Orientation Remote ^^^^^^ +Remote Protocol + Choices are currently HTTP and RTSP. Before RTSP became the industry standard, many ip cameras streamed directly from their web portal. If you have an ip camera that does not speak RTSP then choose HTTP here. **If you camera does speak RTSP then you should change your source type to ffmpeg instead of selecting RTSP here.** The Remote -> RTSP method is no longer being maintained and may go away at some point in the future. +Remote Method + When HTTP is the Remote Protocol, your choices are Simple and Regexp. Most should choose Simple. When RTSP is the Remote Protocol, your choices are RTP/Unicast, RTP/Multicast, RTP/RTSP, RTP,RTSP,HTTP. Try each of these to determine which works with your camera. Most cameras will use either RTP/Unicast (UDP) or RTP/RTSP (TCP). Remote Host/Port/Path - Use these fields to enter the full URL of the camera. Basically if your camera is at http://camserver.home.net:8192/cameras/camera1.jpg then these fields will be camserver.home.net, 8192 and /cameras/camera1.jpg respectively. Leave the port at 80 if there is no special port required. If you require authentication to access your camera then add this onto the host name in the form :@.com. This will usually be 24 bit colour even if the image looks black and white. Look in Supported Hardware > Network Cameras section, how to obtain these strings that may apply to your camera. + Use these fields to enter the full URL of the camera. Basically if your camera is at http://camserver.home.net:8192/cameras/camera1.jpg then these fields will be camserver.home.net, 8192 and /cameras/camera1.jpg respectively. Leave the port at 80 if there is no special port required. If you require authentication to access your camera then add this onto the host name in the form :@.com. This will usually be 32 or 24 bit colour even if the image looks black and white. Look in Supported Hardware > Network Cameras section, how to obtain these strings that may apply to your camera. Remote Image Colours Specify the amount of colours in the captured image. Unlike with local cameras changing this has no controlling effect on the remote camera itself so ensure that your camera is actually capturing to this palette beforehand. Capture Width/Height @@ -118,7 +138,7 @@ File File Path Enter the full path to the file to be used as the image source. File Colours - Specify the amount of colours in the image. Usually 24 bit colour. + Specify the amount of colours in the image. Usually 32 bit colour. Capture Width/Height As per local devices. Keep aspect ratio diff --git a/docs/userguide/definezone.rst b/docs/userguide/definezone.rst index 0cacd3b91..cf33f44c9 100644 --- a/docs/userguide/definezone.rst +++ b/docs/userguide/definezone.rst @@ -40,6 +40,8 @@ Type Preset The preset chooser sets sensible default values based on computational needs (fast v. best) and sensitivity (low, medium, high.) It is not required that you select a preset, and you can alter any of the parameters after choosing a preset. For a small number of monitors with ZoneMinder running on modern equipment, Best, high sensitivity can be chosen as a good starting point. + It is important to understand that the available presets are intended merely as a starting point. Since every camera's view is unique, they are not guaranteed to work properly in every case. Presets tend to work acceptably for indoor cameras, where the objects of interest are relatively close and there typically are few or no unwanted objects moving within the cameras view. Presets, on the other hand, tend to not work acceptably for outdoor cameras, where the field of view is typically much wider, objects of interest are farther away, and changing weather patterns can cause false triggers. For outdoor cameras in particular, you will almost certainly have to tune your motion detection zone to get desired results. Please refer to `this guide `__ to learn how to do this. + Units * Pixels - Selecting this option will allow many of the following values to be entered (or viewed) in units of pixels. * Percentage - Selecting this option will allow may of the following values to be entered (or viewed) as a percentage. The sense of the percentage values refers to the area of the zone and not the image as a whole. This makes trying to work out necessary sizes rather easier. @@ -56,7 +58,7 @@ Alarm Colour These parameters can be used to individually colorize the zone overlay pattern. Alarms in this zone will be highlighted in the alarm colour. This option is irrelevant for Preclusive and Inactive zones and will be disabled. Alarm Check Methods - There are 3 Alarm Check Methods. They are sequential, and are layered: In AlarmedPixels mode, only the AlarmedPixel analysis is performed. In FilteredPixels mode, the AlarmedPixel analysis is performed first, followed by the AlarmedPixel analysis. In the Blobs mode, all 3 analysis methods are performed in order. An alarm is only triggered if *all* of the enabled analysis modes are triggered. For performance reasons, as soon as the criteria for one of the analysis modes is not met, the alarm checking for the frame is complete. Since the subsequent modes each require progressively more computations, it is a good idea to tune the parameters in each of the activated layers. + There are 3 Alarm Check Methods. They are sequential, and are layered: In AlarmedPixels mode, only the AlarmedPixel analysis is performed. In FilteredPixels mode, the AlarmedPixel analysis is performed first, followed by the FilteredPixel analysis. In the Blobs mode, all 3 analysis methods are performed in order. An alarm is only triggered if *all* of the enabled analysis modes are triggered. For performance reasons, as soon as the criteria for one of the analysis modes is not met, the alarm checking for the frame is complete. Since the subsequent modes each require progressively more computations, it is a good idea to tune the parameters in each of the activated layers. For reference purposes, the Zone Area box shows the area of the entire region of interest. In percent mode, this is 100. In Pixels mode, this is the pixel count of the region. All 3 Min/Max Area parameter groups are based on the Zone Area as the maximum sensible value, and all 3 are interpreted in the units specified in the Units input. diff --git a/docs/userguide/filterevents.rst b/docs/userguide/filterevents.rst index bc0dcffe2..8477ef157 100644 --- a/docs/userguide/filterevents.rst +++ b/docs/userguide/filterevents.rst @@ -29,7 +29,7 @@ Here is what the filter window looks like events later and also make sure archived events don't get deleted, for example * Email details of all matches: Sends an email to the configured address with details about the event. The email can be customized as per TBD - * Execute command on all matches: Allows you to execute any arbitrary command on the matched events + * Execute command on all matches: Allows you to execute any arbitrary command on the matched events. You can use replacement tokens as subsequent arguents to the command, the last argument will be the absolute path to the event, preceeded by replacement arguents. eg: /usr/bin/script.sh %MN% will excecute as /usr/bin/script.sh MonitorName /path/to/event. * Delete all matches: Deletes all the matched events * *E*: Use 'Submit' to 'test' your matching conditions. This will just match and show you what filters match. Use 'Execute' to actually execute the action after matching your conditions. Use 'Save' to save the filter for future use and 'Reset' to clear your settings diff --git a/docs/userguide/gettingstarted.rst b/docs/userguide/gettingstarted.rst index b2e92f63f..713a667b5 100644 --- a/docs/userguide/gettingstarted.rst +++ b/docs/userguide/gettingstarted.rst @@ -16,7 +16,7 @@ We strongly recommend enabling authentication right away. There are some situati .. image:: images/getting-started-enable-auth.png * The relevant portions to change are marked in red above -* Enable OPT_USE_ATH - this automatically switches to authentication mode with a default user (more on that later) +* Enable OPT_USE_AUTH - this automatically switches to authentication mode with a default user (more on that later) * Select a random string for AUTH_HASH_SECRET - this is used to make the authentication logic more secure, so please generate your own string and please don't use the same value in the example. * The other options highlighed above should already be set, but if not, please make sure they are diff --git a/docs/userguide/images/README.TXT b/docs/userguide/images/README.TXT new file mode 100644 index 000000000..21f85c8c3 --- /dev/null +++ b/docs/userguide/images/README.TXT @@ -0,0 +1,2 @@ +The XML images present in this folder have been drawn using http://draw.io +To edit images, simple go to draw.io and load the .xml files diff --git a/docs/userguide/images/zm-system-overview.jpg b/docs/userguide/images/zm-system-overview.jpg index 1624e44c6..de02dcf9e 100644 Binary files a/docs/userguide/images/zm-system-overview.jpg and b/docs/userguide/images/zm-system-overview.jpg differ diff --git a/docs/userguide/images/zm-system-overview.xml b/docs/userguide/images/zm-system-overview.xml index d0d55d5d0..e4314666a 100644 --- a/docs/userguide/images/zm-system-overview.xml +++ b/docs/userguide/images/zm-system-overview.xml @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/userguide/options.rst b/docs/userguide/options.rst index 8186d8b6a..873002a0d 100644 --- a/docs/userguide/options.rst +++ b/docs/userguide/options.rst @@ -10,6 +10,7 @@ If you have changed the value of an option you should then ‘save’ it. A numb options/options_display options/options_system options/options_config + options/options_servers options/options_paths options/options_web options/options_images diff --git a/docs/userguide/options/options_system.rst b/docs/userguide/options/options_system.rst index 7df3d259a..53e560fe5 100644 --- a/docs/userguide/options/options_system.rst +++ b/docs/userguide/options/options_system.rst @@ -33,10 +33,6 @@ RUN_AUDIT - The zmaudit daemon exists to check that the saved information in the AUDIT_CHECK_INTERVAL - The zmaudit daemon exists to check that the saved information in the database and on the filesystem match and are consistent with each other. If an error occurs or if you are using 'fast deletes' it may be that database records are deleted but files remain. In this case, and similar, zmaudit will remove redundant information to synchronise the two data stores. The default check interval of 900 seconds (15 minutes) is fine for most systems however if you have a very large number of events the process of scanning the database and filesystem may take a long time and impact performance. In this case you may prefer to make this interval much larger to reduce the impact on your system. This option determines how often these checks are performed. -OPT_FRAME_SERVER - In some circumstances it is possible for a slow disk to take so long writing images to disk that it causes the analysis daemon to fall behind especially during high frame rate events. Setting this option to yes enables a frame server daemon (zmf) which will be sent the images from the analysis daemon and will do the actual writing of images itself freeing up the analysis daemon to get on with other things. Should this transmission fail or other permanent or transient error occur, this function will fall back to the analysis daemon. - -FRAME_SOCKET_SIZE - For large captured images it is possible for the writes from the analysis daemon to the frame server to fail as the amount to be written exceeds the default buffer size. While the images are then written by the analysis daemon so no data is lost, it defeats the object of the frame server daemon in the first place. You can use this option to indicate that a larger buffer size should be used. Note that you may have to change the existing maximum socket buffer size on your system via sysctl (or in /proc/sys/net/core/wmem_max) to allow this new size to be set. Alternatively you can change the default buffer size on your system in the same way in which case that will be used with no change necessary in this option - OPT_CONTROL - ZoneMinder includes limited support for controllable cameras. A number of sample protocols are included and others can easily be added. If you wish to control your cameras via ZoneMinder then select this option otherwise if you only have static cameras or use other control methods then leave this option off. OPT_TRIGGERS - ZoneMinder can interact with external systems which prompt or cancel alarms. This is done via the zmtrigger.pl script. This option indicates whether you want to use these external triggers. Most people will say no here. diff --git a/misc/CMakeLists.txt b/misc/CMakeLists.txt index 1b3bd7c5a..016f19881 100644 --- a/misc/CMakeLists.txt +++ b/misc/CMakeLists.txt @@ -12,7 +12,8 @@ configure_file(zoneminder-tmpfiles.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zonemind # Do not install the misc files by default #install(FILES "${CMAKE_CURRENT_BINARY_DIR}/apache.conf" "${CMAKE_CURRENT_BINARY_DIR}/logrotate.conf" "${CMAKE_CURRENT_BINARY_DIR}/syslog.conf" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/misc") -# Install Policykit rules and actions into the proper folders -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions") -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d") - +# Install Policykit rules and actions into the proper folders only on systems with systemd +if(WITH_SYSTEMD) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d") +endif(WITH_SYSTEMD) diff --git a/misc/apache.conf.in b/misc/apache.conf.in index 1ea6ed7c1..d1c6d70db 100644 --- a/misc/apache.conf.in +++ b/misc/apache.conf.in @@ -5,13 +5,22 @@ # Some values may need to manually adjusted to suit your setup # - ServerName @WEB_HOST@ ServerAdmin webmaster@localhost DocumentRoot "@WEB_PREFIX@" + Alias /zm/ "@WEB_PREFIX@/" - Options FollowSymLinks + Options -Indexes +FollowSymLinks AllowOverride All + + # Apache 2.4 + Require all granted + + + # Apache 2.2 + Order deny,allow + Allow from all + ScriptAlias /cgi-bin "@CGI_PREFIX@" diff --git a/misc/zoneminder.service.in b/misc/zoneminder.service.in index 23ba45601..d1cfb36a0 100644 --- a/misc/zoneminder.service.in +++ b/misc/zoneminder.service.in @@ -12,7 +12,8 @@ Type=forking ExecStart=@BINDIR@/zmpkg.pl start ExecReload=@BINDIR@/zmpkg.pl restart ExecStop=@BINDIR@/zmpkg.pl stop -PIDFile="@ZM_RUNDIR@/zm.pid" +PIDFile=@ZM_RUNDIR@/zm.pid +Environment=TZ=:/etc/localtime [Install] WantedBy=multi-user.target diff --git a/onvif/README b/onvif/README index 25065e18a..06df9cc64 100644 --- a/onvif/README +++ b/onvif/README @@ -14,7 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -------------------------------------------------------------------------- 1. PURPOSE diff --git a/onvif/modules/lib/ONVIF/Client.pm b/onvif/modules/lib/ONVIF/Client.pm index eaf78747d..00137b90a 100644 --- a/onvif/modules/lib/ONVIF/Client.pm +++ b/onvif/modules/lib/ONVIF/Client.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -77,6 +77,8 @@ my %soap_version_of :ATTR(:default<('1.1')>); sub service { my ($self, $serviceName, $attr) = @_; +#print "service: " . $services_of{${$self}}{$serviceName}{$attr} . "\n"; +# Please note that the Std::Class::Fast docs say not to use ident. $services_of{ident $self}{$serviceName}{$attr}; } @@ -113,27 +115,59 @@ sub set_soap_version delete $serializer_of{ ident $self }; } -sub get_service_urls -{ +sub get_service_urls { my ($self) = @_; my $result = $self->service('device', 'ep')->GetServices( { IncludeCapability => 'true', # boolean },, ); - - die $result if not $result; -# print $result . "\n"; - - foreach my $svc ( @{ $result->get_Service() } ) { - my $short_name = $namespace_map{$svc->get_Namespace()}; - my $url_svc = $svc->get_XAddr()->get_value(); - if(defined $short_name && defined $url_svc) { + if ( $result ) { + foreach my $svc ( @{ $result->get_Service() } ) { + my $short_name = $namespace_map{$svc->get_Namespace()}; + my $url_svc = $svc->get_XAddr()->get_value(); + if(defined $short_name && defined $url_svc) { # print "Got $short_name service\n"; - $self->set_service($short_name, 'url', $url_svc); + $self->set_service($short_name, 'url', $url_svc); + } } + # } else { + #print "No results from GetServices: $result\n"; } -} + + # Some devices do not support getServices, so we have to try getCapabilities + + $result = $self->service('device', 'ep')->GetCapabilities( {}, , ); + if ( ! $result ) { + print "No results from GetCapabilities: $result\n"; + return; + } + # Result is a GetCapabilitiesResponse + foreach my $capabilities ( @{ $result->get_Capabilities() } ) { + foreach my $capability ( 'PTZ', 'Media', 'Imaging', 'Events', 'Device' ) { + if ( my $function = $capabilities->can( "get_$capability" ) ) { + my $Services = $function->( $capabilities ); + if ( ! $Services ) { + print "Nothing returned ffrom get_$capability\n"; + } else { + foreach my $svc ( @{ $Services } ) { + # The capability versions don't have a namespace, so just lowercase them. + my $short_name = lc $capability; + my $url_svc = $svc->get_XAddr()->get_value(); + if( defined $url_svc) { +# print "Got $short_name service\n"; + $self->set_service($short_name, 'url', $url_svc); + } + } # end foreach svr + } + } else { + print "No $capability function\n"; + + } # end if has a get_ function + } # end foreach capability + } # end foreach capabilities + +} # end sub get_service_urls sub http_digest { my ($service, $username, $password) = @_; diff --git a/onvif/modules/lib/ONVIF/Deserializer/MessageParser.pm b/onvif/modules/lib/ONVIF/Deserializer/MessageParser.pm index b0ce6f0fd..218a4caa5 100644 --- a/onvif/modules/lib/ONVIF/Deserializer/MessageParser.pm +++ b/onvif/modules/lib/ONVIF/Deserializer/MessageParser.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -272,7 +272,11 @@ sub _initialize { # $_method =~s{\.}{__}xg; $_method =~s{\-}{_}xg; - $list->[-1]->$_method( $current ); + if ( $list->[-1]->can( $_method ) ) { + $list->[-1]->$_method( $current ); + } else { + print ( "ERror " . $list->[-1] . " cannot $_method\n" ); + } $current = pop @$list; # step up in object hierarchy diff --git a/onvif/modules/lib/ONVIF/Deserializer/XSD.pm b/onvif/modules/lib/ONVIF/Deserializer/XSD.pm index b4694d90d..215a3cb4d 100644 --- a/onvif/modules/lib/ONVIF/Deserializer/XSD.pm +++ b/onvif/modules/lib/ONVIF/Deserializer/XSD.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/onvif/modules/lib/ONVIF/Serializer/Base.pm b/onvif/modules/lib/ONVIF/Serializer/Base.pm index adbb173c6..e68ef9ae3 100644 --- a/onvif/modules/lib/ONVIF/Serializer/Base.pm +++ b/onvif/modules/lib/ONVIF/Serializer/Base.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/onvif/modules/lib/ONVIF/Serializer/SOAP11.pm b/onvif/modules/lib/ONVIF/Serializer/SOAP11.pm index 442a11d7a..c3c4caf60 100644 --- a/onvif/modules/lib/ONVIF/Serializer/SOAP11.pm +++ b/onvif/modules/lib/ONVIF/Serializer/SOAP11.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/onvif/modules/lib/ONVIF/Serializer/SOAP12.pm b/onvif/modules/lib/ONVIF/Serializer/SOAP12.pm index 2805ea40f..d0e589cac 100644 --- a/onvif/modules/lib/ONVIF/Serializer/SOAP12.pm +++ b/onvif/modules/lib/ONVIF/Serializer/SOAP12.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/onvif/modules/lib/WSDiscovery/TransportUDP.pm b/onvif/modules/lib/WSDiscovery/TransportUDP.pm index edc837da6..5bbf05671 100644 --- a/onvif/modules/lib/WSDiscovery/TransportUDP.pm +++ b/onvif/modules/lib/WSDiscovery/TransportUDP.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -145,17 +145,18 @@ sub send_receive { } if($last_response) { - $self->code(); - $self->message(); - $self->is_success(1); - $self->status('OK'); + $self->set_code(); + $self->set_message(""); + $self->set_is_success(1); + $self->set_status('OK'); } else{ - $self->code(); - $self->message(); - $self->is_success(0); - $self->status('TIMEOUT'); + $self->set_code(); + $self->set_message("Timed out waiting for response"); + $self->set_is_success(0); + $self->set_status('TIMEOUT'); } + return $last_response; } diff --git a/onvif/modules/lib/WSSecurity/SecuritySerializer.pm b/onvif/modules/lib/WSSecurity/SecuritySerializer.pm index 11a918af7..a99d9064c 100644 --- a/onvif/modules/lib/WSSecurity/SecuritySerializer.pm +++ b/onvif/modules/lib/WSSecurity/SecuritySerializer.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/onvif/proxy/lib/ONVIF/Analytics/Elements/MetadataStream.pm b/onvif/proxy/lib/ONVIF/Analytics/Elements/MetadataStream.pm index eb0831001..43d49bfa0 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Elements/MetadataStream.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Elements/MetadataStream.pm @@ -49,10 +49,10 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Analytics::Types::MetadataStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... VideoAnalytics => { # ONVIF::Analytics::Types::VideoAnalyticsStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... Frame => { # ONVIF::Analytics::Types::Frame PTZStatus => { # ONVIF::Analytics::Types::PTZStatus Position => { # ONVIF::Analytics::Types::PTZVector @@ -155,7 +155,7 @@ Constructor. The following data structure may be passed to new(): }, PTZ => { # ONVIF::Analytics::Types::PTZStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PTZStatus => { # ONVIF::Analytics::Types::PTZStatus Position => { # ONVIF::Analytics::Types::PTZVector PanTilt => , diff --git a/onvif/proxy/lib/ONVIF/Analytics/Interfaces/Analytics/AnalyticsEnginePort.pm b/onvif/proxy/lib/ONVIF/Analytics/Interfaces/Analytics/AnalyticsEnginePort.pm index 481d254ce..8baac1067 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Interfaces/Analytics/AnalyticsEnginePort.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Interfaces/Analytics/AnalyticsEnginePort.pm @@ -235,7 +235,7 @@ of the corresponding class can be passed instead of the marked hash ref. You may pass any combination of objects, hash and list refs to these methods, as long as you meet the structure. -List items (i.e. multiple occurences) are not displayed in the synopsis. +List items (i.e. multiple occurrences) are not displayed in the synopsis. You may generally pass a list ref of hash refs (or objects) instead of a hash ref - this may result in invalid XML if used improperly, though. Note that SOAP::WSDL always expects list references at maximum depth position. diff --git a/onvif/proxy/lib/ONVIF/Analytics/Interfaces/Analytics/RuleEnginePort.pm b/onvif/proxy/lib/ONVIF/Analytics/Interfaces/Analytics/RuleEnginePort.pm index 835e119ff..79a1b8b85 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Interfaces/Analytics/RuleEnginePort.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Interfaces/Analytics/RuleEnginePort.pm @@ -208,7 +208,7 @@ of the corresponding class can be passed instead of the marked hash ref. You may pass any combination of objects, hash and list refs to these methods, as long as you meet the structure. -List items (i.e. multiple occurences) are not displayed in the synopsis. +List items (i.e. multiple occurrences) are not displayed in the synopsis. You may generally pass a list ref of hash refs (or objects) instead of a hash ref - this may result in invalid XML if used improperly, though. Note that SOAP::WSDL always expects list references at maximum depth position. diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/ColorOptions.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/ColorOptions.pm index eaf6fea9f..d586d80ed 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/ColorOptions.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/ColorOptions.pm @@ -98,7 +98,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Analytics::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Analytics::Types::ColorspaceRange X => { # ONVIF::Analytics::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/ConfigurationEntity.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/ConfigurationEntity.pm index 8e544d6a6..5bcc84214 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/ConfigurationEntity.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/ConfigurationEntity.pm @@ -139,7 +139,7 @@ get_/set_ methods: =item * token - Token that uniquely refernces this configuration. Length up to 64 characters. + Token that uniquely references this configuration. Length up to 64 characters. diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/MetadataStream.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/MetadataStream.pm index d8000cf7b..edfc77e61 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/MetadataStream.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/MetadataStream.pm @@ -114,10 +114,10 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Analytics::Types::MetadataStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... VideoAnalytics => { # ONVIF::Analytics::Types::VideoAnalyticsStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... Frame => { # ONVIF::Analytics::Types::Frame PTZStatus => { # ONVIF::Analytics::Types::PTZStatus Position => { # ONVIF::Analytics::Types::PTZVector @@ -220,7 +220,7 @@ Constructor. The following data structure may be passed to new(): }, PTZ => { # ONVIF::Analytics::Types::PTZStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PTZStatus => { # ONVIF::Analytics::Types::PTZStatus Position => { # ONVIF::Analytics::Types::PTZVector PanTilt => , diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/OSDColorOptions.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/OSDColorOptions.pm index f39a8d84c..409bcde78 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/OSDColorOptions.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/OSDColorOptions.pm @@ -107,7 +107,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Analytics::Types::OSDColorOptions Color => { # ONVIF::Analytics::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Analytics::Types::ColorspaceRange X => { # ONVIF::Analytics::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/OSDConfigurationOptions.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/OSDConfigurationOptions.pm index 105d52097..b75c1ce12 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/OSDConfigurationOptions.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/OSDConfigurationOptions.pm @@ -143,7 +143,7 @@ Constructor. The following data structure may be passed to new(): FontColor => { # ONVIF::Analytics::Types::OSDColorOptions Color => { # ONVIF::Analytics::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Analytics::Types::ColorspaceRange X => { # ONVIF::Analytics::Types::FloatRange @@ -171,7 +171,7 @@ Constructor. The following data structure may be passed to new(): BackgroundColor => { # ONVIF::Analytics::Types::OSDColorOptions Color => { # ONVIF::Analytics::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Analytics::Types::ColorspaceRange X => { # ONVIF::Analytics::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/OSDTextOptions.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/OSDTextOptions.pm index 533602ffe..d2d129bf5 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/OSDTextOptions.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/OSDTextOptions.pm @@ -147,7 +147,7 @@ Constructor. The following data structure may be passed to new(): FontColor => { # ONVIF::Analytics::Types::OSDColorOptions Color => { # ONVIF::Analytics::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Analytics::Types::ColorspaceRange X => { # ONVIF::Analytics::Types::FloatRange @@ -175,7 +175,7 @@ Constructor. The following data structure may be passed to new(): BackgroundColor => { # ONVIF::Analytics::Types::OSDColorOptions Color => { # ONVIF::Analytics::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Analytics::Types::ColorspaceRange X => { # ONVIF::Analytics::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/PTZPresetTourPresetDetail.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/PTZPresetTourPresetDetail.pm index f8e0c4128..a513a522c 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/PTZPresetTourPresetDetail.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/PTZPresetTourPresetDetail.pm @@ -114,7 +114,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Analytics::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Analytics::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/PTZPresetTourSpot.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/PTZPresetTourSpot.pm index d7cdb6d61..558a9ee7f 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/PTZPresetTourSpot.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/PTZPresetTourSpot.pm @@ -115,7 +115,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Analytics::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::Analytics::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Analytics::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/PTZPresetTourStatus.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/PTZPresetTourStatus.pm index 2058e86f7..246191893 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/PTZPresetTourStatus.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/PTZPresetTourStatus.pm @@ -109,7 +109,7 @@ Constructor. The following data structure may be passed to new(): CurrentTourSpot => { # ONVIF::Analytics::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::Analytics::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Analytics::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/PTZStream.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/PTZStream.pm index a717d6a1c..218ae3b8d 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/PTZStream.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/PTZStream.pm @@ -98,7 +98,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Analytics::Types::PTZStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PTZStatus => { # ONVIF::Analytics::Types::PTZStatus Position => { # ONVIF::Analytics::Types::PTZVector PanTilt => , diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/PresetTour.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/PresetTour.pm index e084452c2..868f814db 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/PresetTour.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/PresetTour.pm @@ -155,7 +155,7 @@ Constructor. The following data structure may be passed to new(): CurrentTourSpot => { # ONVIF::Analytics::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::Analytics::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Analytics::Types::PTZVector @@ -187,7 +187,7 @@ Constructor. The following data structure may be passed to new(): TourSpot => { # ONVIF::Analytics::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::Analytics::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Analytics::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Analytics/Types/VideoAnalyticsStream.pm b/onvif/proxy/lib/ONVIF/Analytics/Types/VideoAnalyticsStream.pm index d5cadba4f..4a3904992 100644 --- a/onvif/proxy/lib/ONVIF/Analytics/Types/VideoAnalyticsStream.pm +++ b/onvif/proxy/lib/ONVIF/Analytics/Types/VideoAnalyticsStream.pm @@ -98,7 +98,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Analytics::Types::VideoAnalyticsStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... Frame => { # ONVIF::Analytics::Types::Frame PTZStatus => { # ONVIF::Analytics::Types::PTZStatus Position => { # ONVIF::Analytics::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Device/Elements/MetadataStream.pm b/onvif/proxy/lib/ONVIF/Device/Elements/MetadataStream.pm index e3266ba63..86a5d42f8 100644 --- a/onvif/proxy/lib/ONVIF/Device/Elements/MetadataStream.pm +++ b/onvif/proxy/lib/ONVIF/Device/Elements/MetadataStream.pm @@ -49,10 +49,10 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Device::Types::MetadataStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... VideoAnalytics => { # ONVIF::Device::Types::VideoAnalyticsStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... Frame => { # ONVIF::Device::Types::Frame PTZStatus => { # ONVIF::Device::Types::PTZStatus Position => { # ONVIF::Device::Types::PTZVector @@ -155,7 +155,7 @@ Constructor. The following data structure may be passed to new(): }, PTZ => { # ONVIF::Device::Types::PTZStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PTZStatus => { # ONVIF::Device::Types::PTZStatus Position => { # ONVIF::Device::Types::PTZVector PanTilt => , diff --git a/onvif/proxy/lib/ONVIF/Device/Interfaces/Device/DevicePort.pm b/onvif/proxy/lib/ONVIF/Device/Interfaces/Device/DevicePort.pm index 4e158d590..35f259f31 100644 --- a/onvif/proxy/lib/ONVIF/Device/Interfaces/Device/DevicePort.pm +++ b/onvif/proxy/lib/ONVIF/Device/Interfaces/Device/DevicePort.pm @@ -2205,7 +2205,7 @@ of the corresponding class can be passed instead of the marked hash ref. You may pass any combination of objects, hash and list refs to these methods, as long as you meet the structure. -List items (i.e. multiple occurences) are not displayed in the synopsis. +List items (i.e. multiple occurrences) are not displayed in the synopsis. You may generally pass a list ref of hash refs (or objects) instead of a hash ref - this may result in invalid XML if used improperly, though. Note that SOAP::WSDL always expects list references at maximum depth position. @@ -2358,7 +2358,7 @@ Returns a L object. @@ -2602,7 +2602,7 @@ Returns a L object. diff --git a/onvif/proxy/lib/ONVIF/Device/Types/ColorOptions.pm b/onvif/proxy/lib/ONVIF/Device/Types/ColorOptions.pm index 6e99583d0..6c12db1e8 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/ColorOptions.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/ColorOptions.pm @@ -98,7 +98,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Device::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Device::Types::ColorspaceRange X => { # ONVIF::Device::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Device/Types/ConfigurationEntity.pm b/onvif/proxy/lib/ONVIF/Device/Types/ConfigurationEntity.pm index 240e5800c..b79d6791a 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/ConfigurationEntity.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/ConfigurationEntity.pm @@ -139,7 +139,7 @@ get_/set_ methods: =item * token - Token that uniquely refernces this configuration. Length up to 64 characters. + Token that uniquely references this configuration. Length up to 64 characters. diff --git a/onvif/proxy/lib/ONVIF/Device/Types/MetadataStream.pm b/onvif/proxy/lib/ONVIF/Device/Types/MetadataStream.pm index a52bd1097..e5140786c 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/MetadataStream.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/MetadataStream.pm @@ -114,10 +114,10 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Device::Types::MetadataStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... VideoAnalytics => { # ONVIF::Device::Types::VideoAnalyticsStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... Frame => { # ONVIF::Device::Types::Frame PTZStatus => { # ONVIF::Device::Types::PTZStatus Position => { # ONVIF::Device::Types::PTZVector @@ -220,7 +220,7 @@ Constructor. The following data structure may be passed to new(): }, PTZ => { # ONVIF::Device::Types::PTZStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PTZStatus => { # ONVIF::Device::Types::PTZStatus Position => { # ONVIF::Device::Types::PTZVector PanTilt => , diff --git a/onvif/proxy/lib/ONVIF/Device/Types/OSDColorOptions.pm b/onvif/proxy/lib/ONVIF/Device/Types/OSDColorOptions.pm index 453eee12b..6b71ec3c8 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/OSDColorOptions.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/OSDColorOptions.pm @@ -107,7 +107,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Device::Types::OSDColorOptions Color => { # ONVIF::Device::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Device::Types::ColorspaceRange X => { # ONVIF::Device::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Device/Types/OSDConfigurationOptions.pm b/onvif/proxy/lib/ONVIF/Device/Types/OSDConfigurationOptions.pm index ebad78c4d..d40c0f4ab 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/OSDConfigurationOptions.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/OSDConfigurationOptions.pm @@ -143,7 +143,7 @@ Constructor. The following data structure may be passed to new(): FontColor => { # ONVIF::Device::Types::OSDColorOptions Color => { # ONVIF::Device::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Device::Types::ColorspaceRange X => { # ONVIF::Device::Types::FloatRange @@ -171,7 +171,7 @@ Constructor. The following data structure may be passed to new(): BackgroundColor => { # ONVIF::Device::Types::OSDColorOptions Color => { # ONVIF::Device::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Device::Types::ColorspaceRange X => { # ONVIF::Device::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Device/Types/OSDTextOptions.pm b/onvif/proxy/lib/ONVIF/Device/Types/OSDTextOptions.pm index 2247a07c3..69b276cd5 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/OSDTextOptions.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/OSDTextOptions.pm @@ -147,7 +147,7 @@ Constructor. The following data structure may be passed to new(): FontColor => { # ONVIF::Device::Types::OSDColorOptions Color => { # ONVIF::Device::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Device::Types::ColorspaceRange X => { # ONVIF::Device::Types::FloatRange @@ -175,7 +175,7 @@ Constructor. The following data structure may be passed to new(): BackgroundColor => { # ONVIF::Device::Types::OSDColorOptions Color => { # ONVIF::Device::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Device::Types::ColorspaceRange X => { # ONVIF::Device::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Device/Types/PTZPresetTourPresetDetail.pm b/onvif/proxy/lib/ONVIF/Device/Types/PTZPresetTourPresetDetail.pm index bcbd21e7a..5e48bc31d 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/PTZPresetTourPresetDetail.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/PTZPresetTourPresetDetail.pm @@ -114,7 +114,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Device::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Device::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Device/Types/PTZPresetTourSpot.pm b/onvif/proxy/lib/ONVIF/Device/Types/PTZPresetTourSpot.pm index 9c97d1759..2b3aaacaa 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/PTZPresetTourSpot.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/PTZPresetTourSpot.pm @@ -115,7 +115,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Device::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::Device::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Device::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Device/Types/PTZPresetTourStatus.pm b/onvif/proxy/lib/ONVIF/Device/Types/PTZPresetTourStatus.pm index 778a94475..9ac897ad4 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/PTZPresetTourStatus.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/PTZPresetTourStatus.pm @@ -109,7 +109,7 @@ Constructor. The following data structure may be passed to new(): CurrentTourSpot => { # ONVIF::Device::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::Device::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Device::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Device/Types/PTZStream.pm b/onvif/proxy/lib/ONVIF/Device/Types/PTZStream.pm index cf05d13b2..7e2d61529 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/PTZStream.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/PTZStream.pm @@ -98,7 +98,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Device::Types::PTZStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PTZStatus => { # ONVIF::Device::Types::PTZStatus Position => { # ONVIF::Device::Types::PTZVector PanTilt => , diff --git a/onvif/proxy/lib/ONVIF/Device/Types/PresetTour.pm b/onvif/proxy/lib/ONVIF/Device/Types/PresetTour.pm index 68d6b55a5..ca997cabb 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/PresetTour.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/PresetTour.pm @@ -155,7 +155,7 @@ Constructor. The following data structure may be passed to new(): CurrentTourSpot => { # ONVIF::Device::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::Device::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Device::Types::PTZVector @@ -187,7 +187,7 @@ Constructor. The following data structure may be passed to new(): TourSpot => { # ONVIF::Device::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::Device::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Device::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Device/Types/VideoAnalyticsStream.pm b/onvif/proxy/lib/ONVIF/Device/Types/VideoAnalyticsStream.pm index dc5ccb55a..e19d3822b 100644 --- a/onvif/proxy/lib/ONVIF/Device/Types/VideoAnalyticsStream.pm +++ b/onvif/proxy/lib/ONVIF/Device/Types/VideoAnalyticsStream.pm @@ -98,7 +98,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Device::Types::VideoAnalyticsStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... Frame => { # ONVIF::Device::Types::Frame PTZStatus => { # ONVIF::Device::Types::PTZStatus Position => { # ONVIF::Device::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Media/Elements/GetOSDOptionsResponse.pm b/onvif/proxy/lib/ONVIF/Media/Elements/GetOSDOptionsResponse.pm index 0a2be2bfa..fa3b7ca9d 100644 --- a/onvif/proxy/lib/ONVIF/Media/Elements/GetOSDOptionsResponse.pm +++ b/onvif/proxy/lib/ONVIF/Media/Elements/GetOSDOptionsResponse.pm @@ -125,7 +125,7 @@ Constructor. The following data structure may be passed to new(): FontColor => { # ONVIF::Media::Types::OSDColorOptions Color => { # ONVIF::Media::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Media::Types::ColorspaceRange X => { # ONVIF::Media::Types::FloatRange @@ -153,7 +153,7 @@ Constructor. The following data structure may be passed to new(): BackgroundColor => { # ONVIF::Media::Types::OSDColorOptions Color => { # ONVIF::Media::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Media::Types::ColorspaceRange X => { # ONVIF::Media::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Media/Elements/MetadataStream.pm b/onvif/proxy/lib/ONVIF/Media/Elements/MetadataStream.pm index d44f6fe89..26a53e88a 100644 --- a/onvif/proxy/lib/ONVIF/Media/Elements/MetadataStream.pm +++ b/onvif/proxy/lib/ONVIF/Media/Elements/MetadataStream.pm @@ -49,10 +49,10 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Media::Types::MetadataStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... VideoAnalytics => { # ONVIF::Media::Types::VideoAnalyticsStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... Frame => { # ONVIF::Media::Types::Frame PTZStatus => { # ONVIF::Media::Types::PTZStatus Position => { # ONVIF::Media::Types::PTZVector @@ -155,7 +155,7 @@ Constructor. The following data structure may be passed to new(): }, PTZ => { # ONVIF::Media::Types::PTZStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PTZStatus => { # ONVIF::Media::Types::PTZStatus Position => { # ONVIF::Media::Types::PTZVector PanTilt => , diff --git a/onvif/proxy/lib/ONVIF/Media/Interfaces/Media/MediaPort.pm b/onvif/proxy/lib/ONVIF/Media/Interfaces/Media/MediaPort.pm index e7351e5f1..ecb985b30 100644 --- a/onvif/proxy/lib/ONVIF/Media/Interfaces/Media/MediaPort.pm +++ b/onvif/proxy/lib/ONVIF/Media/Interfaces/Media/MediaPort.pm @@ -2127,7 +2127,7 @@ of the corresponding class can be passed instead of the marked hash ref. You may pass any combination of objects, hash and list refs to these methods, as long as you meet the structure. -List items (i.e. multiple occurences) are not displayed in the synopsis. +List items (i.e. multiple occurrences) are not displayed in the synopsis. You may generally pass a list ref of hash refs (or objects) instead of a hash ref - this may result in invalid XML if used improperly, though. Note that SOAP::WSDL always expects list references at maximum depth position. diff --git a/onvif/proxy/lib/ONVIF/Media/Types/ColorOptions.pm b/onvif/proxy/lib/ONVIF/Media/Types/ColorOptions.pm index 138cffec9..da35e97e4 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/ColorOptions.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/ColorOptions.pm @@ -98,7 +98,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Media::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Media::Types::ColorspaceRange X => { # ONVIF::Media::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Media/Types/ConfigurationEntity.pm b/onvif/proxy/lib/ONVIF/Media/Types/ConfigurationEntity.pm index 937f568c5..594a0913b 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/ConfigurationEntity.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/ConfigurationEntity.pm @@ -139,7 +139,7 @@ get_/set_ methods: =item * token - Token that uniquely refernces this configuration. Length up to 64 characters. + Token that uniquely references this configuration. Length up to 64 characters. diff --git a/onvif/proxy/lib/ONVIF/Media/Types/MetadataStream.pm b/onvif/proxy/lib/ONVIF/Media/Types/MetadataStream.pm index 8cea2dd99..983f5f72e 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/MetadataStream.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/MetadataStream.pm @@ -114,10 +114,10 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Media::Types::MetadataStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... VideoAnalytics => { # ONVIF::Media::Types::VideoAnalyticsStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... Frame => { # ONVIF::Media::Types::Frame PTZStatus => { # ONVIF::Media::Types::PTZStatus Position => { # ONVIF::Media::Types::PTZVector @@ -220,7 +220,7 @@ Constructor. The following data structure may be passed to new(): }, PTZ => { # ONVIF::Media::Types::PTZStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PTZStatus => { # ONVIF::Media::Types::PTZStatus Position => { # ONVIF::Media::Types::PTZVector PanTilt => , diff --git a/onvif/proxy/lib/ONVIF/Media/Types/OSDColorOptions.pm b/onvif/proxy/lib/ONVIF/Media/Types/OSDColorOptions.pm index d356f24cc..7f459a809 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/OSDColorOptions.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/OSDColorOptions.pm @@ -107,7 +107,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Media::Types::OSDColorOptions Color => { # ONVIF::Media::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Media::Types::ColorspaceRange X => { # ONVIF::Media::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Media/Types/OSDConfigurationOptions.pm b/onvif/proxy/lib/ONVIF/Media/Types/OSDConfigurationOptions.pm index 53a2b24a9..30056dcda 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/OSDConfigurationOptions.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/OSDConfigurationOptions.pm @@ -143,7 +143,7 @@ Constructor. The following data structure may be passed to new(): FontColor => { # ONVIF::Media::Types::OSDColorOptions Color => { # ONVIF::Media::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Media::Types::ColorspaceRange X => { # ONVIF::Media::Types::FloatRange @@ -171,7 +171,7 @@ Constructor. The following data structure may be passed to new(): BackgroundColor => { # ONVIF::Media::Types::OSDColorOptions Color => { # ONVIF::Media::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Media::Types::ColorspaceRange X => { # ONVIF::Media::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Media/Types/OSDTextOptions.pm b/onvif/proxy/lib/ONVIF/Media/Types/OSDTextOptions.pm index a66451874..829999055 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/OSDTextOptions.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/OSDTextOptions.pm @@ -147,7 +147,7 @@ Constructor. The following data structure may be passed to new(): FontColor => { # ONVIF::Media::Types::OSDColorOptions Color => { # ONVIF::Media::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Media::Types::ColorspaceRange X => { # ONVIF::Media::Types::FloatRange @@ -175,7 +175,7 @@ Constructor. The following data structure may be passed to new(): BackgroundColor => { # ONVIF::Media::Types::OSDColorOptions Color => { # ONVIF::Media::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::Media::Types::ColorspaceRange X => { # ONVIF::Media::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/Media/Types/PTZPresetTourPresetDetail.pm b/onvif/proxy/lib/ONVIF/Media/Types/PTZPresetTourPresetDetail.pm index 91763bb36..db9fa8d66 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/PTZPresetTourPresetDetail.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/PTZPresetTourPresetDetail.pm @@ -114,7 +114,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Media::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Media::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Media/Types/PTZPresetTourSpot.pm b/onvif/proxy/lib/ONVIF/Media/Types/PTZPresetTourSpot.pm index ce2176c8a..63e7da894 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/PTZPresetTourSpot.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/PTZPresetTourSpot.pm @@ -115,7 +115,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Media::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::Media::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Media::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Media/Types/PTZPresetTourStatus.pm b/onvif/proxy/lib/ONVIF/Media/Types/PTZPresetTourStatus.pm index f25a5ec1a..8b36c2f84 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/PTZPresetTourStatus.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/PTZPresetTourStatus.pm @@ -109,7 +109,7 @@ Constructor. The following data structure may be passed to new(): CurrentTourSpot => { # ONVIF::Media::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::Media::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Media::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Media/Types/PTZStream.pm b/onvif/proxy/lib/ONVIF/Media/Types/PTZStream.pm index bbbc55652..2386d28cb 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/PTZStream.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/PTZStream.pm @@ -98,7 +98,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Media::Types::PTZStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PTZStatus => { # ONVIF::Media::Types::PTZStatus Position => { # ONVIF::Media::Types::PTZVector PanTilt => , diff --git a/onvif/proxy/lib/ONVIF/Media/Types/PresetTour.pm b/onvif/proxy/lib/ONVIF/Media/Types/PresetTour.pm index 6cb9d4a7f..ed66ee1fa 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/PresetTour.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/PresetTour.pm @@ -155,7 +155,7 @@ Constructor. The following data structure may be passed to new(): CurrentTourSpot => { # ONVIF::Media::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::Media::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Media::Types::PTZVector @@ -187,7 +187,7 @@ Constructor. The following data structure may be passed to new(): TourSpot => { # ONVIF::Media::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::Media::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::Media::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/Media/Types/VideoAnalyticsStream.pm b/onvif/proxy/lib/ONVIF/Media/Types/VideoAnalyticsStream.pm index c037814ad..20a488c86 100644 --- a/onvif/proxy/lib/ONVIF/Media/Types/VideoAnalyticsStream.pm +++ b/onvif/proxy/lib/ONVIF/Media/Types/VideoAnalyticsStream.pm @@ -98,7 +98,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::Media::Types::VideoAnalyticsStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... Frame => { # ONVIF::Media::Types::Frame PTZStatus => { # ONVIF::Media::Types::PTZStatus Position => { # ONVIF::Media::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/PTZ/Elements/GetPresetTourResponse.pm b/onvif/proxy/lib/ONVIF/PTZ/Elements/GetPresetTourResponse.pm index 65395f766..7b5bc8dc8 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Elements/GetPresetTourResponse.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Elements/GetPresetTourResponse.pm @@ -117,7 +117,7 @@ Constructor. The following data structure may be passed to new(): CurrentTourSpot => { # ONVIF::PTZ::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector @@ -149,7 +149,7 @@ Constructor. The following data structure may be passed to new(): TourSpot => { # ONVIF::PTZ::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/PTZ/Elements/GetPresetToursResponse.pm b/onvif/proxy/lib/ONVIF/PTZ/Elements/GetPresetToursResponse.pm index 5c16a06cb..d8f8ce639 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Elements/GetPresetToursResponse.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Elements/GetPresetToursResponse.pm @@ -117,7 +117,7 @@ Constructor. The following data structure may be passed to new(): CurrentTourSpot => { # ONVIF::PTZ::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector @@ -149,7 +149,7 @@ Constructor. The following data structure may be passed to new(): TourSpot => { # ONVIF::PTZ::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/PTZ/Elements/MetadataStream.pm b/onvif/proxy/lib/ONVIF/PTZ/Elements/MetadataStream.pm index f355909a6..c09224497 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Elements/MetadataStream.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Elements/MetadataStream.pm @@ -49,10 +49,10 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::PTZ::Types::MetadataStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... VideoAnalytics => { # ONVIF::PTZ::Types::VideoAnalyticsStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... Frame => { # ONVIF::PTZ::Types::Frame PTZStatus => { # ONVIF::PTZ::Types::PTZStatus Position => { # ONVIF::PTZ::Types::PTZVector @@ -155,7 +155,7 @@ Constructor. The following data structure may be passed to new(): }, PTZ => { # ONVIF::PTZ::Types::PTZStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PTZStatus => { # ONVIF::PTZ::Types::PTZStatus Position => { # ONVIF::PTZ::Types::PTZVector PanTilt => , diff --git a/onvif/proxy/lib/ONVIF/PTZ/Elements/ModifyPresetTour.pm b/onvif/proxy/lib/ONVIF/PTZ/Elements/ModifyPresetTour.pm index 666c61a12..19eb96195 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Elements/ModifyPresetTour.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Elements/ModifyPresetTour.pm @@ -131,7 +131,7 @@ Constructor. The following data structure may be passed to new(): CurrentTourSpot => { # ONVIF::PTZ::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector @@ -163,7 +163,7 @@ Constructor. The following data structure may be passed to new(): TourSpot => { # ONVIF::PTZ::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/PTZ/Interfaces/PTZ/PTZPort.pm b/onvif/proxy/lib/ONVIF/PTZ/Interfaces/PTZ/PTZPort.pm index 71915f701..8d42d439e 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Interfaces/PTZ/PTZPort.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Interfaces/PTZ/PTZPort.pm @@ -775,7 +775,7 @@ of the corresponding class can be passed instead of the marked hash ref. You may pass any combination of objects, hash and list refs to these methods, as long as you meet the structure. -List items (i.e. multiple occurences) are not displayed in the synopsis. +List items (i.e. multiple occurrences) are not displayed in the synopsis. You may generally pass a list ref of hash refs (or objects) instead of a hash ref - this may result in invalid XML if used improperly, though. Note that SOAP::WSDL always expects list references at maximum depth position. @@ -1136,7 +1136,7 @@ Returns a L { # ONVIF::PTZ::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector @@ -1168,7 +1168,7 @@ Returns a L { # ONVIF::PTZ::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/ColorOptions.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/ColorOptions.pm index a1c6097c4..5a3354adb 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/ColorOptions.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/ColorOptions.pm @@ -98,7 +98,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::PTZ::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::PTZ::Types::ColorspaceRange X => { # ONVIF::PTZ::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/ConfigurationEntity.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/ConfigurationEntity.pm index c23095ead..d619e80a3 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/ConfigurationEntity.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/ConfigurationEntity.pm @@ -139,7 +139,7 @@ get_/set_ methods: =item * token - Token that uniquely refernces this configuration. Length up to 64 characters. + Token that uniquely references this configuration. Length up to 64 characters. diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/MetadataStream.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/MetadataStream.pm index cdc6fa421..50f88d36b 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/MetadataStream.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/MetadataStream.pm @@ -114,10 +114,10 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::PTZ::Types::MetadataStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... VideoAnalytics => { # ONVIF::PTZ::Types::VideoAnalyticsStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... Frame => { # ONVIF::PTZ::Types::Frame PTZStatus => { # ONVIF::PTZ::Types::PTZStatus Position => { # ONVIF::PTZ::Types::PTZVector @@ -220,7 +220,7 @@ Constructor. The following data structure may be passed to new(): }, PTZ => { # ONVIF::PTZ::Types::PTZStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PTZStatus => { # ONVIF::PTZ::Types::PTZStatus Position => { # ONVIF::PTZ::Types::PTZVector PanTilt => , diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/OSDColorOptions.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/OSDColorOptions.pm index d73700380..eedf14f1a 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/OSDColorOptions.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/OSDColorOptions.pm @@ -107,7 +107,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::PTZ::Types::OSDColorOptions Color => { # ONVIF::PTZ::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::PTZ::Types::ColorspaceRange X => { # ONVIF::PTZ::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/OSDConfigurationOptions.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/OSDConfigurationOptions.pm index 80d57ab5c..a0627a1bd 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/OSDConfigurationOptions.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/OSDConfigurationOptions.pm @@ -143,7 +143,7 @@ Constructor. The following data structure may be passed to new(): FontColor => { # ONVIF::PTZ::Types::OSDColorOptions Color => { # ONVIF::PTZ::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::PTZ::Types::ColorspaceRange X => { # ONVIF::PTZ::Types::FloatRange @@ -171,7 +171,7 @@ Constructor. The following data structure may be passed to new(): BackgroundColor => { # ONVIF::PTZ::Types::OSDColorOptions Color => { # ONVIF::PTZ::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::PTZ::Types::ColorspaceRange X => { # ONVIF::PTZ::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/OSDTextOptions.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/OSDTextOptions.pm index 82660279c..3f8a7c66f 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/OSDTextOptions.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/OSDTextOptions.pm @@ -147,7 +147,7 @@ Constructor. The following data structure may be passed to new(): FontColor => { # ONVIF::PTZ::Types::OSDColorOptions Color => { # ONVIF::PTZ::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::PTZ::Types::ColorspaceRange X => { # ONVIF::PTZ::Types::FloatRange @@ -175,7 +175,7 @@ Constructor. The following data structure may be passed to new(): BackgroundColor => { # ONVIF::PTZ::Types::OSDColorOptions Color => { # ONVIF::PTZ::Types::ColorOptions # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... ColorList => , ColorspaceRange => { # ONVIF::PTZ::Types::ColorspaceRange X => { # ONVIF::PTZ::Types::FloatRange diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/PTZPresetTourPresetDetail.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/PTZPresetTourPresetDetail.pm index a29609cbc..052575b16 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/PTZPresetTourPresetDetail.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/PTZPresetTourPresetDetail.pm @@ -114,7 +114,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/PTZPresetTourSpot.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/PTZPresetTourSpot.pm index 5e5c8d01e..c88c3f660 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/PTZPresetTourSpot.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/PTZPresetTourSpot.pm @@ -115,7 +115,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::PTZ::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/PTZPresetTourStatus.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/PTZPresetTourStatus.pm index 99e347247..d4292efa9 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/PTZPresetTourStatus.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/PTZPresetTourStatus.pm @@ -109,7 +109,7 @@ Constructor. The following data structure may be passed to new(): CurrentTourSpot => { # ONVIF::PTZ::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/PTZStream.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/PTZStream.pm index 0296e95b3..eca3b5557 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/PTZStream.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/PTZStream.pm @@ -98,7 +98,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::PTZ::Types::PTZStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PTZStatus => { # ONVIF::PTZ::Types::PTZStatus Position => { # ONVIF::PTZ::Types::PTZVector PanTilt => , diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/PresetTour.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/PresetTour.pm index 8b61618b2..a7db2aaed 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/PresetTour.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/PresetTour.pm @@ -155,7 +155,7 @@ Constructor. The following data structure may be passed to new(): CurrentTourSpot => { # ONVIF::PTZ::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector @@ -187,7 +187,7 @@ Constructor. The following data structure may be passed to new(): TourSpot => { # ONVIF::PTZ::Types::PTZPresetTourSpot PresetDetail => { # ONVIF::PTZ::Types::PTZPresetTourPresetDetail # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... PresetToken => $some_value, # ReferenceToken Home => $some_value, # boolean PTZPosition => { # ONVIF::PTZ::Types::PTZVector diff --git a/onvif/proxy/lib/ONVIF/PTZ/Types/VideoAnalyticsStream.pm b/onvif/proxy/lib/ONVIF/PTZ/Types/VideoAnalyticsStream.pm index d17e60a45..ff66bcf6e 100644 --- a/onvif/proxy/lib/ONVIF/PTZ/Types/VideoAnalyticsStream.pm +++ b/onvif/proxy/lib/ONVIF/PTZ/Types/VideoAnalyticsStream.pm @@ -98,7 +98,7 @@ Constructor. The following data structure may be passed to new(): { # ONVIF::PTZ::Types::VideoAnalyticsStream # One of the following elements. - # No occurance checks yet, so be sure to pass just one... + # No occurrence checks yet, so be sure to pass just one... Frame => { # ONVIF::PTZ::Types::Frame PTZStatus => { # ONVIF::PTZ::Types::PTZStatus Position => { # ONVIF::PTZ::Types::PTZVector diff --git a/onvif/proxy/lib/WSDiscovery10/Elements/Probe.pm b/onvif/proxy/lib/WSDiscovery10/Elements/Probe.pm index e07ec2510..33e3c5f96 100644 --- a/onvif/proxy/lib/WSDiscovery10/Elements/Probe.pm +++ b/onvif/proxy/lib/WSDiscovery10/Elements/Probe.pm @@ -15,6 +15,7 @@ __PACKAGE__->__set_ref(); use base qw( SOAP::WSDL::XSD::Typelib::Element WSDiscovery10::Types::ProbeType + WSDiscovery10::Types::ProbeMatchesType ); } diff --git a/onvif/proxy/lib/WSDiscovery10/Interfaces/WSDiscovery/WSDiscoveryPort.pm b/onvif/proxy/lib/WSDiscovery10/Interfaces/WSDiscovery/WSDiscoveryPort.pm index f1b6446a5..b8b6a735e 100644 --- a/onvif/proxy/lib/WSDiscovery10/Interfaces/WSDiscovery/WSDiscoveryPort.pm +++ b/onvif/proxy/lib/WSDiscovery10/Interfaces/WSDiscovery/WSDiscoveryPort.pm @@ -104,7 +104,7 @@ of the corresponding class can be passed instead of the marked hash ref. You may pass any combination of objects, hash and list refs to these methods, as long as you meet the structure. -List items (i.e. multiple occurences) are not displayed in the synopsis. +List items (i.e. multiple occurrences) are not displayed in the synopsis. You may generally pass a list ref of hash refs (or objects) instead of a hash ref - this may result in invalid XML if used improperly, though. Note that SOAP::WSDL always expects list references at maximum depth position. diff --git a/onvif/proxy/lib/WSDiscovery10/Types/ProbeType.pm b/onvif/proxy/lib/WSDiscovery10/Types/ProbeType.pm index 8d7e9e7ca..4e3452db2 100644 --- a/onvif/proxy/lib/WSDiscovery10/Types/ProbeType.pm +++ b/onvif/proxy/lib/WSDiscovery10/Types/ProbeType.pm @@ -89,10 +89,11 @@ sub serialize() my $ident = ${ $_[0] }; my $option_ref = $_[1]; my $attr_str = ""; + my %attr_hash = %{$Attribs_of{$ident}}; - foreach my $attr (keys %{$Attribs_of{$ident}}) + foreach my $attr (keys %attr_hash) { - my $value = %{$Attribs_of{$ident}}{$attr}; + my $value = $attr_hash{$attr}; $attr_str .= " $attr=\"$value\""; } diff --git a/onvif/proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/NotificationProducerPort.pm b/onvif/proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/NotificationProducerPort.pm index c86b26f9f..ee55035ca 100644 --- a/onvif/proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/NotificationProducerPort.pm +++ b/onvif/proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/NotificationProducerPort.pm @@ -127,7 +127,7 @@ of the corresponding class can be passed instead of the marked hash ref. You may pass any combination of objects, hash and list refs to these methods, as long as you meet the structure. -List items (i.e. multiple occurences) are not displayed in the synopsis. +List items (i.e. multiple occurrences) are not displayed in the synopsis. You may generally pass a list ref of hash refs (or objects) instead of a hash ref - this may result in invalid XML if used improperly, though. Note that SOAP::WSDL always expects list references at maximum depth position. diff --git a/onvif/proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/SubscriptionManagerPort.pm b/onvif/proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/SubscriptionManagerPort.pm index a10978083..807c2ace2 100644 --- a/onvif/proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/SubscriptionManagerPort.pm +++ b/onvif/proxy/lib/WSNotification/Interfaces/WSBaseNotificationSender/SubscriptionManagerPort.pm @@ -127,7 +127,7 @@ of the corresponding class can be passed instead of the marked hash ref. You may pass any combination of objects, hash and list refs to these methods, as long as you meet the structure. -List items (i.e. multiple occurences) are not displayed in the synopsis. +List items (i.e. multiple occurrences) are not displayed in the synopsis. You may generally pass a list ref of hash refs (or objects) instead of a hash ref - this may result in invalid XML if used improperly, though. Note that SOAP::WSDL always expects list references at maximum depth position. diff --git a/onvif/scripts/zmonvif-probe.pl b/onvif/scripts/zmonvif-probe.pl index 10aa26a08..c66403e56 100755 --- a/onvif/scripts/zmonvif-probe.pl +++ b/onvif/scripts/zmonvif-probe.pl @@ -1,4 +1,5 @@ #!/usr/bin/perl -w +use strict; # # ========================================================================== # @@ -17,7 +18,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -41,6 +42,7 @@ require WSDiscovery::TransportUDP; # Globals my $verbose = 0; +my $soap_version = undef; my $client; # ========================================================================= @@ -171,61 +173,66 @@ sub discover my %services; my $uuid_gen = Data::UUID->new(); + + if ( ( ! $soap_version ) or ( $soap_version eq '1.1' ) ) { - if($verbose) { - print "Probing for SOAP 1.1\n" - } - my $svc_discover = WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort->new({ + if($verbose) { + print "Probing for SOAP 1.1\n" + } + my $svc_discover = WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort->new({ # no_dispatch => '1', - }); - $svc_discover->set_soap_version('1.1'); + }); + $svc_discover->set_soap_version('1.1'); - my $uuid = $uuid_gen->create_str(); + my $uuid = $uuid_gen->create_str(); - my $result = $svc_discover->ProbeOp( - { # WSDiscovery::Types::ProbeType - Types => 'http://www.onvif.org/ver10/network/wsdl:NetworkVideoTransmitter http://www.onvif.org/ver10/device/wsdl:Device', # QNameListType - Scopes => { value => '' }, - }, - WSDiscovery10::Elements::Header->new({ - Action => { value => 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' }, - MessageID => { value => "urn:uuid:$uuid" }, - To => { value => 'urn:schemas-xmlsoap-org:ws:2005:04:discovery' }, - }) - ); -# print $result . "\n"; + my $result = $svc_discover->ProbeOp( + { # WSDiscovery::Types::ProbeType + Types => 'http://www.onvif.org/ver10/network/wsdl:NetworkVideoTransmitter http://www.onvif.org/ver10/device/wsdl:Device', # QNameListType + Scopes => { value => '' }, + }, + WSDiscovery10::Elements::Header->new({ + Action => { value => 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' }, + MessageID => { value => "urn:uuid:$uuid" }, + To => { value => 'urn:schemas-xmlsoap-org:ws:2005:04:discovery' }, + }) + ); + print $result . "\n" if $verbose; - interpret_messages($svc_discover, \%services, @responses); - @responses = (); + interpret_messages($svc_discover, \%services, @responses); + @responses = (); + } # end if doing soap 1.1 - if($verbose) { - print "Probing for SOAP 1.2\n" - } - $svc_discover = WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort->new({ + if ( ( ! $soap_version ) or ( $soap_version eq '1.2' ) ) { + if($verbose) { + print "Probing for SOAP 1.2\n" + } + my $svc_discover = WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort->new({ # no_dispatch => '1', - }); - $svc_discover->set_soap_version('1.2'); + }); + $svc_discover->set_soap_version('1.2'); - # copies of the same Probe message must have the same MessageID. - # This is not a copy. So we generate a new uuid. - $uuid = $uuid_gen->create_str(); +# copies of the same Probe message must have the same MessageID. +# This is not a copy. So we generate a new uuid. + my $uuid = $uuid_gen->create_str(); - $result = $svc_discover->ProbeOp( - { # WSDiscovery::Types::ProbeType - xmlattr => { 'xmlns:dn' => 'http://www.onvif.org/ver10/network/wsdl', - 'xmlns:tds' => 'http://www.onvif.org/ver10/device/wsdl', }, - Types => 'dn:NetworkVideoTransmitter tds:Device', # QNameListType - Scopes => { value => '' }, - }, - WSDiscovery10::Elements::Header->new({ - Action => { value => 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' }, - MessageID => { value => "urn:uuid:$uuid" }, - To => { value => 'urn:schemas-xmlsoap-org:ws:2005:04:discovery' }, - }) - ); -# print $result . "\n"; +# Everyone else, like the nodejs onvif code and odm only ask for NetworkVideoTransmitter + my $result = $svc_discover->ProbeOp( + { # WSDiscovery::Types::ProbeType + xmlattr => { 'xmlns:dn' => 'http://www.onvif.org/ver10/network/wsdl', }, + Types => 'dn:NetworkVideoTransmitter', # QNameListType + Scopes => { value => '' }, + }, + WSDiscovery10::Elements::Header->new({ + Action => { value => 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' }, + MessageID => { value => "urn:uuid:$uuid" }, + To => { value => 'urn:schemas-xmlsoap-org:ws:2005:04:discovery' }, + }) + ); + print $result . "\n" if $verbose; + interpret_messages($svc_discover, \%services, @responses); + } # end if doing soap 1.2 - interpret_messages($svc_discover, \%services, @responses); } @@ -321,7 +328,7 @@ my $OPTIONS = "v"; sub HELP_MESSAGE { my ($fh, $pkg, $ver, $opts) = @_; - print $fh "Usage: " . __FILE__ . " [-v] probe \n"; + print $fh "Usage: " . __FILE__ . " [-v] probe \n"; print $fh " " . __FILE__ . " [-v] \n"; print $fh <new( { 'url_svc_device' => $url_svc_device, diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index 37f93fd12..5846a39d2 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -26,9 +26,10 @@ configure_file(zm.in "${CMAKE_CURRENT_BINARY_DIR}/zm" @ONLY) #configure_file(zmeventdump.in zmeventdump @ONLY) # Generate man files for the perl scripts destined for the bin folder -file(GLOB perlscripts RELATIVE "${CMAKE_CURRENT_BINARY_DIR}" "*.pl") +file(GLOB perlscripts "*.pl") FOREACH(PERLSCRIPT ${perlscripts}) - POD2MAN(${CMAKE_CURRENT_SOURCE_DIR}/${PERLSCRIPT} zoneminder-${PERLSCRIPT} 8) + get_filename_component(PERLSCRIPTNAME ${PERLSCRIPT} NAME) + POD2MAN(${PERLSCRIPT} zoneminder-${PERLSCRIPTNAME} 8) ENDFOREACH(PERLSCRIPT ${perlscripts}) # Install the perl scripts diff --git a/scripts/ZoneMinder/lib/ZoneMinder.pm b/scripts/ZoneMinder/lib/ZoneMinder.pm index 87a6b14b7..d6ab646ae 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Base.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Base.pm.in index 70140ea66..d80add206 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Base.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Base.pm.in @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -32,7 +32,7 @@ require Exporter; our @ISA = qw(Exporter); -use constant ZM_VERSION => "@VERSION@"; +use constant ZM_VERSION => '@VERSION@'; # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. @@ -47,7 +47,7 @@ our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = qw(); -our $VERSION = "@VERSION@"; +our $VERSION = '@VERSION@'; 1; __END__ @@ -58,7 +58,7 @@ ZoneMinder::Base - Base perl module for ZoneMinder =head1 SYNOPSIS - use ZoneMinder::Base; +use ZoneMinder::Base; =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index 1f21b7407..60cdce658 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -46,14 +46,14 @@ our @EXPORT_CONFIG = qw( %Config ); # Get populated by BEGIN our %EXPORT_TAGS = ( functions => [ qw( - zmConfigLoad - loadConfigFromDB - saveConfigToDB - ) ], + zmConfigLoad + loadConfigFromDB + saveConfigToDB + ) ], constants => [ qw( - ZM_PID - ) ] -); + ZM_PID + ) ] + ); push( @{$EXPORT_TAGS{config}}, @EXPORT_CONFIG ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; @@ -65,61 +65,98 @@ our $VERSION = $ZoneMinder::Base::VERSION; use constant ZM_PID => "@ZM_PID@"; # Path to the ZoneMinder run pid file use constant ZM_CONFIG => "@ZM_CONFIG@"; # Path to the ZoneMinder config file +use constant ZM_CONFIG_SUBDIR => "@ZM_CONFIG_SUBDIR@"; # Path to the ZoneMinder config subfolder use Carp; # Load the config from the database into the symbol table -BEGIN -{ - my $config_file = ZM_CONFIG; - open( my $CONFIG, "<", $config_file ) - or croak( "Can't open config file '$config_file': $!" ); - foreach my $str ( <$CONFIG> ) - { - next if ( $str =~ /^\s*$/ ); - next if ( $str =~ /^\s*#/ ); - my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*(.*?)\s*$/; - if ( ! $name ) { - print( STDERR "Warning, bad line in $config_file: $str\n" ); - next; - } # end if - $name =~ tr/a-z/A-Z/; - $Config{$name} = $value; - } - close( $CONFIG ); +BEGIN { - use DBI; - my $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .";host=".$Config{ZM_DB_HOST} - , $Config{ZM_DB_USER} - , $Config{ZM_DB_PASS} - ) or croak( "Can't connect to db" ); - my $sql = 'select * from Config'; - my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or croak( "Can't execute: ".$sth->errstr() ); - while( my $config = $sth->fetchrow_hashref() ) { - $Config{$config->{Name}} = $config->{Value}; + # Process name, value pairs from the main config file first + my $config_file = ZM_CONFIG; + process_configfile($config_file); + + # Search for user created config files. If one or more are found then + # update the Config hash with those values + if ( -d ZM_CONFIG_SUBDIR ) { + if ( -R ZM_CONFIG_SUBDIR ) { + foreach my $filename ( glob ZM_CONFIG_SUBDIR."/*.conf" ) { + process_configfile($filename); + } + } else { + print( STDERR "WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on ".ZM_CONFIG_SUBDIR.".\n" ); + } + } + + use DBI; + my $socket; + my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); + + if ( defined($portOrSocket) ) { + if ( $portOrSocket =~ /^\// ) { + $socket = ";mysql_socket=".$portOrSocket; + } else { + $socket = ";host=".$host.";port=".$portOrSocket; + } + } else { + $socket = ";host=".$Config{ZM_DB_HOST}; + } + my $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} + .$socket + , $Config{ZM_DB_USER} + , $Config{ZM_DB_PASS} + ) or croak( "Can't connect to db" ); + my $sql = 'select * from Config'; + my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or croak( "Can't execute: ".$sth->errstr() ); + while( my $config = $sth->fetchrow_hashref() ) { + $Config{$config->{Name}} = $config->{Value}; + } + $sth->finish(); +#$dbh->disconnect(); + if ( ! exists $Config{ZM_SERVER_ID} ) { + $Config{ZM_SERVER_ID} = undef; + $sth = $dbh->prepare_cached( 'SELECT * FROM Servers WHERE Name=?' ); + if ( $Config{ZM_SERVER_NAME} ) { + $res = $sth->execute( $Config{ZM_SERVER_NAME} ); + my $result = $sth->fetchrow_hashref(); + $Config{ZM_SERVER_ID} = $$result{Id}; + } elsif ( $Config{ZM_SERVER_HOST} ) { + $res = $sth->execute( $Config{ZM_SERVER_HOST} ); + my $result = $sth->fetchrow_hashref(); + $Config{ZM_SERVER_ID} = $$result{Id}; } $sth->finish(); - #$dbh->disconnect(); - if ( ! exists $Config{ZM_SERVER_ID} ) { - $Config{ZM_SERVER_ID} = undef; - $sth = $dbh->prepare_cached( 'SELECT * FROM Servers WHERE Name=?' ); - if ( $Config{ZM_SERVER_NAME} ) { - $res = $sth->execute( $Config{ZM_SERVER_NAME} ); - my $result = $sth->fetchrow_hashref(); - $Config{ZM_SERVER_ID} = $$result{Id}; - } elsif ( $Config{ZM_SERVER_HOST} ) { - $res = $sth->execute( $Config{ZM_SERVER_HOST} ); - my $result = $sth->fetchrow_hashref(); - $Config{ZM_SERVER_ID} = $$result{Id}; + } + + # This subroutine must be inside the BEGIN block + sub process_configfile { + my $config_file = shift; + + if ( -R $config_file ) { + open( my $CONFIG, "<", $config_file ) + or croak( "Can't open config file '$config_file': $!" ); + foreach my $str ( <$CONFIG> ) { + next if ( $str =~ /^\s*$/ ); + next if ( $str =~ /^\s*#/ ); + my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*(.*?)\s*$/; + if ( ! $name ) { + print( STDERR "Warning, bad line in $config_file: $str\n" ); + next; + } # end if + $name =~ tr/a-z/A-Z/; + $Config{$name} = $value; + } + close( $CONFIG ); + } else { + print( STDERR "WARNING: ZoneMinder configuration file found but is not readable. Check file permissions on $config_file\n" ); } - $sth->finish(); - } -} + } + +} # end BEGIN sub loadConfigFromDB { - print( "Loading config from DB\n" ); + print( "Loading config from DB" ); my $dbh = ZoneMinder::Database::zmDbConnect(); if ( !$dbh ) { print( "Error: unable to load options from database: $DBI::errstr\n" ); @@ -136,7 +173,7 @@ sub loadConfigFromDB { #print( "Name = '$name'\n" ); my $option = $options_hash{$name}; if ( !$option ) { - warn( "No option '$name' found, removing" ); + warn( "No option '$name' found, removing.\n" ); next; } #next if ( $option->{category} eq 'hidden' ); @@ -150,14 +187,14 @@ sub loadConfigFromDB { $option_count++;; } $sth->finish(); + print( " $option_count entries\n" ); return( $option_count ); -} +} # end sub loadConfigFromDB sub saveConfigToDB { - print( "Saving config to DB\n" ); + print( "Saving config to DB " . @options . " entries\n" ); my $dbh = ZoneMinder::Database::zmDbConnect(); - if ( !$dbh ) - { + if ( !$dbh ) { print( "Error: unable to save options to database: $DBI::errstr\n" ); return( 0 ); } @@ -175,41 +212,27 @@ sub saveConfigToDB { $sql = "replace into Config set Id = ?, Name = ?, Value = ?, Type = ?, DefaultValue = ?, Hint = ?, Pattern = ?, Format = ?, Prompt = ?, Help = ?, Category = ?, Readonly = ?, Requires = ?"; my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); - foreach my $option ( @options ) - { + foreach my $option ( @options ) { #next if ( $option->{category} eq 'hidden' ); #print( $option->{name}."\n" ) if ( !$option->{category} ); $option->{db_type} = $option->{type}->{db_type}; $option->{db_hint} = $option->{type}->{hint}; $option->{db_pattern} = $option->{type}->{pattern}; $option->{db_format} = $option->{type}->{format}; - if ( $option->{db_type} eq "boolean" ) - { - $option->{db_value} = ($option->{value} eq "yes") - ? "1" - : "0" - ; - } - else - { + if ( $option->{db_type} eq "boolean" ) { + $option->{db_value} = ($option->{value} eq "yes") ? "1" : "0"; + } else { $option->{db_value} = $option->{value}; } - if ( my $requires = $option->{requires} ) - { + if ( my $requires = $option->{requires} ) { $option->{db_requires} = join( ";", map { - my $value = $_->{value}; - $value = ($value eq "yes") - ? 1 - : 0 - if ( $options_hash{$_->{name}}->{db_type} eq "boolean" ) - ; ( "$_->{name}=$value" ) + my $value = $_->{value}; + $value = ($value eq "yes") ? 1 : 0 if ( $options_hash{$_->{name}}->{db_type} eq "boolean" ); + ( "$_->{name}=$value" ) } @$requires ); } - else - { - } my $res = $sth->execute( $option->{id}, $option->{name}, @@ -225,12 +248,12 @@ sub saveConfigToDB { $option->{readonly} ? 1 : 0, $option->{db_requires} ) or croak( "Can't execute: ".$sth->errstr() ); - } + } # end foreach option $sth->finish(); $dbh->do('UNLOCK TABLES'); $dbh->{AutoCommit} = $ac; -} +} # end sub saveConfigToDB 1; __END__ @@ -297,7 +320,7 @@ Copyright (C) 2001-2008 Philip Coombes This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, - at your option, any later version of Perl 5 you may have available. +at your option, any later version of Perl 5 you may have available. - =cut +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigAdmin.pm b/scripts/ZoneMinder/lib/ZoneMinder/ConfigAdmin.pm deleted file mode 100644 index f586448fb..000000000 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigAdmin.pm +++ /dev/null @@ -1,276 +0,0 @@ -# ========================================================================== -# -# ZoneMinder Config Admin Module, $Date$, $Revision$ -# Copyright (C) 2001-2008 Philip Coombes -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -# ========================================================================== -# -# This module contains the debug definitions and functions used by the rest -# of the ZoneMinder scripts -# -package ZoneMinder::ConfigAdmin; - -use 5.006; -use strict; -use warnings; - -require Exporter; -require ZoneMinder::Base; - -our @ISA = qw(Exporter ZoneMinder::Base); - -# Items to export into callers namespace by default. Note: do not export -# names by default without a very good reason. Use EXPORT_OK instead. -# Do not simply export all your public functions/methods/constants. - -# This allows declaration use ZoneMinder ':all'; -# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK -# will save memory. -our %EXPORT_TAGS = ( - 'functions' => [ qw( - loadConfigFromDB - saveConfigToDB - ) ] -); -push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; - -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'functions'} } ); - -our @EXPORT = qw(); - -our $VERSION = $ZoneMinder::Base::VERSION; - -# ========================================================================== -# -# Configuration Administration -# -# ========================================================================== - -use ZoneMinder::Config qw(:all); -use ZoneMinder::ConfigData qw(:all); - -use Carp; - -sub loadConfigFromDB -{ - print( "Loading config from DB\n" ); - my $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .";host=".$Config{ZM_DB_HOST} - ,$Config{ZM_DB_USER} - ,$Config{ZM_DB_PASS} - ); - - if ( !$dbh ) - { - print( "Error: unable to load options from database: $DBI::errstr\n" ); - return( 0 ); - } - my $sql = "select * from Config"; - my $sth = $dbh->prepare_cached( $sql ) - or croak( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() - or croak( "Can't execute: ".$sth->errstr() ); - my $option_count = 0; - while( my $config = $sth->fetchrow_hashref() ) - { - my ( $name, $value ) = ( $config->{Name}, $config->{Value} ); - #print( "Name = '$name'\n" ); - my $option = $options_hash{$name}; - if ( !$option ) - { - warn( "No option '$name' found, removing" ); - next; - } - #next if ( $option->{category} eq 'hidden' ); - if ( defined($value) ) - { - if ( $option->{type} == $types{boolean} ) - { - $option->{value} = $value?"yes":"no"; - } - else - { - $option->{value} = $value; - } - } - $option_count++;; - } - $sth->finish(); - $dbh->disconnect(); - return( $option_count ); -} - -sub saveConfigToDB -{ - print( "Saving config to DB\n" ); - my $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .";host=".$Config{ZM_DB_HOST} - ,$Config{ZM_DB_USER} - ,$Config{ZM_DB_PASS} - ); - - if ( !$dbh ) - { - print( "Error: unable to save options to database: $DBI::errstr\n" ); - return( 0 ); - } - - my $ac = $dbh->{AutoCommit}; - $dbh->{AutoCommit} = 0; - - $dbh->do('LOCK TABLE Config WRITE') - or croak( "Can't lock Config table: " . $dbh->errstr() ); - - my $sql = "delete from Config"; - my $res = $dbh->do( $sql ) - or croak( "Can't do '$sql': ".$dbh->errstr() ); - - $sql = "replace into Config set Id = ?, Name = ?, Value = ?, Type = ?, DefaultValue = ?, Hint = ?, Pattern = ?, Format = ?, Prompt = ?, Help = ?, Category = ?, Readonly = ?, Requires = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or croak( "Can't prepare '$sql': ".$dbh->errstr() ); - foreach my $option ( @options ) - { - #next if ( $option->{category} eq 'hidden' ); - #print( $option->{name}."\n" ) if ( !$option->{category} ); - $option->{db_type} = $option->{type}->{db_type}; - $option->{db_hint} = $option->{type}->{hint}; - $option->{db_pattern} = $option->{type}->{pattern}; - $option->{db_format} = $option->{type}->{format}; - if ( $option->{db_type} eq "boolean" ) - { - $option->{db_value} = ($option->{value} eq "yes") - ? "1" - : "0" - ; - } - else - { - $option->{db_value} = $option->{value}; - } - if ( my $requires = $option->{requires} ) - { - $option->{db_requires} = join( ";", - map { - my $value = $_->{value}; - $value = ($value eq "yes") - ? 1 - : 0 - if ( $options_hash{$_->{name}}->{db_type} eq "boolean" ) - ; ( "$_->{name}=$value" ) - } @$requires - ); - } - else - { - } - my $res = $sth->execute( - $option->{id}, - $option->{name}, - $option->{db_value}, - $option->{db_type}, - $option->{default}, - $option->{db_hint}, - $option->{db_pattern}, - $option->{db_format}, - $option->{description}, - $option->{help}, - $option->{category}, - $option->{readonly} ? 1 : 0, - $option->{db_requires} - ) or croak( "Can't execute: ".$sth->errstr() ); - } - $sth->finish(); - - $dbh->do('UNLOCK TABLES'); - $dbh->{AutoCommit} = $ac; - - $dbh->disconnect(); -} - -1; -__END__ - -=head1 NAME - -ZoneMinder::ConfigAdmin - ZoneMinder Configuration Administration module - -=head1 SYNOPSIS - - use ZoneMinder::ConfigAdmin; - use ZoneMinder::ConfigAdmin qw(:all); - - loadConfigFromDB(); - saveConfigToDB(); - -=head1 DESCRIPTION - -The ZoneMinder:ConfigAdmin module contains the master definition of the -ZoneMinder configuration options as well as helper methods. This module is -intended for specialist confguration management and would not normally be -used by end users. - -The configuration held in this module, which was previously in zmconfig.pl, -includes the name, default value, description, help text, type and category -for each option, as well as a number of additional fields in a small number -of cases. - -=head1 METHODS - -=over 4 - -=item loadConfigFromDB (); - -Loads existing configuration from the database (if any) and merges it with -the definitions held in this module. This results in the merging of any new -configuration and the removal of any deprecated configuration while -preserving the existing values of every else. - -=item saveConfigToDB (); - -Saves configuration held in memory to the database. The act of loading and -saving configuration is a convenient way to ensure that the configuration -held in the database corresponds with the most recent definitions and that -all components are using the same set of configuration. - -=back - -=head2 EXPORT - -None by default. -The :data tag will export the various configuration data structures -The :functions tag will export the helper functions. -The :all tag will export all above symbols. - - -=head1 SEE ALSO - -http://www.zoneminder.com - -=head1 AUTHOR - -Philip Coombes, Ephilip.coombes@zoneminder.comE - -=head1 COPYRIGHT AND LICENSE - -Copyright (C) 2001-2008 Philip Coombes - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself, either Perl version 5.8.3 or, -at your option, any later version of Perl 5 you may have available. - - -=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 610d2432d..29efe85ca 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -41,15 +41,15 @@ our @ISA = qw(Exporter ZoneMinder::Base); # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( - 'data' => [ qw( - %types - @options - %options_hash - ) ] -); + data => [ qw( + %types + @options + %options_hash + ) ] + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'data'} } ); +our @EXPORT_OK = ( @{ $EXPORT_TAGS{data} } ); our @EXPORT = qw(); @@ -65,3839 +65,3846 @@ use Carp; our $configInitialised = 0; -sub INIT -{ - initialiseConfig(); +sub INIT { + initialiseConfig(); } # Types -our %types = -( - string => { - db_type =>"string", - hint =>"string", - pattern =>qr|^(.+)$|, - format =>q( $1 ) - }, - alphanum => { - db_type =>"string", - hint =>"alphanumeric", - pattern =>qr|^([a-zA-Z0-9-_]+)$|, - format =>q( $1 ) - }, - text => { - db_type =>"text", - hint =>"free text", - pattern =>qr|^(.+)$|, - format =>q( $1 ) - }, - boolean => { - db_type =>"boolean", - hint =>"yes|no", - pattern =>qr|^([yn])|i, - check =>q( $1 ), - format =>q( ($1 =~ /^y/) - ? "yes" - : "no" - ) - }, - integer => { - db_type =>"integer", - hint =>"integer", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - decimal => { - db_type =>"decimal", - hint =>"decimal", - pattern =>qr|^(\d+(?:\.\d+)?)$|, - format =>q( $1 ) - }, - hexadecimal => { - db_type =>"hexadecimal", - hint =>"hexadecimal", - pattern =>qr|^(?:0x)?([0-9a-f]{1,8})$|, - format =>q( "0x".$1 ) - }, - tristate => { - db_type =>"string", - hint =>"auto|yes|no", - pattern =>qr|^([ayn])|i, check=>q( $1 ), - format =>q( ($1 =~ /^y/) - ? "yes" - : ($1 =~ /^n/ ? "no" : "auto" ) - ) - }, - abs_path => { - db_type =>"string", - hint =>"/absolute/path/to/somewhere", - pattern =>qr|^((?:/[^/]*)+?)/?$|, - format =>q( $1 ) - }, - rel_path => { - db_type =>"string", - hint =>"relative/path/to/somewhere", - pattern =>qr|^((?:[^/].*)?)/?$|, - format =>q( $1 ) - }, - directory => { - db_type =>"string", - hint =>"directory", - pattern =>qr|^([a-zA-Z0-9-_.]+)$|, - format =>q( $1 ) - }, - file => { - db_type =>"string", - hint =>"filename", - pattern =>qr|^([a-zA-Z0-9-_.]+)$|, - format =>q( $1 ) - }, - hostname => { - db_type =>"string", - hint =>"host.your.domain", - pattern =>qr|^([a-zA-Z0-9_.-]+)$|, - format =>q( $1 ) - }, - url => { - db_type =>"string", - hint =>"http://host.your.domain/", - pattern =>qr|^(?:http://)?(.+)$|, - format =>q( "http://".$1 ) - }, - email => { - db_type =>"string", - hint =>"your.name\@your.domain", - pattern =>qr|^([a-zA-Z0-9_.-]+)\@([a-zA-Z0-9_.-]+)$|, - format =>q( $1\@$2 ) - }, +our %types = ( + string => { + db_type => 'string', + hint => 'string', + pattern => qr|^(.+)$|, + format => q( $1 ) + }, + alphanum => { + db_type => 'string', + hint => 'alphanumeric', + pattern => qr|^([a-zA-Z0-9-_]+)$|, + format => q( $1 ) + }, + text => { + db_type => 'text', + hint => 'free text', + pattern => qr|^(.+)$|, + format => q( $1 ) + }, + boolean => { + db_type => 'boolean', + hint => 'yes|no', + pattern => qr|^([yn])|i, + check => q( $1 ), + format => q( ($1 =~ /^y/) ? 'yes' : 'no' ) + }, + integer => { + db_type => 'integer', + hint => 'integer', + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + decimal => { + db_type => 'decimal', + hint => 'decimal', + pattern => qr|^(\d+(?:\.\d+)?)$|, + format => q( $1 ) + }, + hexadecimal => { + db_type => 'hexadecimal', + hint => 'hexadecimal', + pattern => qr|^(?:0x)?([0-9a-f]{1,8})$|, + format => q( '0x'.$1 ) + }, + tristate => { + db_type => 'string', + hint => 'auto|yes|no', + pattern => qr|^([ayn])|i, check=>q( $1 ), + format => q( ($1 =~ /^y/) ? 'yes' : ($1 =~ /^n/ ? 'no' : 'auto' ) ) + }, + abs_path => { + db_type => 'string', + hint => '/absolute/path/to/somewhere', + pattern => qr|^((?:/[^/]*)+?)/?$|, + format => q( $1 ) + }, + rel_path => { + db_type => 'string', + hint => 'relative/path/to/somewhere', + pattern => qr|^((?:[^/].*)?)/?$|, + format => q( $1 ) + }, + directory => { + db_type => 'string', + hint => 'directory', + pattern => qr|^([a-zA-Z0-9-_.]+)$|, + format => q( $1 ) + }, + file => { + db_type => 'string', + hint => 'filename', + pattern => qr|^([a-zA-Z0-9-_.]+)$|, + format => q( $1 ) + }, + hostname => { + db_type => 'string', + hint => 'host.your.domain', + pattern => qr|^([a-zA-Z0-9_.-]+)$|, + format => q( $1 ) + }, + url => { + db_type => 'string', + hint => 'http://host.your.domain/', + pattern => qr|^(?:http://)?(.+)$|, + format => q( 'http://'.$1 ) + }, + email => { + db_type => 'string', + hint => 'your.name@your.domain', + pattern => qr|^([a-zA-Z0-9_.-]+)\@([a-zA-Z0-9_.-]+)$|, + format => q( $1\@$2 ) + }, ); -sub qqq { ## Un-pad paragraph of text. - local $_ = shift; - s{\n?^\s*}{ }mg; - return $_; -} +our @options = ( + { + name => 'ZM_SKIN_DEFAULT', + default => 'classic', + description => 'Default skin used by web interface', -our @options = -( - { - name => "ZM_SKIN_DEFAULT", - default => "classic", - description => "Default skin used by web interface", + help => q` + ZoneMinder allows the use of many different web interfaces. + This option allows you to set the default skin used by the + website. Users can change their skin later, this merely sets + the default. + `, + type => $types{string}, + category => 'system', + }, + { + name => 'ZM_CSS_DEFAULT', + default => 'classic', + description => 'Default set of css files used by web interface', + help => q` + ZoneMinder allows the use of many different web interfaces, and + some skins allow the use of different set of CSS files to + control the appearance. This option allows you to set the + default set of css files used by the website. Users can change + their css later, this merely sets the default. + `, + type => $types{string}, + category => 'system', + }, + { + name => 'ZM_LANG_DEFAULT', + default => 'en_gb', + description => 'Default language used by web interface', + help => q` + ZoneMinder allows the web interface to use languages other than + English if the appropriate language file has been created and + is present. This option allows you to change the default + language that is used from the shipped language, British + English, to another language + `, + type => $types{string}, + category => 'system', + }, + { + name => 'ZM_OPT_USE_AUTH', + default => 'no', + description => 'Authenticate user logins to ZoneMinder', + help => q` + ZoneMinder can run in two modes. The simplest is an entirely + unauthenticated mode where anyone can access ZoneMinder and + perform all tasks. This is most suitable for installations + where the web server access is limited in other ways. The other + mode enables user accounts with varying sets of permissions. + Users must login or authenticate to access ZoneMinder and are + limited by their defined permissions. + `, + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_AUTH_TYPE', + default => 'builtin', + description => 'What is used to authenticate ZoneMinder users', + help => q` + ZoneMinder can use two methods to authenticate users when + running in authenticated mode. The first is a builtin method + where ZoneMinder provides facilities for users to log in and + maintains track of their identity. The second method allows + interworking with other methods such as http basic + authentication which passes an independently authentication + 'remote' user via http. In this case ZoneMinder would use the + supplied user without additional authentication provided such a + user is configured ion ZoneMinder. + `, + requires => [ { name=>'ZM_OPT_USE_AUTH', value=>'yes' } ], + type => { + db_type => 'string', + hint => 'builtin|remote', + pattern => qr|^([br])|i, + format => q( $1 =~ /^b/ ? 'builtin' : 'remote' ) + }, + category => 'system', + }, + { + name => 'ZM_AUTH_RELAY', + default => 'hashed', + description => 'Method used to relay authentication information', + help => q` + When ZoneMinder is running in authenticated mode it can pass + user details between the web pages and the back end processes. + There are two methods for doing this. This first is to use a + time limited hashed string which contains no direct username or + password details, the second method is to pass the username and + passwords around in plaintext. This method is not recommend + except where you do not have the md5 libraries available on + your system or you have a completely isolated system with no + external access. You can also switch off authentication + relaying if your system is isolated in other ways. + `, + requires => [ { name=>'ZM_OPT_USE_AUTH', value=>'yes' } ], + type => { + db_type => 'string', + hint => 'hashed|plain|none', + pattern => qr|^([hpn])|i, + format => q( ($1 =~ /^h/) ? 'hashed' : ($1 =~ /^p/ ? 'plain' : 'none' ) ) + }, + category => 'system', + }, + { + name => 'ZM_AUTH_HASH_SECRET', + default => '...Change me to something unique...', + description => 'Secret for encoding hashed authentication information', + help => q` + When ZoneMinder is running in hashed authenticated mode it is + necessary to generate hashed strings containing encrypted + sensitive information such as usernames and password. Although + these string are reasonably secure the addition of a random + secret increases security substantially. + `, + requires => [ + { name=>'ZM_OPT_USE_AUTH', value=>'yes' }, + { name=>'ZM_AUTH_RELAY', value=>'hashed' } + ], + type => $types{string}, + category => 'system', + }, + { + name => 'ZM_AUTH_HASH_IPS', + default => 'yes', + description => 'Include IP addresses in the authentication hash', + help => q` + When ZoneMinder is running in hashed authenticated mode it can + optionally include the requesting IP address in the resultant + hash. This adds an extra level of security as only requests + from that address may use that authentication key. However in + some circumstances, such as access over mobile networks, the + requesting address can change for each request which will cause + most requests to fail. This option allows you to control + whether IP addresses are included in the authentication hash on + your system. If you experience intermitent problems with + authentication, switching this option off may help. + `, + requires => [ + { name=>'ZM_OPT_USE_AUTH', value=>'yes' }, + { name=>'ZM_AUTH_RELAY', value=>'hashed' } + ], + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_AUTH_HASH_LOGINS', + default => 'no', + description => 'Allow login by authentication hash', + help => q` + The normal process for logging into ZoneMinder is via the login + screen with username and password. In some circumstances it may + be desirable to allow access directly to one or more pages, for + instance from a third party application. If this option is + enabled then adding an 'auth' parameter to any request will + include a shortcut login bypassing the login screen, if not + already logged in. As authentication hashes are time and, + optionally, IP limited this can allow short-term access to + ZoneMinder screens from other web pages etc. In order to use + this the calling application will have to generate the + authentication hash itself and ensure it is valid. If you use + this option you should ensure that you have modified the + ZM_AUTH_HASH_SECRET to something unique to your system. + `, + requires => [ + { name=>'ZM_OPT_USE_AUTH', value=>'yes' }, + { name=>'ZM_AUTH_RELAY', value=>'hashed' } + ], + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_ENABLE_CSRF_MAGIC', + default => 'no', + description => 'Enable csrf-magic library', + help => q` + CSRF stands for Cross-Site Request Forgery which, under specific + circumstances, can allow an attacker to perform any task your + ZoneMinder user account has permission to perform. To accomplish + this, the attacker must write a very specific web page and get + you to navigate to it, while you are logged into the ZoneMinder + web console at the same time. Enabling ZM_ENABLE_CSRF_MAGIC will + help mitigate these kinds of attackes. Be warned this feature + is experimental and may cause problems, particularly with the API. + If you find a false positive and can document how to reproduce it, + then please report it. This feature defaults to OFF currently due to + its experimental nature. + `, + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_OPT_USE_API', + default => 'yes', + description => 'Enable ZoneMinder APIs', + help => q` + ZoneMinder now features a new API using which 3rd party + applications can interact with ZoneMinder data. It is + STRONGLY recommended that you enable authentication along + with APIs. Note that the APIs return sensitive data like + Monitor access details which are configured as JSON objects. + Which is why we recommend you enabling authentication, especially + if you are exposing your ZM instance on the Internet. + `, + type => $types{boolean}, + category => 'system', + }, + # PP - Google reCaptcha settings + { + name => 'ZM_OPT_USE_GOOG_RECAPTCHA', + default => 'no', + description => 'Add Google reCaptcha to login page', + help => q` + This option allows you to include a google + reCaptcha validation at login. This means in addition to providing + a valid usernane and password, you will also have to + pass the reCaptcha test. Please note that enabling this + option results in the zoneminder login page reach out + to google servers for captcha validation. Also please note + that enabling this option will break 3rd party clients + like zmNinja and zmView as they also need to login to ZoneMinder + and they will fail the reCaptcha test. + `, + requires => [ + {name=>'ZM_OPT_USE_AUTH', value=>'yes'} + ], + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_OPT_GOOG_RECAPTCHA_SITEKEY', + default => '...Insert your recaptcha site-key here...', + description => 'Your recaptcha site-key', + help => q`You need to generate your keys from + the Google reCaptcha website. + Please refer to https://www.google.com/recaptcha/ + for more details. + `, + requires => [ + {name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'} + ], + type => $types{string}, + category => 'system', + }, + { + name => 'ZM_OPT_GOOG_RECAPTCHA_SECRETKEY', + default => '...Insert your recaptcha secret-key here...', + description => 'Your recaptcha secret-key', + help => q`You need to generate your keys from + the Google reCaptcha website. + Please refer to https://www.google.com/recaptcha/ + for more details. + `, + requires => [ + {name=>'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'} + ], + type => $types{string}, + category => 'system', + }, + { + name => 'ZM_DIR_EVENTS', + default => 'events', + description => 'Directory where events are stored', + help => q` + This is the path to the events directory where all the event + images and other miscellaneous files are stored. CAUTION: The + directory you specify here cannot be outside the web root. This + is a common mistake. Most users should never change this value. + If you intend to record events to a second disk or network + share, then you should mount the drive or share directly to the + ZoneMinder events folder or follow the instructions in the + ZoneMinder Wiki titled Using a dedicated Hard Drive. + `, + type => $types{directory}, + category => 'paths', + }, + { + name => 'ZM_USE_DEEP_STORAGE', + default => 'yes', + description => 'Use a deep filesystem hierarchy for events', + help => q` + This option is now the default for new ZoneMinder systems and + should not be changed. Previous versions of ZoneMinder stored + all events for a monitor under one folder. Enabling + USE_DEEP_STORAGE causes ZoneMinder to store events under a + folder structure that follows year/month/day/hour/min/second. + Storing events this way avoids the limitation of storing more + than 32k files in a single folder inherent in some filesystems. + It is important to note that you cannot simply change this + option. You must stop zoneminder, enable USE_DEEP_STORAGE, and + then run "sudo zmupdate.pl --migrate-events". FAILURE TO DO + SO WILL RESULT IN LOSS OF YOUR DATA! Consult the ZoneMinder + WiKi for further details. + `, + type => $types{boolean}, + category => 'hidden', + }, + { + name => 'ZM_DIR_IMAGES', + default => 'images', + description => 'Directory where the images that the ZoneMinder client generates are stored', + help => q` + ZoneMinder generates a myriad of images, mostly of which are + associated with events. For those that aren't this is where + they go. CAUTION: The directory you specify here cannot be + outside the web root. This is a common mistake. Most users + should never change this value. If you intend to save images to + a second disk or network share, then you should mount the drive + or share directly to the ZoneMinder images folder or follow the + instructions in the ZoneMinder Wiki titled Using a dedicated + Hard Drive. + `, + type => $types{directory}, + category => 'paths', + }, + { + name => 'ZM_DIR_SOUNDS', + default => 'sounds', + description => 'Directory to the sounds that the ZoneMinder client can use', + help => q` + ZoneMinder can optionally play a sound file when an alarm is + detected. This indicates where to look for this file. CAUTION: + The directory you specify here cannot be outside the web root. + Most users should never change this value. + `, + type => $types{directory}, + category => 'paths', + }, + { + name => 'ZM_DIR_EXPORTS', + default => '@ZM_TMPDIR@', + description => 'Directory where exported archives are stored', + help => q` + This is the path to the exports directory where exported + tar.gz and zip archives are stored. By default this points to + ZoneMinder's temp folder, which often sits in ram. Since exported + archives can potentially become large, it is a good idea to move + this folder to some other location on machines with low memory. + `, + type => $types{directory}, + category => 'paths', + }, + { + name => 'ZM_PATH_ZMS', + default => '/cgi-bin/nph-zms', + description => 'Web path to zms streaming server', + help => q` + The ZoneMinder streaming server is required to send streamed + images to your browser. It will be installed into the cgi-bin + path given at configuration time. This option determines what + the web path to the server is rather than the local path on + your machine. Ordinarily the streaming server runs in + parser-header mode however if you experience problems with + streaming you can change this to non-parsed-header (nph) mode + by changing 'zms' to 'nph-zms'. + `, + type => $types{rel_path}, + category => 'paths', + }, + { + name => 'ZM_COLOUR_JPEG_FILES', + default => 'no', + description => 'Colourise greyscale JPEG files', + help => q` + Cameras that capture in greyscale can write their captured + images to jpeg files with a corresponding greyscale colour + space. This saves a small amount of disk space over colour + ones. However some tools such as ffmpeg either fail to work + with this colour space or have to convert it beforehand. + Setting this option to yes uses up a little more space but + makes creation of MPEG files much faster. + `, + type => $types{boolean}, + category => 'images', + }, + { + name => 'ZM_ADD_JPEG_COMMENTS', + default => 'no', + description => 'Add jpeg timestamp annotations as file header comments', + help => q` + JPEG files may have a number of extra fields added to the file + header. The comment field may have any kind of text added. This + options allows you to have the same text that is used to + annotate the image additionally included as a file header + comment. If you archive event images to other locations this + may help you locate images for particular events or times if + you use software that can read comment headers. + `, + type => $types{boolean}, + category => 'images', + }, + { + name => 'ZM_JPEG_FILE_QUALITY', + default => '70', + description => 'Set the JPEG quality setting for the saved event files (1-100)', + help => q` + When ZoneMinder detects an event it will save the images + associated with that event to files. These files are in the + JPEG format and can be viewed or streamed later. This option + specifies what image quality should be used to save these + files. A higher number means better quality but less + compression so will take up more disk space and take longer to + view over a slow connection. By contrast a low number means + smaller, quicker to view, files but at the price of lower + quality images. This setting applies to all images written + except if the capture image has caused an alarm and the alarm + file quality option is set at a higher value when that is used + instead. + `, + type => $types{integer}, + category => 'images', + }, + { + name => 'ZM_JPEG_ALARM_FILE_QUALITY', + default => '0', + description => 'Set the JPEG quality setting for the saved event files during an alarm (1-100)', + help => q` + This value is equivalent to the regular jpeg file quality + setting above except that it only applies to images saved while + in an alarm state and then only if this value is set to a + higher quality setting than the ordinary file setting. If set + to a lower value then it is ignored. Thus leaving it at the + default of 0 effectively means to use the regular file quality + setting for all saved images. This is to prevent acccidentally + saving important images at a worse quality setting. + `, + type => $types{integer}, + category => 'images', + }, + # Deprecated, now stream quality + { + name => 'ZM_JPEG_IMAGE_QUALITY', + default => '70', + description => q`Set the JPEG quality setting for the streamed 'live' images (1-100)`, + help => q` + When viewing a 'live' stream for a monitor ZoneMinder will grab + an image from the buffer and encode it into JPEG format before + sending it. This option specifies what image quality should be + used to encode these images. A higher number means better + quality but less compression so will take longer to view over a + slow connection. By contrast a low number means quicker to view + images but at the price of lower quality images. This option + does not apply when viewing events or still images as these are + usually just read from disk and so will be encoded at the + quality specified by the previous options. + `, + type => $types{integer}, + category => 'hidden', + }, + { + name => 'ZM_JPEG_STREAM_QUALITY', + default => '70', + description => q`Set the JPEG quality setting for the streamed 'live' images (1-100)`, + help => q` + When viewing a 'live' stream for a monitor ZoneMinder will grab + an image from the buffer and encode it into JPEG format before + sending it. This option specifies what image quality should be + used to encode these images. A higher number means better + quality but less compression so will take longer to view over a + slow connection. By contrast a low number means quicker to view + images but at the price of lower quality images. This option + does not apply when viewing events or still images as these are + usually just read from disk and so will be encoded at the + quality specified by the previous options. + `, + type => $types{integer}, + category => 'images', + }, + { + name => 'ZM_MPEG_TIMED_FRAMES', + default => 'yes', + description => 'Tag video frames with a timestamp for more realistic streaming', + help => q` + When using streamed MPEG based video, either for live monitor + streams or events, ZoneMinder can send the streams in two ways. + If this option is selected then the timestamp for each frame, + taken from it's capture time, is included in the stream. This + means that where the frame rate varies, for instance around an + alarm, the stream will still maintain it's 'real' timing. If + this option is not selected then an approximate frame rate is + calculated and that is used to schedule frames instead. This + option should be selected unless you encounter problems with + your preferred streaming method. + `, + type => $types{boolean}, + category => 'images', + }, + { + name => 'ZM_MPEG_LIVE_FORMAT', + default => 'swf', + description => q`What format 'live' video streams are played in`, + help => q` + When using MPEG mode ZoneMinder can output live video. However + what formats are handled by the browser varies greatly between + machines. This option allows you to specify a video format + using a file extension format, so you would just enter the + extension of the file type you would like and the rest is + determined from that. The default of 'asf' works well under + Windows with Windows Media Player but I'm currently not sure + what, if anything, works on a Linux platform. If you find out + please let me know! If this option is left blank then live + streams will revert to being in motion jpeg format + `, + type => $types{string}, + category => 'images', + }, + { + name => 'ZM_MPEG_REPLAY_FORMAT', + default => 'swf', + description => q`What format 'replay' video streams are played in`, + help => q` + When using MPEG mode ZoneMinder can replay events in encoded + video format. However what formats are handled by the browser + varies greatly between machines. This option allows you to + specify a video format using a file extension format, so you + would just enter the extension of the file type you would like + and the rest is determined from that. The default of 'asf' + works well under Windows with Windows Media Player and 'mpg', + or 'avi' etc should work under Linux. If you know any more then + please let me know! If this option is left blank then live + streams will revert to being in motion jpeg format + `, + type => $types{string}, + category => 'images', + }, + { + name => 'ZM_RAND_STREAM', + default => 'yes', + description => 'Add a random string to prevent caching of streams', + help => q` + Some browsers can cache the streams used by ZoneMinder. In + order to prevent his a harmless random string can be appended + to the url to make each invocation of the stream appear unique. + `, + type => $types{boolean}, + category => 'images', + }, + { + name => 'ZM_OPT_CAMBOZOLA', + default => 'no', + description => 'Is the (optional) cambozola java streaming client installed', + help => q` + Cambozola is a handy low fat cheese flavoured Java applet that + ZoneMinder uses to view image streams on browsers such as + Internet Explorer that don't natively support this format. If + you use this browser it is highly recommended to install this + from http://www.charliemouse.com/code/cambozola/ however if it + is not installed still images at a lower refresh rate can still + be viewed. + `, + type => $types{boolean}, + category => 'images', + }, + { + name => 'ZM_PATH_CAMBOZOLA', + default => 'cambozola.jar', + description => 'Web path to (optional) cambozola java streaming client', + help => q` + Cambozola is a handy low fat cheese flavoured Java applet that + ZoneMinder uses to view image streams on browsers such as + Internet Explorer that don't natively support this format. If + you use this browser it is highly recommended to install this + from http://www.charliemouse.com/code/cambozola/ however if it + is not installed still images at a lower refresh rate can still + be viewed. Leave this as 'cambozola.jar' if cambozola is + installed in the same directory as the ZoneMinder web client + files. + `, + requires => [ { name=>'ZM_OPT_CAMBOZOLA', value=>'yes' } ], + type => $types{rel_path}, + category => 'images', + }, + { + name => 'ZM_RELOAD_CAMBOZOLA', + default => '0', + description => 'After how many seconds should Cambozola be reloaded in live view', + help => q` + Cambozola allows for the viewing of streaming MJPEG however it + caches the entire stream into cache space on the computer, + setting this to a number > 0 will cause it to automatically + reload after that many seconds to avoid filling up a hard + drive. + `, + type => $types{integer}, + category => 'images', + }, + { + name => 'ZM_TIMESTAMP_ON_CAPTURE', + default => 'yes', + description => 'Timestamp images as soon as they are captured', + help => q` + ZoneMinder can add a timestamp to images in two ways. The + default method, when this option is set, is that each image is + timestamped immediately when captured and so the image held in + memory is marked right away. The second method does not + timestamp the images until they are either saved as part of an + event or accessed over the web. The timestamp used in both + methods will contain the same time as this is preserved along + with the image. The first method ensures that an image is + timestamped regardless of any other circumstances but will + result in all images being timestamped even those never saved + or viewed. The second method necessitates that saved images are + copied before being saved otherwise two timestamps perhaps at + different scales may be applied. This has the (perhaps) + desirable side effect that the timestamp is always applied at + the same resolution so an image that has scaling applied will + still have a legible and correctly scaled timestamp. + `, + type => $types{boolean}, + category => 'config', + }, + { + name => 'ZM_CPU_EXTENSIONS', + default => 'yes', + description => 'Use advanced CPU extensions to increase performance', + help => q` + When advanced processor extensions such as SSE2 or SSSE3 are + available, ZoneMinder can use them, which should increase + performance and reduce system load. Enabling this option on + processors that do not support the advanced processors + extensions used by ZoneMinder is harmless and will have no + effect. + `, + type => $types{boolean}, + category => 'config', + }, + { + name => 'ZM_FAST_IMAGE_BLENDS', + default => 'yes', + description => 'Use a fast algorithm to blend the reference image', + help => q` + To detect alarms ZoneMinder needs to blend the captured image + with the stored reference image to update it for comparison + with the next image. The reference blend percentage specified + for the monitor controls how much the new image affects the + reference image. There are two methods that are available for + this. If this option is set then fast calculation which does + not use any multiplication or division is used. This + calculation is extremely fast, however it limits the possible + blend percentages to 50%, 25%, 12.5%, 6.25%, 3.25% and 1.5%. + Any other blend percentage will be rounded to the nearest + possible one. The alternative is to switch this option off + and use standard blending instead, which is slower. + `, + type => $types{boolean}, + category => 'config', + }, + { + name => 'ZM_OPT_ADAPTIVE_SKIP', + default => 'yes', + description => 'Should frame analysis try and be efficient in skipping frames', + help => q` + In previous versions of ZoneMinder the analysis daemon would + attempt to keep up with the capture daemon by processing the + last captured frame on each pass. This would sometimes have the + undesirable side-effect of missing a chunk of the initial + activity that caused the alarm because the pre-alarm frames + would all have to be written to disk and the database before + processing the next frame, leading to some delay between the + first and second event frames. Setting this option enables a + newer adaptive algorithm where the analysis daemon attempts to + process as many captured frames as possible, only skipping + frames when in danger of the capture daemon overwriting yet to + be processed frames. This skip is variable depending on the + size of the ring buffer and the amount of space left in it. + Enabling this option will give you much better coverage of the + beginning of alarms whilst biasing out any skipped frames + towards the middle or end of the event. However you should be + aware that this will have the effect of making the analysis + daemon run somewhat behind the capture daemon during events and + for particularly fast rates of capture it is possible for the + adaptive algorithm to be overwhelmed and not have time to react + to a rapid build up of pending frames and thus for a buffer + overrun condition to occur. + `, + type => $types{boolean}, + category => 'config', + }, + { + name => 'ZM_MAX_SUSPEND_TIME', + default => '30', + description => 'Maximum time that a monitor may have motion detection suspended', + help => q` + ZoneMinder allows monitors to have motion detection to be + suspended, for instance while panning a camera. Ordinarily this + relies on the operator resuming motion detection afterwards as + failure to do so can leave a monitor in a permanently suspended + state. This setting allows you to set a maximum time which a + camera may be suspended for before it automatically resumes + motion detection. This time can be extended by subsequent + suspend indications after the first so continuous camera + movement will also occur while the monitor is suspended. + `, + type => $types{integer}, + category => 'config', + }, + # Deprecated, really no longer necessary + { + name => 'ZM_OPT_REMOTE_CAMERAS', + default => 'no', + description => 'Are you going to use remote/networked cameras', + help => q` + ZoneMinder can work with both local cameras, ie. those attached + physically to your computer and remote or network cameras. If + you will be using networked cameras select this option. + `, + type => $types{boolean}, + category => 'hidden', + }, + # Deprecated, now set on a per monitor basis using the Method field + { + name => 'ZM_NETCAM_REGEXPS', + default => 'yes', + description => 'Use regular expression matching with network cameras', + help => q` + Traditionally ZoneMinder has used complex regular regular + expressions to handle the multitude of formats that network + cameras produce. In versions from 1.21.1 the default is to use + a simpler and faster built in pattern matching methodology. + This works well with most networks cameras but if you have + problems you can try the older, but more flexible, regular + expression based method by selecting this option. Note, to use + this method you must have libpcre installed on your system. + `, + requires => [ { name => 'ZM_OPT_REMOTE_CAMERAS', value => 'yes' } ], + type => $types{boolean}, + category => 'hidden', + }, + { + name => 'ZM_HTTP_VERSION', + default => '1.0', + description => 'The version of HTTP that ZoneMinder will use to connect', + help => q` + ZoneMinder can communicate with network cameras using either of + the HTTP/1.1 or HTTP/1.0 standard. A server will normally fall + back to the version it supports with no problem so this should + usually by left at the default. However it can be changed to + HTTP/1.0 if necessary to resolve particular issues. + `, + type => { + db_type => 'string', + hint => '1.1|1.0', + pattern => qr|^(1\.[01])$|, + format => q( $1?$1:'' ) + }, + category => 'network', + }, + { + name => 'ZM_HTTP_UA', + default => 'ZoneMinder', + description => 'The user agent that ZoneMinder uses to identify itself', + help => q` + When ZoneMinder communicates with remote cameras it will + identify itself using this string and it's version number. This + is normally sufficient, however if a particular cameras expects + only to communicate with certain browsers then this can be + changed to a different string identifying ZoneMinder as + Internet Explorer or Netscape etc. + `, + type => $types{string}, + category => 'network', + }, + { + name => 'ZM_HTTP_TIMEOUT', + default => '2500', + description => 'How long ZoneMinder waits before giving up on images (milliseconds)', + help => q` + When retrieving remote images ZoneMinder will wait for this + length of time before deciding that an image is not going to + arrive and taking steps to retry. This timeout is in + milliseconds (1000 per second) and will apply to each part of + an image if it is not sent in one whole chunk. + `, + type => $types{integer}, + category => 'network', + }, + { + name => 'ZM_MIN_RTP_PORT', + default => '40200', + description => 'Minimum port that ZoneMinder will listen for RTP traffic on', + help => q` + When ZoneMinder communicates with MPEG4 capable cameras using + RTP with the unicast method it must open ports for the camera + to connect back to for control and streaming purposes. This + setting specifies the minimum port number that ZoneMinder will + use. Ordinarily two adjacent ports are used for each camera, + one for control packets and one for data packets. This port + should be set to an even number, you may also need to open up a + hole in your firewall to allow cameras to connect back if you + wish to use unicasting. + `, + type => $types{integer}, + category => 'network', + }, + { + name => 'ZM_MAX_RTP_PORT', + default => '40499', + description => 'Maximum port that ZoneMinder will listen for RTP traffic on', + help => q` + When ZoneMinder communicates with MPEG4 capable cameras using + RTP with the unicast method it must open ports for the camera + to connect back to for control and streaming purposes. This + setting specifies the maximum port number that ZoneMinder will + use. Ordinarily two adjacent ports are used for each camera, + one for control packets and one for data packets. This port + should be set to an even number, you may also need to open up a + hole in your firewall to allow cameras to connect back if you + wish to use unicasting. You should also ensure that you have + opened up at least two ports for each monitor that will be + connecting to unicasting network cameras. + `, + type => $types{integer}, + category => 'network', + }, + { + name => 'ZM_OPT_FFMPEG', + default => '@OPT_FFMPEG@', + description => 'Is the ffmpeg video encoder/decoder installed', + help => q` + ZoneMinder can optionally encode a series of video images into + an MPEG encoded movie file for viewing, downloading or storage. + This option allows you to specify whether you have the ffmpeg + tools installed. Note that creating MPEG files can be fairly + CPU and disk intensive and is not a required option as events + can still be reviewed as video streams without it. + `, + type => $types{boolean}, + category => 'images', + }, + { + name => 'ZM_PATH_FFMPEG', + default => '@PATH_FFMPEG@', + description => 'Path to (optional) ffmpeg mpeg encoder', + help => 'This path should point to where ffmpeg has been installed.', + requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], + type => $types{abs_path}, + category => 'images', + }, + { + name => 'ZM_FFMPEG_INPUT_OPTIONS', + default => '', + description => 'Additional input options to ffmpeg', + help => q` + Ffmpeg can take many options on the command line to control the + quality of video produced. This option allows you to specify + your own set that apply to the input to ffmpeg (options that + are given before the -i option). Check the ffmpeg documentation + for a full list of options which may be used here. + `, + requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], + type => $types{string}, + category => 'images', + }, + { + name => 'ZM_FFMPEG_OUTPUT_OPTIONS', + default => '-r 25', + description => 'Additional output options to ffmpeg', + help => q` + Ffmpeg can take many options on the command line to control the + quality of video produced. This option allows you to specify + your own set that apply to the output from ffmpeg (options that + are given after the -i option). Check the ffmpeg documentation + for a full list of options which may be used here. The most + common one will often be to force an output frame rate + supported by the video encoder. + `, + requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], + type => $types{string}, + category => 'images', + }, + { + name => 'ZM_FFMPEG_FORMATS', + default => 'mpg mpeg wmv asf avi* mov swf 3gp**', + description => 'Formats to allow for ffmpeg video generation', + help => q` + Ffmpeg can generate video in many different formats. This + option allows you to list the ones you want to be able to + select. As new formats are supported by ffmpeg you can add them + here and be able to use them immediately. Adding a '*' after a + format indicates that this will be the default format used for + web video, adding '**' defines the default format for phone + video. + `, + requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], + type => $types{string}, + category => 'images', + }, + { + name => 'ZM_FFMPEG_OPEN_TIMEOUT', + default => '10', + description => 'Timeout in seconds when opening a stream.', + help => q` + When Ffmpeg is opening a stream, it can take a long time before + failing; certain circumstances even seem to be able to lock + indefinitely. This option allows you to set a maximum time in + seconds to pass before closing the stream and trying to reopen + it again. + `, + requires => [ { name=>'ZM_OPT_FFMPEG', value=>'yes' } ], + type => $types{integer}, + category => 'images', + }, + { + name => 'ZM_LOG_LEVEL_SYSLOG', + default => '0', + description => 'Save logging output to the system log', + help => q` + ZoneMinder logging is now more more integrated between + components and allows you to specify the destination for + logging output and the individual levels for each. This option + lets you control the level of logging output that goes to the + system log. ZoneMinder binaries have always logged to the + system log but now scripts and web logging is also included. To + preserve the previous behaviour you should ensure this value is + set to Info or Warning. This option controls the maximum level + of logging that will be written, so Info includes Warnings and + Errors etc. To disable entirely, set this option to None. You + should use caution when setting this option to Debug as it can + affect severely affect system performance. If you want debug + you will also need to set a level and component below + `, + type => { + db_type => 'integer', + hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + category => 'logging', + }, + { + name => 'ZM_LOG_LEVEL_FILE', + default => '-5', + description => 'Save logging output to component files', + help => q` + ZoneMinder logging is now more more integrated between + components and allows you to specify the destination for + logging output and the individual levels for each. This option + lets you control the level of logging output that goes to + individual log files written by specific components. This is + how logging worked previously and although useful for tracking + down issues in specific components it also resulted in many + disparate log files. To preserve this behaviour you should + ensure this value is set to Info or Warning. This option + controls the maximum level of logging that will be written, so + Info includes Warnings and Errors etc. To disable entirely, set + this option to None. You should use caution when setting this + option to Debug as it can affect severely affect system + performance though file output has less impact than the other + options. If you want debug you will also need to set a level + and component below + `, + type => { + db_type => 'integer', + hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + category => 'logging', + }, + { + name => 'ZM_LOG_LEVEL_WEBLOG', + default => '-5', + description => 'Save logging output to the weblog', + help => q` + ZoneMinder logging is now more more integrated between + components and allows you to specify the destination for + logging output and the individual levels for each. This option + lets you control the level of logging output from the web + interface that goes to the httpd error log. Note that only web + logging from PHP and JavaScript files is included and so this + option is really only useful for investigating specific issues + with those components. This option controls the maximum level + of logging that will be written, so Info includes Warnings and + Errors etc. To disable entirely, set this option to None. You + should use caution when setting this option to Debug as it can + affect severely affect system performance. If you want debug + you will also need to set a level and component below + `, + type => { + db_type => 'integer', + hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + category => 'logging', + }, + { + name => 'ZM_LOG_LEVEL_DATABASE', + default => '0', + description => 'Save logging output to the database', + help => q` + ZoneMinder logging is now more more integrated between + components and allows you to specify the destination for + logging output and the individual levels for each. This option + lets you control the level of logging output that is written to + the database. This is a new option which can make viewing + logging output easier and more intuitive and also makes it + easier to get an overall impression of how the system is + performing. If you have a large or very busy system then it is + possible that use of this option may slow your system down if + the table becomes very large. Ensure you use the + LOG_DATABASE_LIMIT option to keep the table to a manageable + size. This option controls the maximum level of logging that + will be written, so Info includes Warnings and Errors etc. To + disable entirely, set this option to None. You should use + caution when setting this option to Debug as it can affect + severely affect system performance. If you want debug you will + also need to set a level and component below + `, + type => { + db_type => 'integer', + hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + category => 'logging', + }, + { + name => 'ZM_LOG_DATABASE_LIMIT', + default => '7 day', + description => 'Maximum number of log entries to retain', + help => q` + If you are using database logging then it is possible to + quickly build up a large number of entries in the Logs table. + This option allows you to specify how many of these entries are + kept. If you set this option to a number greater than zero then + that number is used to determine the maximum number of rows, + less than or equal to zero indicates no limit and is not + recommended. You can also set this value to time values such as + ' day' which will limit the log entries to those newer than + that time. You can specify 'hour', 'day', 'week', 'month' and + 'year', note that the values should be singular (no 's' at the + end). The Logs table is pruned periodically so it is possible + for more than the expected number of rows to be present briefly + in the meantime. + `, + type => $types{string}, + category => 'logging', + }, + { + name => 'ZM_LOG_DEBUG', + default => 'no', + description => 'Switch debugging on', + help => q` + ZoneMinder components usually support debug logging available + to help with diagnosing problems. Binary components have + several levels of debug whereas more other components have only + one. Normally this is disabled to minimise performance + penalties and avoid filling logs too quickly. This option lets + you switch on other options that allow you to configure + additional debug information to be output. Components will pick + up this instruction when they are restarted. + `, + type => $types{boolean}, + category => 'logging', + }, + { + name => 'ZM_LOG_DEBUG_TARGET', + default => '', + description => 'What components should have extra debug enabled', + help => q` + There are three scopes of debug available. Leaving this option + blank means that all components will use extra debug (not + recommended). Setting this option to '_', e.g. _zmc, + will limit extra debug to that component only. Setting this + option to '__', e.g. '_zmc_m1' will limit + extra debug to that instance of the component only. This is + ordinarily what you probably want to do. To debug scripts use + their names without the .pl extension, e.g. '_zmvideo' and to + debug issues with the web interface use '_web'. You can specify + multiple targets by separating them with '|' characters. + `, + requires => [ { name => 'ZM_LOG_DEBUG', value => 'yes' } ], + type => $types{string}, + category => 'logging', + }, + { + name => 'ZM_LOG_DEBUG_LEVEL', + default => 1, + description => 'What level of extra debug should be enabled', + help => q` + There are 9 levels of debug available, with higher numbers + being more debug and level 0 being no debug. However not all + levels are used by all components. Also if there is debug at a + high level it is usually likely to be output at such a volume + that it may obstruct normal operation. For this reason you + should set the level carefully and cautiously until the degree + of debug you wish to see is present. Scripts and the web + interface only have one level so this is an on/off type option + for them. + `, + requires => [ { name => 'ZM_LOG_DEBUG', value => 'yes' } ], + type => { + db_type => 'integer', + hint => '1|2|3|4|5|6|7|8|9', + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + category => 'logging', + }, + { + name => 'ZM_LOG_DEBUG_FILE', + default => '@ZM_LOGDIR@/zm_debug.log+', + description => 'Where extra debug is output to', + help => q` + This option allows you to specify a different target for debug + output. All components have a default log file which will + norally be in /tmp or /var/log and this is where debug will be + written to if this value is empty. Adding a path here will + temporarily redirect debug, and other logging output, to this + file. This option is a simple filename and you are debugging + several components then they will all try and write to the same + file with undesirable consequences. Appending a '+' to the + filename will cause the file to be created with a '.' + suffix containing your process id. In this way debug from each + run of a component is kept separate. This is the recommended + setting as it will also prevent subsequent runs from + overwriting the same log. You should ensure that permissions + are set up to allow writing to the file and directory specified + here. + `, + requires => [ { name => 'ZM_LOG_DEBUG', value => 'yes' } ], + type => $types{string}, + category => 'logging', + }, + { + name => 'ZM_LOG_CHECK_PERIOD', + default => '900', + description => 'Time period used when calculating overall system health', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to indicate what period of historical + events are used in this calculation. This value is expressed in + seconds and is ignored if LOG_LEVEL_DATABASE is set to None. + `, + type => $types{integer}, + category => 'logging', + }, + { + name => 'ZM_LOG_ALERT_WAR_COUNT', + default => '1', + description => 'Number of warnings indicating system alert state', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many warnings must have + occurred within the defined time period to generate an overall + system alert state. A value of zero means warnings are not + considered. This value is ignored if LOG_LEVEL_DATABASE is set + to None. + `, + type => $types{integer}, + category => 'logging', + }, + { + name => 'ZM_LOG_ALERT_ERR_COUNT', + default => '1', + description => 'Number of errors indicating system alert state', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many errors must have + occurred within the defined time period to generate an overall + system alert state. A value of zero means errors are not + considered. This value is ignored if LOG_LEVEL_DATABASE is set + to None. + `, + type => $types{integer}, + category => 'logging', + }, + { + name => 'ZM_LOG_ALERT_FAT_COUNT', + default => '0', + description => 'Number of fatal error indicating system alert state', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many fatal errors + (including panics) must have occurred within the defined time + period to generate an overall system alert state. A value of + zero means fatal errors are not considered. This value is + ignored if LOG_LEVEL_DATABASE is set to None. + `, + type => $types{integer}, + category => 'logging', + }, + { + name => 'ZM_LOG_ALARM_WAR_COUNT', + default => '100', + description => 'Number of warnings indicating system alarm state', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many warnings must have + occurred within the defined time period to generate an overall + system alarm state. A value of zero means warnings are not + considered. This value is ignored if LOG_LEVEL_DATABASE is set + to None. + `, + type => $types{integer}, + category => 'logging', + }, + { + name => 'ZM_LOG_ALARM_ERR_COUNT', + default => '10', + description => 'Number of errors indicating system alarm state', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many errors must have + occurred within the defined time period to generate an overall + system alarm state. A value of zero means errors are not + considered. This value is ignored if LOG_LEVEL_DATABASE is set + to None. + `, + type => $types{integer}, + category => 'logging', + }, + { + name => 'ZM_LOG_ALARM_FAT_COUNT', + default => '1', + description => 'Number of fatal error indicating system alarm state', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many fatal errors + (including panics) must have occurred within the defined time + period to generate an overall system alarm state. A value of + zero means fatal errors are not considered. This value is + ignored if LOG_LEVEL_DATABASE is set to None. + `, + type => $types{integer}, + category => 'logging', + }, + { + name => 'ZM_RECORD_EVENT_STATS', + default => 'yes', + description => 'Record event statistical information, switch off if too slow', + help => q` + This version of ZoneMinder records detailed information about + events in the Stats table. This can help in profiling what the + optimum settings are for Zones though this is tricky at + present. However in future releases this will be done more + easily and intuitively, especially with a large sample of + events. The default option of 'yes' allows this information to + be collected now in readiness for this but if you are concerned + about performance you can switch this off in which case no + Stats information will be saved. + `, + type => $types{boolean}, + category => 'logging', + }, + { + name => 'ZM_RECORD_DIAG_IMAGES', + default => 'no', + description => 'Record intermediate alarm diagnostic images, can be very slow', + help => q` + In addition to recording event statistics you can also record + the intermediate diagnostic images that display the results of + the various checks and processing that occur when trying to + determine if an alarm event has taken place. There are several + of these images generated for each frame and zone for each + alarm or alert frame so this can have a massive impact on + performance. Only switch this setting on for debug or analysis + purposes and remember to switch it off again once no longer + required. + `, + type => $types{boolean}, + category => 'logging', + }, + { + name => 'ZM_DUMP_CORES', + default => 'no', + description => 'Create core files on unexpected process failure.', + help => q` + When an unrecoverable error occurs in a ZoneMinder binary + process is has traditionally been trapped and the details + written to logs to aid in remote analysis. However in some + cases it is easier to diagnose the error if a core file, which + is a memory dump of the process at the time of the error, is + created. This can be interactively analysed in the debugger and + may reveal more or better information than that available from + the logs. This option is recommended for advanced users only + otherwise leave at the default. Note using this option to + trigger core files will mean that there will be no indication + in the binary logs that a process has died, they will just + stop, however the zmdc log will still contain an entry. Also + note that you may have to explicitly enable core file creation + on your system via the 'ulimit -c' command or other means + otherwise no file will be created regardless of the value of + this option. + `, + type => $types{boolean}, + category => 'logging', + }, + { + name => 'ZM_PATH_MAP', + default => '/dev/shm', + description => 'Path to the mapped memory files that that ZoneMinder can use', + help => q` + ZoneMinder has historically used IPC shared memory for shared + data between processes. This has it's advantages and + limitations. This version of ZoneMinder can use an alternate + method, mapped memory, instead with can be enabled with the + --enable--mmap directive to configure. This requires less + system configuration and is generally more flexible. However it + requires each shared data segment to map onto a filesystem + file. This option indicates where those mapped files go. You + should ensure that this location has sufficient space for these + files and for the best performance it should be a tmpfs file + system or ramdisk otherwise disk access may render this method + slower than the regular shared memory one. + `, + type => $types{abs_path}, + category => 'paths', + }, + { + name => 'ZM_PATH_SOCKS', + default => '@ZM_SOCKDIR@', + description => 'Path to the various Unix domain socket files that ZoneMinder uses', + help => q` + ZoneMinder generally uses Unix domain sockets where possible. + This reduces the need for port assignments and prevents + external applications from possibly compromising the daemons. + However each Unix socket requires a .sock file to be created. + This option indicates where those socket files go. + `, + type => $types{abs_path}, + category => 'paths', + }, + { + name => 'ZM_PATH_LOGS', + default => '@ZM_LOGDIR@', + description => 'Path to the various logs that the ZoneMinder daemons generate', + help => q` + There are various daemons that are used by ZoneMinder to + perform various tasks. Most generate helpful log files and this + is where they go. They can be deleted if not required for + debugging. + `, + type => $types{abs_path}, + category => 'paths', + }, + { + name => 'ZM_PATH_SWAP', + default => '@ZM_TMPDIR@', + description => 'Path to location for temporary swap images used in streaming', + help => q` + Buffered playback requires temporary swap images to be stored + for each instance of the streaming daemons. This option + determines where these images will be stored. The images will + actually be stored in sub directories beneath this location and + will be automatically cleaned up after a period of time. + `, + type => $types{abs_path}, + category => 'paths', + }, + { + name => 'ZM_PATH_ARP', + default => '', + description => 'Path to a supported ARP tool', + help => q` + The camera probe function uses Address Resolution Protocol in + order to find known devices on the network. Optionally supply + the full path to \"ip neigh\", \"arp -a\", or any other tool on + your system that returns ip/mac address pairs. If this field is + left empty, ZoneMinder will search for the command \"arp\" and + attempt to use that. + `, + type => $types{abs_path}, + category => 'paths', + }, + { + name => 'ZM_WEB_TITLE_PREFIX', + default => 'ZM', + description => 'The title prefix displayed on each window', + help => q` + If you have more than one installation of ZoneMinder it can be + helpful to display different titles for each one. Changing this + option allows you to customise the window titles to include + further information to aid identification. + `, + type => $types{string}, + category => 'web', + }, + { + name => 'ZM_WEB_CONSOLE_BANNER', + default => '', + description => 'Arbitrary text message near the top of the console', + help => q` + Allows the administrator to place an arbitrary text message + near the top of the web console. This is useful for the developers + to display a message which indicates the running instance of + ZoneMinder is a development snapshot, but it can also be used for + any other purpose as well. + `, + type => $types{string}, + category => 'web', + }, + { + name => 'ZM_WEB_EVENT_DISK_SPACE', + default => 'no', + description => 'Whether to show disk space used by each event.', + help => q`Adds another column to the listing of events + showing the disk space used by the event. This will impart a small + overhead as it will call du on the event directory. In practice + this overhead is fairly small but may be noticeable on IO-constrained + systems. + `, + type => $types{boolean}, + category => 'web', + }, + { + name => 'ZM_WEB_RESIZE_CONSOLE', + default => 'yes', + description => 'Should the console window resize itself to fit', + help => q` + Traditionally the main ZoneMinder web console window has + resized itself to shrink to a size small enough to list only + the monitors that are actually present. This is intended to + make the window more unobtrusize but may not be to everyones + tastes, especially if opened in a tab in browsers which support + this kind if layout. Switch this option off to have the console + window size left to the users preference + `, + type => $types{boolean}, + category => 'web', + }, + { + name => 'ZM_WEB_ID_ON_CONSOLE', + default => 'no', + description => 'Should the console list the monitor id', + help => q` + Some find it useful to have the id always visible + on the console. This option will add a column listing it. + `, + type => $types{boolean}, + category => 'web', + }, + { + name => 'ZM_WEB_POPUP_ON_ALARM', + default => 'yes', + description => 'Should the monitor window jump to the top if an alarm occurs', + help => q` + When viewing a live monitor stream you can specify whether you + want the window to pop to the front if an alarm occurs when the + window is minimised or behind another window. This is most + useful if your monitors are over doors for example when they + can pop up if someone comes to the doorway. + `, + type => $types{boolean}, + category => 'web', + }, + { + name => 'ZM_OPT_X10', + default => 'no', + description => 'Support interfacing with X10 devices', + help => q` + If you have an X10 Home Automation setup in your home you can + use ZoneMinder to initiate or react to X10 signals if your + computer has the appropriate interface controller. This option + indicates whether X10 options will be available in the browser + client. + `, + type => $types{boolean}, + category => 'x10', + }, + { + name => 'ZM_X10_DEVICE', + default => '/dev/ttyS0', + description => 'What device is your X10 controller connected on', + requires => [ { name => 'ZM_OPT_X10', value => 'yes' } ], + help => q` + If you have an X10 controller device (e.g. XM10U) connected to + your computer this option details which port it is connected on, + the default of /dev/ttyS0 maps to serial or com port 1. + `, + type => $types{abs_path}, + category => 'x10', + }, + { + name => 'ZM_X10_HOUSE_CODE', + default => 'A', + description => 'What X10 house code should be used', + requires => [ { name => 'ZM_OPT_X10', value => 'yes' } ], + help => q` + X10 devices are grouped together by identifying them as all + belonging to one House Code. This option details what that is. + It should be a single letter between A and P. + `, + type => { db_type=>'string', hint=>'A-P', pattern=>qr|^([A-P])|i, format=>q( uc($1) ) }, + category => 'x10', + }, + { + name => 'ZM_X10_DB_RELOAD_INTERVAL', + default => '60', + description => 'How often (in seconds) the X10 daemon reloads the monitors from the database', + requires => [ { name => 'ZM_OPT_X10', value => 'yes' } ], + help => q` + The zmx10 daemon periodically checks the database to find out + what X10 events trigger, or result from, alarms. This option + determines how frequently this check occurs, unless you change + this area frequently this can be a fairly large value. + `, + type => $types{integer}, + category => 'x10', + }, + { + name => 'ZM_WEB_SOUND_ON_ALARM', + default => 'no', + description => 'Should the monitor window play a sound if an alarm occurs', + help => q` + When viewing a live monitor stream you can specify whether you + want the window to play a sound to alert you if an alarm + occurs. + `, + type => $types{boolean}, + category => 'web', + }, + { + name => 'ZM_WEB_ALARM_SOUND', + default => '', + description => 'The sound to play on alarm, put this in the sounds directory', + help => q` + You can specify a sound file to play if an alarm occurs whilst + you are watching a live monitor stream. So long as your browser + understands the format it does not need to be any particular + type. This file should be placed in the sounds directory + defined earlier. + `, + type => $types{file}, + requires => [ { name => 'ZM_WEB_SOUND_ON_ALARM', value => 'yes' } ], + category => 'web', + }, + { + name => 'ZM_WEB_COMPACT_MONTAGE', + default => 'no', + description => 'Compact the montage view by removing extra detail', + help => q` + The montage view shows the output of all of your active + monitors in one window. This include a small menu and status + information for each one. This can increase the web traffic and + make the window larger than may be desired. Setting this option + on removes all this extraneous information and just displays + the images. + `, + type => $types{boolean}, + category => 'web', + }, + { + name => 'ZM_OPT_FAST_DELETE', + default => 'no', + description => 'Delete only event database records for speed', + help => q` + Normally an event created as the result of an alarm consists of + entries in one or more database tables plus the various files + associated with it. When deleting events in the browser it can + take a long time to remove all of this if your are trying to do + a lot of events at once. It is recommended that you set this + option which means that the browser client only deletes the key + entries in the events table, which means the events will no + longer appear in the listing, and leaves the zmaudit daemon to + clear up the rest later. Note that this feature is less relevant + with modern hardware. Recommend this feature be left off. + `, + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_STRICT_VIDEO_CONFIG', + default => 'yes', + description => 'Allow errors in setting video config to be fatal', + help => q` + With some video devices errors can be reported in setting the + various video attributes when in fact the operation was + successful. Switching this option off will still allow these + errors to be reported but will not cause them to kill the video + capture daemon. Note however that doing this will cause all + errors to be ignored including those which are genuine and + which may cause the video capture to not function correctly. + Use this option with caution. + `, + type => $types{boolean}, + category => 'config', + }, + { + name => 'ZM_LD_PRELOAD', + default => '', + description => "Path to library to preload before launching daemons", + help => q` + Some older cameras require the use of the v4l1 compat + library. This setting allows the setting of the path + to the library, so that it can be loaded by zmdc.pl + before launching zmc. + `, + type => $types{abs_path}, + category => 'config', + }, + { + name => 'ZM_SIGNAL_CHECK_POINTS', + default => '10', + description => 'How many points in a captured image to check for signal loss', + help => q` + For locally attached video cameras ZoneMinder can check for + signal loss by looking at a number of random points on each + captured image. If all of these points are set to the same + fixed colour then the camera is assumed to have lost signal. + When this happens any open events are closed and a short one + frame signal loss event is generated, as is another when the + signal returns. This option defines how many points on each + image to check. Note that this is a maximum, any points found + to not have the check colour will abort any further checks so + in most cases on a couple of points will actually be checked. + Network and file based cameras are never checked. + `, + type => $types{integer}, + category => 'config', + }, + { + name => 'ZM_V4L_MULTI_BUFFER', + default => 'yes', + description => 'Use more than one buffer for Video 4 Linux devices', + help => q` + Performance when using Video 4 Linux devices is usually best if + multiple buffers are used allowing the next image to be + captured while the previous one is being processed. If you have + multiple devices on a card sharing one input that requires + switching then this approach can sometimes cause frames from + one source to be mixed up with frames from another. Switching + this option off prevents multi buffering resulting in slower + but more stable image capture. This option is ignored for + non-local cameras or if only one input is present on a capture + chip. This option addresses a similar problem to the + ZM_CAPTURES_PER_FRAME option and you should normally change the + value of only one of the options at a time. If you have + different capture cards that need different values you can + override them in each individual monitor on the source page. + `, + type => $types{boolean}, + category => 'config', + }, + { + name => 'ZM_CAPTURES_PER_FRAME', + default => '1', + description => 'How many images are captured per returned frame, for shared local cameras', + help => q` + If you are using cameras attached to a video capture card which + forces multiple inputs to share one capture chip, it can + sometimes produce images with interlaced frames reversed + resulting in poor image quality and a distinctive comb edge + appearance. Increasing this setting allows you to force + additional image captures before one is selected as the + captured frame. This allows the capture hardware to 'settle + down' and produce better quality images at the price of lesser + capture rates. This option has no effect on (a) network + cameras, or (b) where multiple inputs do not share a capture + chip. This option addresses a similar problem to the + ZM_V4L_MULTI_BUFFER option and you should normally change the + value of only one of the options at a time. If you have + different capture cards that need different values you can + override them in each individual monitor on the source page. + `, + type => $types{integer}, + category => 'config', + }, + { + name => 'ZM_FILTER_RELOAD_DELAY', + default => '300', + description => 'How often (in seconds) filters are reloaded in zmfilter', + help => q` + ZoneMinder allows you to save filters to the database which + allow events that match certain criteria to be emailed, deleted + or uploaded to a remote machine etc. The zmfilter daemon loads + these and does the actual operation. This option determines how + often the filters are reloaded from the database to get the + latest versions or new filters. If you don't change filters + very often this value can be set to a large value. + `, + type => $types{integer}, + category => 'system', + }, + { + name => 'ZM_FILTER_EXECUTE_INTERVAL', + default => '60', + description => 'How often (in seconds) to run automatic saved filters', + help => q` + ZoneMinder allows you to save filters to the database which + allow events that match certain criteria to be emailed, deleted + or uploaded to a remote machine etc. The zmfilter daemon loads + these and does the actual operation. This option determines how + often the filters are executed on the saved event in the + database. If you want a rapid response to new events this + should be a smaller value, however this may increase the + overall load on the system and affect performance of other + elements. + `, + type => $types{integer}, + category => 'system', + }, + { + name => 'ZM_OPT_UPLOAD', + default => 'no', + description => 'Should ZoneMinder support uploading events from filters', + help => q` + In ZoneMinder you can create event filters that specify whether + events that match certain criteria should be uploaded to a + remote server for archiving. This option specifies whether this + functionality should be available + `, + type => $types{boolean}, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_ARCH_FORMAT', + default => 'tar', + description => 'What format the uploaded events should be created in.', + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + help => q` + Uploaded events may be stored in either .tar or .zip format, + this option specifies which. Note that to use this you will + need to have the Archive::Tar and/or Archive::Zip perl modules + installed. + `, + type => { + db_type =>'string', + hint =>'tar|zip', + pattern =>qr|^([tz])|i, + format =>q( $1 =~ /^t/ ? 'tar' : 'zip' ) + }, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_ARCH_COMPRESS', + default => 'no', + description => 'Should archive files be compressed', + help => q` + When the archive files are created they can be compressed. + However in general since the images are compressed already this + saves only a minimal amount of space versus utilising more CPU + in their creation. Only enable if you have CPU to waste and are + limited in disk space on your remote server or bandwidth. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{boolean}, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_ARCH_ANALYSE', + default => 'no', + description => 'Include the analysis files in the archive', + help => q` + When the archive files are created they can contain either just + the captured frames or both the captured frames and, for frames + that caused an alarm, the analysed image with the changed area + highlighted. This option controls files are included. Only + include analysed frames if you have a high bandwidth connection + to the remote server or if you need help in figuring out what + caused an alarm in the first place as archives with these files + in can be considerably larger. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{boolean}, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_PROTOCOL', + default => 'ftp', + description => 'What protocol to use to upload events', + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + help => q` + ZoneMinder can upload events to a remote server using either + FTP or SFTP. Regular FTP is widely supported but not + necessarily very secure whereas SFTP (Secure FTP) runs over an + ssh connection and so is encrypted and uses regular ssh ports. + Note that to use this you will need to have the appropriate + perl module, either Net::FTP or Net::SFTP installed depending + on your choice. + `, + type => { + db_type =>'string', + hint =>'ftp|sftp', + pattern =>qr|^([tz])|i, + format =>q( $1 =~ /^f/ ? 'ftp' : 'sftp' ) + }, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_FTP_HOST', + default => '', + description => 'The remote server to upload to', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the name, or ip + address, of the server to use. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{hostname}, + category => 'hidden', + }, + { + name => 'ZM_UPLOAD_HOST', + default => '', + description => 'The remote server to upload events to', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the name, or ip address, + of the server to use. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{hostname}, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_PORT', + default => '', + description => 'The port on the remote upload server, if not the default (SFTP only)', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. If you are using the SFTP protocol then this + option allows you to specify a particular port to use for + connection. If this option is left blank then the default, port + 22, is used. This option is ignored for FTP uploads. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{integer}, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_FTP_USER', + default => '', + description => 'Your ftp username', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the username that + ZoneMinder should use to log in for ftp transfer. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{alphanum}, + category => 'hidden', + }, + { + name => 'ZM_UPLOAD_USER', + default => '', + description => 'Remote server username', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the username that + ZoneMinder should use to log in for transfer. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{alphanum}, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_FTP_PASS', + default => '', + description => 'Your ftp password', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the password that + ZoneMinder should use to log in for ftp transfer. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{string}, + category => 'hidden', + }, + { + name => 'ZM_UPLOAD_PASS', + default => '', + description => 'Remote server password', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the password that + ZoneMinder should use to log in for transfer. If you are using + certificate based logins for SFTP servers you can leave this + option blank. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{string}, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_FTP_LOC_DIR', + default => '@ZM_TMPDIR@', + description => 'The local directory in which to create upload files', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the local directory + that ZoneMinder should use for temporary upload files. These + are files that are created from events, uploaded and then + deleted. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{abs_path}, + category => 'hidden', + }, + { + name => 'ZM_UPLOAD_LOC_DIR', + default => '@ZM_TMPDIR@', + description => 'The local directory in which to create upload files', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the local directory that + ZoneMinder should use for temporary upload files. These are + files that are created from events, uploaded and then deleted. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{abs_path}, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_FTP_REM_DIR', + default => '', + description => 'The remote directory to upload to', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the remote directory + that ZoneMinder should use to upload event files to. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{rel_path}, + category => 'hidden', + }, + { + name => 'ZM_UPLOAD_REM_DIR', + default => '', + description => 'The remote directory to upload to', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the remote directory + that ZoneMinder should use to upload event files to. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{rel_path}, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_FTP_TIMEOUT', + default => '120', + description => 'How long to allow the transfer to take for each file', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the maximum ftp + inactivity timeout (in seconds) that should be tolerated before + ZoneMinder determines that the transfer has failed and closes + down the connection. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{integer}, + category => 'hidden', + }, + { + name => 'ZM_UPLOAD_TIMEOUT', + default => '120', + description => 'How long to allow the transfer to take for each file', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the maximum inactivity + timeout (in seconds) that should be tolerated before ZoneMinder + determines that the transfer has failed and closes down the + connection. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{integer}, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_STRICT', + default => 'no', + description => 'Require strict host key checking for SFTP uploads', + help => q` + You can require SFTP uploads to verify the host key of the remote server + for protection against man-in-the-middle attacks. You will need to add the + server's key to the known_hosts file. On most systems, this will be + ~/.ssh/known_hosts, where ~ is the home directory of the web server running + ZoneMinder. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{boolean}, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_FTP_PASSIVE', + default => 'yes', + description => 'Use passive ftp when uploading', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates that ftp transfers + should be done in passive mode. This uses a single connection + for all ftp activity and, whilst slower than active transfers, + is more robust and likely to work from behind filewalls. This + option is ignored for SFTP transfers. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + help => q` + If your computer is behind a firewall or proxy you may need to + set FTP to passive mode. In fact for simple transfers it makes + little sense to do otherwise anyway but you can set this to + 'No' if you wish. + `, + type => $types{boolean}, + category => 'upload', + }, + { + name => 'ZM_UPLOAD_FTP_DEBUG', + default => 'no', + description => 'Switch ftp debugging on', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. If you are having (or expecting) troubles + with uploading events then setting this to 'yes' permits + additional information to be included in the zmfilter log file. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{boolean}, + category => 'hidden', + }, + { + name => 'ZM_UPLOAD_DEBUG', + default => 'no', + description => 'Switch upload debugging on', + help => q` + You can use filters to instruct ZoneMinder to upload events to + a remote server. If you are having (or expecting) troubles with + uploading events then setting this to 'yes' permits additional + information to be generated by the underlying transfer modules + and included in the logs. + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + type => $types{boolean}, + category => 'upload', + }, + { + name => 'ZM_OPT_EMAIL', + default => 'no', + description => 'Should ZoneMinder email you details of events that match corresponding filters', + help => q` + In ZoneMinder you can create event filters that specify whether + events that match certain criteria should have their details + emailed to you at a designated email address. This will allow + you to be notified of events as soon as they occur and also to + quickly view the events directly. This option specifies whether + this functionality should be available. The email created with + this option can be any size and is intended to be sent to a + regular email reader rather than a mobile device. + `, + type => $types{boolean}, + category => 'mail', + }, + { + name => 'ZM_EMAIL_ADDRESS', + default => '', + description => 'The email address to send matching event details to', + requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], + help => q` + This option is used to define the email address that any events + that match the appropriate filters will be sent to. + `, + type => $types{email}, + category => 'mail', + }, + { + name => 'ZM_EMAIL_TEXT', + default => 'subject = "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)" + body = " + Hello, - help => qqq(" - ZoneMinder allows the use of many different web interfaces. - This option allows you to set the default skin used by the - website. Users can change their skin later, this merely sets - the default. - "), - type => $types{string}, - category => "system", - }, - { - name => "ZM_CSS_DEFAULT", - default => "classic", - description => "Default set of css files used by web interface", - help => qqq(" - ZoneMinder allows the use of many different web interfaces, and - some skins allow the use of different set of CSS files to - control the appearance. This option allows you to set the - default set of css files used by the website. Users can change - their css later, this merely sets the default. - "), - type => $types{string}, - category => "system", - }, - { - name => "ZM_LANG_DEFAULT", - default => "en_gb", - description => "Default language used by web interface", - help => qqq(" - ZoneMinder allows the web interface to use languages other than - English if the appropriate language file has been created and - is present. This option allows you to change the default - language that is used from the shipped language, British - English, to another language - "), - type => $types{string}, - category => "system", - }, - { - name => "ZM_OPT_USE_AUTH", - default => "no", - description => "Authenticate user logins to ZoneMinder", - help => qqq(" - ZoneMinder can run in two modes. The simplest is an entirely - unauthenticated mode where anyone can access ZoneMinder and - perform all tasks. This is most suitable for installations - where the web server access is limited in other ways. The other - mode enables user accounts with varying sets of permissions. - Users must login or authenticate to access ZoneMinder and are - limited by their defined permissions. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_AUTH_TYPE", - default => "builtin", - description => "What is used to authenticate ZoneMinder users", - help => qqq(" - ZoneMinder can use two methods to authenticate users when - running in authenticated mode. The first is a builtin method - where ZoneMinder provides facilities for users to log in and - maintains track of their identity. The second method allows - interworking with other methods such as http basic - authentication which passes an independently authentication - 'remote' user via http. In this case ZoneMinder would use the - supplied user without additional authentication provided such a - user is configured ion ZoneMinder. - "), - requires => [ { name=>"ZM_OPT_USE_AUTH", value=>"yes" } ], - type => { - db_type =>"string", - hint =>"builtin|remote", - pattern =>qr|^([br])|i, - format =>q( $1 =~ /^b/ ? "builtin" : "remote" ) - }, - category => "system", - }, - { - name => "ZM_AUTH_RELAY", - default => "hashed", - description => "Method used to relay authentication information", - help => qqq(" - When ZoneMinder is running in authenticated mode it can pass - user details between the web pages and the back end processes. - There are two methods for doing this. This first is to use a - time limited hashed string which contains no direct username or - password details, the second method is to pass the username and - passwords around in plaintext. This method is not recommend - except where you do not have the md5 libraries available on - your system or you have a completely isolated system with no - external access. You can also switch off authentication - relaying if your system is isolated in other ways. - "), - requires => [ { name=>"ZM_OPT_USE_AUTH", value=>"yes" } ], - type => { - db_type =>"string", - hint =>"hashed|plain|none", - pattern =>qr|^([hpn])|i, - format =>q( ($1 =~ /^h/) - ? "hashed" - : ($1 =~ /^p/ ? "plain" : "none" ) - ) - }, - category => "system", - }, - { - name => "ZM_AUTH_HASH_SECRET", - default => "...Change me to something unique...", - description => "Secret for encoding hashed authentication information", - help => qqq(" - When ZoneMinder is running in hashed authenticated mode it is - necessary to generate hashed strings containing encrypted - sensitive information such as usernames and password. Although - these string are reasonably secure the addition of a random - secret increases security substantially. - "), - requires => [ - { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, - { name=>"ZM_AUTH_RELAY", value=>"hashed" } - ], - type => $types{string}, - category => "system", - }, - { - name => "ZM_AUTH_HASH_IPS", - default => "yes", - description => "Include IP addresses in the authentication hash", - help => qqq(" - When ZoneMinder is running in hashed authenticated mode it can - optionally include the requesting IP address in the resultant - hash. This adds an extra level of security as only requests - from that address may use that authentication key. However in - some circumstances, such as access over mobile networks, the - requesting address can change for each request which will cause - most requests to fail. This option allows you to control - whether IP addresses are included in the authentication hash on - your system. If you experience intermitent problems with - authentication, switching this option off may help. - "), - requires => [ - { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, - { name=>"ZM_AUTH_RELAY", value=>"hashed" } - ], - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_AUTH_HASH_LOGINS", - default => "no", - description => "Allow login by authentication hash", - help => qqq(" - The normal process for logging into ZoneMinder is via the login - screen with username and password. In some circumstances it may - be desirable to allow access directly to one or more pages, for - instance from a third party application. If this option is - enabled then adding an 'auth' parameter to any request will - include a shortcut login bypassing the login screen, if not - already logged in. As authentication hashes are time and, - optionally, IP limited this can allow short-term access to - ZoneMinder screens from other web pages etc. In order to use - this the calling application will have to generate the - authentication hash itself and ensure it is valid. If you use - this option you should ensure that you have modified the - ZM_AUTH_HASH_SECRET to something unique to your system. - "), - requires => [ - { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, - { name=>"ZM_AUTH_RELAY", value=>"hashed" } - ], - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_OPT_USE_API", - default => "yes", - description => "Enable ZoneMinder APIs", - help => qqq(" - ZoneMinder now features a new API using which 3rd party - applications can interact with ZoneMinder data. It is - STRONGLY recommended that you enable authentication along - with APIs. Note that the APIs return sensitive data like - Monitor access details which are configured as JSON objects. - Which is why we recommend you enabling authentication, especially - if you are exposing your ZM instance on the Internet. - "), - type => $types{boolean}, - category => "system", - }, -# PP - Google reCaptcha settings - { - name => "ZM_OPT_USE_GOOG_RECAPTCHA", - default => "no", - description => "Add Google reCaptcha to login page", - help => qqq(" - This option allows you to include a google - reCaptcha validation at login. This means in addition to providing - a valid usernane and password, you will also have to - pass the reCaptcha test. Please note that enabling this - option results in the zoneminder login page reach out - to google servers for captcha validation. Also please note - that enabling this option will break 3rd party clients - like zmNinja and zmView as they also need to login to ZoneMinder - and they will fail the reCaptcha test. - "), - requires => [ - {name=>"ZM_OPT_USE_AUTH", value=>"yes"} - ], - type => $types {boolean}, - category => "system", - }, - - { - name => "ZM_OPT_GOOG_RECAPTCHA_SITEKEY", - default => "...Insert your recaptcha site-key here...", - description => "Your recaptcha site-key", - help => qqq("You need to generate your keys from - the Google reCaptcha website. - Please refer to https://www.google.com/recaptcha/ - for more details. - "), - requires => [ - {name=>"ZM_OPT_USE_GOOG_RECAPTCHA", value=>"yes"} - ], - type => $types {string}, - category => "system", - }, - { - name => "ZM_OPT_GOOG_RECAPTCHA_SECRETKEY", - default => "...Insert your recaptcha secret-key here...", - description => "Your recaptcha secret-key", - help => qqq("You need to generate your keys from - the Google reCaptcha website. - Please refer to https://www.google.com/recaptcha/ - for more details. - "), - requires => [ - {name=>"ZM_OPT_USE_GOOG_RECAPTCHA", value=>"yes"} - ], - type => $types {string}, - category => "system", - }, - + An alarm has been detected on your installation of the ZoneMinder. - { - name => "ZM_DIR_EVENTS", - default => "events", - description => "Directory where events are stored", - help => qqq(" - This is the path to the events directory where all the event - images and other miscellaneous files are stored. CAUTION: The - directory you specify here cannot be outside the web root. This - is a common mistake. Most users should never change this value. - If you intend to record events to a second disk or network - share, then you should mount the drive or share directly to the - ZoneMinder events folder or follow the instructions in the - ZoneMinder Wiki titled Using a dedicated Hard Drive. - "), - type => $types{directory}, - category => "paths", - }, - { - name => "ZM_USE_DEEP_STORAGE", - default => "yes", - description => "Use a deep filesystem hierarchy for events", - help => qqq(" - This option is now the default for new ZoneMinder systems and - should not be changed. Previous versions of ZoneMinder stored - all events for a monitor under one folder. Enabling - USE_DEEP_STORAGE causes ZoneMinder to store events under a - folder structure that follows year/month/day/hour/min/second. - Storing events this way avoids the limitation of storing more - than 32k files in a single folder inherent in some filesystems. - It is important to note that you cannot simply change this - option. You must stop zoneminder, enable USE_DEEP_STORAGE, and - then run \"sudo zmupdate.pl --migrate-events\". FAILURE TO DO - SO WILL RESULT IN LOSS OF YOUR DATA! Consult the ZoneMinder - WiKi for further details. - "), - type => $types{boolean}, - category => "hidden", - }, - { - name => "ZM_DIR_IMAGES", - default => "images", - description => "Directory where the images that the ZoneMinder client generates are stored", - help => qqq(" - ZoneMinder generates a myriad of images, mostly of which are - associated with events. For those that aren't this is where - they go. CAUTION: The directory you specify here cannot be - outside the web root. This is a common mistake. Most users - should never change this value. If you intend to save images to - a second disk or network share, then you should mount the drive - or share directly to the ZoneMinder images folder or follow the - instructions in the ZoneMinder Wiki titled Using a dedicated - Hard Drive. - "), - type => $types{directory}, - category => "paths", - }, - { - name => "ZM_DIR_SOUNDS", - default => "sounds", - description => "Directory to the sounds that the ZoneMinder client can use", - help => qqq(" - ZoneMinder can optionally play a sound file when an alarm is - detected. This indicates where to look for this file. CAUTION: - The directory you specify here cannot be outside the web root. - Most users should never change this value. - "), - type => $types{directory}, - category => "paths", - }, - { - name => "ZM_PATH_ZMS", - default => "/cgi-bin/nph-zms", - description => "Web path to zms streaming server", - help => qqq(" - The ZoneMinder streaming server is required to send streamed - images to your browser. It will be installed into the cgi-bin - path given at configuration time. This option determines what - the web path to the server is rather than the local path on - your machine. Ordinarily the streaming server runs in - parser-header mode however if you experience problems with - streaming you can change this to non-parsed-header (nph) mode - by changing 'zms' to 'nph-zms'. - "), - type => $types{rel_path}, - category => "paths", - }, - { - name => "ZM_COLOUR_JPEG_FILES", - default => "no", - description => "Colourise greyscale JPEG files", - help => qqq(" - Cameras that capture in greyscale can write their captured - images to jpeg files with a corresponding greyscale colour - space. This saves a small amount of disk space over colour - ones. However some tools such as ffmpeg either fail to work - with this colour space or have to convert it beforehand. - Setting this option to yes uses up a little more space but - makes creation of MPEG files much faster. - "), - type => $types{boolean}, - category => "images", - }, - { - name => "ZM_ADD_JPEG_COMMENTS", - default => "no", - description => "Add jpeg timestamp annotations as file header comments", - help => qqq(" - JPEG files may have a number of extra fields added to the file - header. The comment field may have any kind of text added. This - options allows you to have the same text that is used to - annotate the image additionally included as a file header - comment. If you archive event images to other locations this - may help you locate images for particular events or times if - you use software that can read comment headers. - "), - type => $types{boolean}, - category => "images", - }, - { - name => "ZM_JPEG_FILE_QUALITY", - default => "70", - description => "Set the JPEG quality setting for the saved event files (1-100)", - help => qqq(" - When ZoneMinder detects an event it will save the images - associated with that event to files. These files are in the - JPEG format and can be viewed or streamed later. This option - specifies what image quality should be used to save these - files. A higher number means better quality but less - compression so will take up more disk space and take longer to - view over a slow connection. By contrast a low number means - smaller, quicker to view, files but at the price of lower - quality images. This setting applies to all images written - except if the capture image has caused an alarm and the alarm - file quality option is set at a higher value when that is used - instead. - "), - type => $types{integer}, - category => "images", - }, - { - name => "ZM_JPEG_ALARM_FILE_QUALITY", - default => "0", - description => "Set the JPEG quality setting for the saved event files during an alarm (1-100)", - help => qqq(" - This value is equivalent to the regular jpeg file quality - setting above except that it only applies to images saved while - in an alarm state and then only if this value is set to a - higher quality setting than the ordinary file setting. If set - to a lower value then it is ignored. Thus leaving it at the - default of 0 effectively means to use the regular file quality - setting for all saved images. This is to prevent acccidentally - saving important images at a worse quality setting. - "), - type => $types{integer}, - category => "images", - }, - # Deprecated, now stream quality - { - name => "ZM_JPEG_IMAGE_QUALITY", - default => "70", - description => "Set the JPEG quality setting for the streamed 'live' images (1-100)", - help => qqq(" - When viewing a 'live' stream for a monitor ZoneMinder will grab - an image from the buffer and encode it into JPEG format before - sending it. This option specifies what image quality should be - used to encode these images. A higher number means better - quality but less compression so will take longer to view over a - slow connection. By contrast a low number means quicker to view - images but at the price of lower quality images. This option - does not apply when viewing events or still images as these are - usually just read from disk and so will be encoded at the - quality specified by the previous options. - "), - type => $types{integer}, - category => "hidden", - }, - { - name => "ZM_JPEG_STREAM_QUALITY", - default => "70", - description => "Set the JPEG quality setting for the streamed 'live' images (1-100)", - help => qqq(" - When viewing a 'live' stream for a monitor ZoneMinder will grab - an image from the buffer and encode it into JPEG format before - sending it. This option specifies what image quality should be - used to encode these images. A higher number means better - quality but less compression so will take longer to view over a - slow connection. By contrast a low number means quicker to view - images but at the price of lower quality images. This option - does not apply when viewing events or still images as these are - usually just read from disk and so will be encoded at the - quality specified by the previous options. - "), - type => $types{integer}, - category => "images", - }, - { - name => "ZM_MPEG_TIMED_FRAMES", - default => "yes", - description => "Tag video frames with a timestamp for more realistic streaming", - help => qqq(" - When using streamed MPEG based video, either for live monitor - streams or events, ZoneMinder can send the streams in two ways. - If this option is selected then the timestamp for each frame, - taken from it's capture time, is included in the stream. This - means that where the frame rate varies, for instance around an - alarm, the stream will still maintain it's 'real' timing. If - this option is not selected then an approximate frame rate is - calculated and that is used to schedule frames instead. This - option should be selected unless you encounter problems with - your preferred streaming method. - "), - type => $types{boolean}, - category => "images", - }, - { - name => "ZM_MPEG_LIVE_FORMAT", - default => "swf", - description => "What format 'live' video streams are played in", - help => qqq(" - When using MPEG mode ZoneMinder can output live video. However - what formats are handled by the browser varies greatly between - machines. This option allows you to specify a video format - using a file extension format, so you would just enter the - extension of the file type you would like and the rest is - determined from that. The default of 'asf' works well under - Windows with Windows Media Player but I'm currently not sure - what, if anything, works on a Linux platform. If you find out - please let me know! If this option is left blank then live - streams will revert to being in motion jpeg format - "), - type => $types{string}, - category => "images", - }, - { - name => "ZM_MPEG_REPLAY_FORMAT", - default => "swf", - description => "What format 'replay' video streams are played in", - help => qqq(" - When using MPEG mode ZoneMinder can replay events in encoded - video format. However what formats are handled by the browser - varies greatly between machines. This option allows you to - specify a video format using a file extension format, so you - would just enter the extension of the file type you would like - and the rest is determined from that. The default of 'asf' - works well under Windows with Windows Media Player and 'mpg', - or 'avi' etc should work under Linux. If you know any more then - please let me know! If this option is left blank then live - streams will revert to being in motion jpeg format - "), - type => $types{string}, - category => "images", - }, - { - name => "ZM_RAND_STREAM", - default => "yes", - description => "Add a random string to prevent caching of streams", - help => qqq(" - Some browsers can cache the streams used by ZoneMinder. In - order to prevent his a harmless random string can be appended - to the url to make each invocation of the stream appear unique. - "), - type => $types{boolean}, - category => "images", - }, - { - name => "ZM_OPT_CAMBOZOLA", - default => "no", - description => "Is the (optional) cambozola java streaming client installed", - help => qqq(" - Cambozola is a handy low fat cheese flavoured Java applet that - ZoneMinder uses to view image streams on browsers such as - Internet Explorer that don't natively support this format. If - you use this browser it is highly recommended to install this - from http://www.charliemouse.com/code/cambozola/ however if it - is not installed still images at a lower refresh rate can still - be viewed. - "), - type => $types{boolean}, - category => "images", - }, - { - name => "ZM_PATH_CAMBOZOLA", - default => "cambozola.jar", - description => "Web path to (optional) cambozola java streaming client", - help => qqq(" - Cambozola is a handy low fat cheese flavoured Java applet that - ZoneMinder uses to view image streams on browsers such as - Internet Explorer that don't natively support this format. If - you use this browser it is highly recommended to install this - from http://www.charliemouse.com/code/cambozola/ however if it - is not installed still images at a lower refresh rate can still - be viewed. Leave this as 'cambozola.jar' if cambozola is - installed in the same directory as the ZoneMinder web client - files. - "), - requires => [ { name=>"ZM_OPT_CAMBOZOLA", value=>"yes" } ], - type => $types{rel_path}, - category => "images", - }, - { - name => "ZM_RELOAD_CAMBOZOLA", - default => "0", - description => "After how many seconds should Cambozola be reloaded in live view", - help => qqq(" - Cambozola allows for the viewing of streaming MJPEG however it - caches the entire stream into cache space on the computer, - setting this to a number > 0 will cause it to automatically - reload after that many seconds to avoid filling up a hard - drive. - "), - type => $types{integer}, - category => "images", - }, - { - name => "ZM_TIMESTAMP_ON_CAPTURE", - default => "yes", - description => "Timestamp images as soon as they are captured", - help => qqq(" - ZoneMinder can add a timestamp to images in two ways. The - default method, when this option is set, is that each image is - timestamped immediately when captured and so the image held in - memory is marked right away. The second method does not - timestamp the images until they are either saved as part of an - event or accessed over the web. The timestamp used in both - methods will contain the same time as this is preserved along - with the image. The first method ensures that an image is - timestamped regardless of any other circumstances but will - result in all images being timestamped even those never saved - or viewed. The second method necessitates that saved images are - copied before being saved otherwise two timestamps perhaps at - different scales may be applied. This has the (perhaps) - desirable side effect that the timestamp is always applied at - the same resolution so an image that has scaling applied will - still have a legible and correctly scaled timestamp. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_CPU_EXTENSIONS", - default => "yes", - description => "Use advanced CPU extensions to increase performance", - help => qqq(" - When advanced processor extensions such as SSE2 or SSSE3 are - available, ZoneMinder can use them, which should increase - performance and reduce system load. Enabling this option on - processors that do not support the advanced processors - extensions used by ZoneMinder is harmless and will have no - effect. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_FAST_IMAGE_BLENDS", - default => "yes", - description => "Use a fast algorithm to blend the reference image", - help => qqq(" - To detect alarms ZoneMinder needs to blend the captured image - with the stored reference image to update it for comparison - with the next image. The reference blend percentage specified - for the monitor controls how much the new image affects the - reference image. There are two methods that are available for - this. If this option is set then fast calculation which does - not use any multiplication or division is used. This - calculation is extremely fast, however it limits the possible - blend percentages to 50%, 25%, 12.5%, 6.25%, 3.25% and 1.5%. - Any other blend percentage will be rounded to the nearest - possible one. The alternative is to switch this option off - and use standard blending instead, which is slower. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_OPT_ADAPTIVE_SKIP", - default => "yes", - description => "Should frame analysis try and be efficient in skipping frames", - help => qqq(" - In previous versions of ZoneMinder the analysis daemon would - attempt to keep up with the capture daemon by processing the - last captured frame on each pass. This would sometimes have the - undesirable side-effect of missing a chunk of the initial - activity that caused the alarm because the pre-alarm frames - would all have to be written to disk and the database before - processing the next frame, leading to some delay between the - first and second event frames. Setting this option enables a - newer adaptive algorithm where the analysis daemon attempts to - process as many captured frames as possible, only skipping - frames when in danger of the capture daemon overwriting yet to - be processed frames. This skip is variable depending on the - size of the ring buffer and the amount of space left in it. - Enabling this option will give you much better coverage of the - beginning of alarms whilst biasing out any skipped frames - towards the middle or end of the event. However you should be - aware that this will have the effect of making the analysis - daemon run somewhat behind the capture daemon during events and - for particularly fast rates of capture it is possible for the - adaptive algorithm to be overwhelmed and not have time to react - to a rapid build up of pending frames and thus for a buffer - overrun condition to occur. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_MAX_SUSPEND_TIME", - default => "30", - description => "Maximum time that a monitor may have motion detection suspended", - help => qqq(" - ZoneMinder allows monitors to have motion detection to be - suspended, for instance while panning a camera. Ordinarily this - relies on the operator resuming motion detection afterwards as - failure to do so can leave a monitor in a permanently suspended - state. This setting allows you to set a maximum time which a - camera may be suspended for before it automatically resumes - motion detection. This time can be extended by subsequent - suspend indications after the first so continuous camera - movement will also occur while the monitor is suspended. - "), - type => $types{integer}, - category => "config", - }, - # Deprecated, really no longer necessary - { - name => "ZM_OPT_REMOTE_CAMERAS", - default => "no", - description => "Are you going to use remote/networked cameras", - help => qqq(" - ZoneMinder can work with both local cameras, ie. those attached - physically to your computer and remote or network cameras. If - you will be using networked cameras select this option. - "), - type => $types{boolean}, - category => "hidden", - }, - # Deprecated, now set on a per monitor basis using the Method field - { - name => "ZM_NETCAM_REGEXPS", - default => "yes", - description => "Use regular expression matching with network cameras", - help => qqq(" - Traditionally ZoneMinder has used complex regular regular - expressions to handle the multitude of formats that network - cameras produce. In versions from 1.21.1 the default is to use - a simpler and faster built in pattern matching methodology. - This works well with most networks cameras but if you have - problems you can try the older, but more flexible, regular - expression based method by selecting this option. Note, to use - this method you must have libpcre installed on your system. - "), - requires => [ { name => "ZM_OPT_REMOTE_CAMERAS", value => "yes" } ], - type => $types{boolean}, - category => "hidden", - }, - { - name => "ZM_HTTP_VERSION", - default => "1.0", - description => "The version of HTTP that ZoneMinder will use to connect", - help => qqq(" - ZoneMinder can communicate with network cameras using either of - the HTTP/1.1 or HTTP/1.0 standard. A server will normally fall - back to the version it supports with no problem so this should - usually by left at the default. However it can be changed to - HTTP/1.0 if necessary to resolve particular issues. - "), - type => { - db_type =>"string", - hint =>"1.1|1.0", - pattern =>qr|^(1\.[01])$|, - format =>q( $1?$1:"" ) - }, - category => "network", - }, - { - name => "ZM_HTTP_UA", - default => "ZoneMinder", - description => "The user agent that ZoneMinder uses to identify itself", - help => qqq(" - When ZoneMinder communicates with remote cameras it will - identify itself using this string and it's version number. This - is normally sufficient, however if a particular cameras expects - only to communicate with certain browsers then this can be - changed to a different string identifying ZoneMinder as - Internet Explorer or Netscape etc. - "), - type => $types{string}, - category => "network", - }, - { - name => "ZM_HTTP_TIMEOUT", - default => "2500", - description => "How long ZoneMinder waits before giving up on images (milliseconds)", - help => qqq(" - When retrieving remote images ZoneMinder will wait for this - length of time before deciding that an image is not going to - arrive and taking steps to retry. This timeout is in - milliseconds (1000 per second) and will apply to each part of - an image if it is not sent in one whole chunk. - "), - type => $types{integer}, - category => "network", - }, - { - name => "ZM_MIN_RTP_PORT", - default => "40200", - description => "Minimum port that ZoneMinder will listen for RTP traffic on", - help => qqq(" - When ZoneMinder communicates with MPEG4 capable cameras using - RTP with the unicast method it must open ports for the camera - to connect back to for control and streaming purposes. This - setting specifies the minimum port number that ZoneMinder will - use. Ordinarily two adjacent ports are used for each camera, - one for control packets and one for data packets. This port - should be set to an even number, you may also need to open up a - hole in your firewall to allow cameras to connect back if you - wish to use unicasting. - "), - type => $types{integer}, - category => "network", - }, - { - name => "ZM_MAX_RTP_PORT", - default => "40499", - description => "Maximum port that ZoneMinder will listen for RTP traffic on", - help => qqq(" - When ZoneMinder communicates with MPEG4 capable cameras using - RTP with the unicast method it must open ports for the camera - to connect back to for control and streaming purposes. This - setting specifies the maximum port number that ZoneMinder will - use. Ordinarily two adjacent ports are used for each camera, - one for control packets and one for data packets. This port - should be set to an even number, you may also need to open up a - hole in your firewall to allow cameras to connect back if you - wish to use unicasting. You should also ensure that you have - opened up at least two ports for each monitor that will be - connecting to unicasting network cameras. - "), - type => $types{integer}, - category => "network", - }, - { - name => "ZM_OPT_FFMPEG", - default => "@OPT_FFMPEG@", - description => "Is the ffmpeg video encoder/decoder installed", - help => qqq(" - ZoneMinder can optionally encode a series of video images into - an MPEG encoded movie file for viewing, downloading or storage. - This option allows you to specify whether you have the ffmpeg - tools installed. Note that creating MPEG files can be fairly - CPU and disk intensive and is not a required option as events - can still be reviewed as video streams without it. - "), - type => $types{boolean}, - category => "images", - }, - { - name => "ZM_PATH_FFMPEG", - default => "@PATH_FFMPEG@", - description => "Path to (optional) ffmpeg mpeg encoder", - help => "This path should point to where ffmpeg has been installed.", - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], - type => $types{abs_path}, - category => "images", - }, - { - name => "ZM_FFMPEG_INPUT_OPTIONS", - default => "", - description => "Additional input options to ffmpeg", - help => qqq(" - Ffmpeg can take many options on the command line to control the - quality of video produced. This option allows you to specify - your own set that apply to the input to ffmpeg (options that - are given before the -i option). Check the ffmpeg documentation - for a full list of options which may be used here. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], - type => $types{string}, - category => "images", - }, - { - name => "ZM_FFMPEG_OUTPUT_OPTIONS", - default => "-r 25", - description => "Additional output options to ffmpeg", - help => qqq(" - Ffmpeg can take many options on the command line to control the - quality of video produced. This option allows you to specify - your own set that apply to the output from ffmpeg (options that - are given after the -i option). Check the ffmpeg documentation - for a full list of options which may be used here. The most - common one will often be to force an output frame rate - supported by the video encoder. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], - type => $types{string}, - category => "images", - }, - { - name => "ZM_FFMPEG_FORMATS", - default => "mpg mpeg wmv asf avi* mov swf 3gp**", - description => "Formats to allow for ffmpeg video generation", - help => qqq(" - Ffmpeg can generate video in many different formats. This - option allows you to list the ones you want to be able to - select. As new formats are supported by ffmpeg you can add them - here and be able to use them immediately. Adding a '*' after a - format indicates that this will be the default format used for - web video, adding '**' defines the default format for phone - video. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], - type => $types{string}, - category => "images", - }, - { - name => "ZM_FFMPEG_OPEN_TIMEOUT", - default => "10", - description => "Timeout in seconds when opening a stream.", - help => qqq(" - When Ffmpeg is opening a stream, it can take a long time before - failing; certain circumstances even seem to be able to lock - indefinitely. This option allows you to set a maximum time in - seconds to pass before closing the stream and trying to reopen - it again. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], - type => $types{integer}, - category => "images", - }, - { - name => "ZM_LOG_LEVEL_SYSLOG", - default => "0", - description => "Save logging output to the system log", - help => qqq(" - ZoneMinder logging is now more more integrated between - components and allows you to specify the destination for - logging output and the individual levels for each. This option - lets you control the level of logging output that goes to the - system log. ZoneMinder binaries have always logged to the - system log but now scripts and web logging is also included. To - preserve the previous behaviour you should ensure this value is - set to Info or Warning. This option controls the maximum level - of logging that will be written, so Info includes Warnings and - Errors etc. To disable entirely, set this option to None. You - should use caution when setting this option to Debug as it can - affect severely affect system performance. If you want debug - you will also need to set a level and component below - "), - type => { - db_type =>"integer", - hint =>"None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "logging", - }, - { - name => "ZM_LOG_LEVEL_FILE", - default => "-5", - description => "Save logging output to component files", - help => qqq(" - ZoneMinder logging is now more more integrated between - components and allows you to specify the destination for - logging output and the individual levels for each. This option - lets you control the level of logging output that goes to - individual log files written by specific components. This is - how logging worked previously and although useful for tracking - down issues in specific components it also resulted in many - disparate log files. To preserve this behaviour you should - ensure this value is set to Info or Warning. This option - controls the maximum level of logging that will be written, so - Info includes Warnings and Errors etc. To disable entirely, set - this option to None. You should use caution when setting this - option to Debug as it can affect severely affect system - performance though file output has less impact than the other - options. If you want debug you will also need to set a level - and component below - "), - type => { - db_type =>"integer", - hint =>"None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "logging", - }, - { - name => "ZM_LOG_LEVEL_WEBLOG", - default => "-5", - description => "Save logging output to the weblog", - help => qqq(" - ZoneMinder logging is now more more integrated between - components and allows you to specify the destination for - logging output and the individual levels for each. This option - lets you control the level of logging output from the web - interface that goes to the httpd error log. Note that only web - logging from PHP and JavaScript files is included and so this - option is really only useful for investigating specific issues - with those components. This option controls the maximum level - of logging that will be written, so Info includes Warnings and - Errors etc. To disable entirely, set this option to None. You - should use caution when setting this option to Debug as it can - affect severely affect system performance. If you want debug - you will also need to set a level and component below - "), - type => { - db_type =>"integer", - hint =>"None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "logging", - }, - { - name => "ZM_LOG_LEVEL_DATABASE", - default => "0", - description => "Save logging output to the database", - help => qqq(" - ZoneMinder logging is now more more integrated between - components and allows you to specify the destination for - logging output and the individual levels for each. This option - lets you control the level of logging output that is written to - the database. This is a new option which can make viewing - logging output easier and more intuitive and also makes it - easier to get an overall impression of how the system is - performing. If you have a large or very busy system then it is - possible that use of this option may slow your system down if - the table becomes very large. Ensure you use the - LOG_DATABASE_LIMIT option to keep the table to a manageable - size. This option controls the maximum level of logging that - will be written, so Info includes Warnings and Errors etc. To - disable entirely, set this option to None. You should use - caution when setting this option to Debug as it can affect - severely affect system performance. If you want debug you will - also need to set a level and component below - "), - type => { - db_type =>"integer", - hint =>"None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "logging", - }, - { - name => "ZM_LOG_DATABASE_LIMIT", - default => "7 day", - description => "Maximum number of log entries to retain", - help => qqq(" - If you are using database logging then it is possible to - quickly build up a large number of entries in the Logs table. - This option allows you to specify how many of these entries are - kept. If you set this option to a number greater than zero then - that number is used to determine the maximum number of rows, - less than or equal to zero indicates no limit and is not - recommended. You can also set this value to time values such as - ' day' which will limit the log entries to those newer than - that time. You can specify 'hour', 'day', 'week', 'month' and - 'year', note that the values should be singular (no 's' at the - end). The Logs table is pruned periodically so it is possible - for more than the expected number of rows to be present briefly - in the meantime. - "), - type => $types{string}, - category => "logging", - }, - { - name => "ZM_LOG_DEBUG", - default => "no", - description => "Switch debugging on", - help => qqq(" - ZoneMinder components usually support debug logging available - to help with diagnosing problems. Binary components have - several levels of debug whereas more other components have only - one. Normally this is disabled to minimise performance - penalties and avoid filling logs too quickly. This option lets - you switch on other options that allow you to configure - additional debug information to be output. Components will pick - up this instruction when they are restarted. - "), - type => $types{boolean}, - category => "logging", - }, - { - name => "ZM_LOG_DEBUG_TARGET", - default => "", - description => "What components should have extra debug enabled", - help => qqq(" - There are three scopes of debug available. Leaving this option - blank means that all components will use extra debug (not - recommended). Setting this option to '_', e.g. _zmc, - will limit extra debug to that component only. Setting this - option to '__', e.g. '_zmc_m1' will limit - extra debug to that instance of the component only. This is - ordinarily what you probably want to do. To debug scripts use - their names without the .pl extension, e.g. '_zmvideo' and to - debug issues with the web interface use '_web'. You can specify - multiple targets by separating them with '|' characters. - "), - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], - type => $types{string}, - category => "logging", - }, - { - name => "ZM_LOG_DEBUG_LEVEL", - default => 1, - description => "What level of extra debug should be enabled", - help => qqq(" - There are 9 levels of debug available, with higher numbers - being more debug and level 0 being no debug. However not all - levels are used by all components. Also if there is debug at a - high level it is usually likely to be output at such a volume - that it may obstruct normal operation. For this reason you - should set the level carefully and cautiously until the degree - of debug you wish to see is present. Scripts and the web - interface only have one level so this is an on/off type option - for them. - "), - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], - type => { - db_type =>"integer", - hint =>"1|2|3|4|5|6|7|8|9", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "logging", - }, - { - name => "ZM_LOG_DEBUG_FILE", - default => "@ZM_LOGDIR@/zm_debug.log+", - description => "Where extra debug is output to", - help => qqq(" - This option allows you to specify a different target for debug - output. All components have a default log file which will - norally be in /tmp or /var/log and this is where debug will be - written to if this value is empty. Adding a path here will - temporarily redirect debug, and other logging output, to this - file. This option is a simple filename and you are debugging - several components then they will all try and write to the same - file with undesirable consequences. Appending a '+' to the - filename will cause the file to be created with a '.' - suffix containing your process id. In this way debug from each - run of a component is kept separate. This is the recommended - setting as it will also prevent subsequent runs from - overwriting the same log. You should ensure that permissions - are set up to allow writing to the file and directory specified - here. - "), - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], - type => $types{string}, - category => "logging", - }, - { - name => "ZM_LOG_CHECK_PERIOD", - default => "900", - description => "Time period used when calculating overall system health", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to indicate what period of historical - events are used in this calculation. This value is expressed in - seconds and is ignored if LOG_LEVEL_DATABASE is set to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_LOG_ALERT_WAR_COUNT", - default => "1", - description => "Number of warnings indicating system alert state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many warnings must have - occurred within the defined time period to generate an overall - system alert state. A value of zero means warnings are not - considered. This value is ignored if LOG_LEVEL_DATABASE is set - to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_LOG_ALERT_ERR_COUNT", - default => "1", - description => "Number of errors indicating system alert state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many errors must have - occurred within the defined time period to generate an overall - system alert state. A value of zero means errors are not - considered. This value is ignored if LOG_LEVEL_DATABASE is set - to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_LOG_ALERT_FAT_COUNT", - default => "0", - description => "Number of fatal error indicating system alert state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many fatal errors - (including panics) must have occurred within the defined time - period to generate an overall system alert state. A value of - zero means fatal errors are not considered. This value is - ignored if LOG_LEVEL_DATABASE is set to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_LOG_ALARM_WAR_COUNT", - default => "100", - description => "Number of warnings indicating system alarm state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many warnings must have - occurred within the defined time period to generate an overall - system alarm state. A value of zero means warnings are not - considered. This value is ignored if LOG_LEVEL_DATABASE is set - to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_LOG_ALARM_ERR_COUNT", - default => "10", - description => "Number of errors indicating system alarm state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many errors must have - occurred within the defined time period to generate an overall - system alarm state. A value of zero means errors are not - considered. This value is ignored if LOG_LEVEL_DATABASE is set - to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_LOG_ALARM_FAT_COUNT", - default => "1", - description => "Number of fatal error indicating system alarm state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many fatal errors - (including panics) must have occurred within the defined time - period to generate an overall system alarm state. A value of - zero means fatal errors are not considered. This value is - ignored if LOG_LEVEL_DATABASE is set to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_RECORD_EVENT_STATS", - default => "yes", - description => "Record event statistical information, switch off if too slow", - help => qqq(" - This version of ZoneMinder records detailed information about - events in the Stats table. This can help in profiling what the - optimum settings are for Zones though this is tricky at - present. However in future releases this will be done more - easily and intuitively, especially with a large sample of - events. The default option of 'yes' allows this information to - be collected now in readiness for this but if you are concerned - about performance you can switch this off in which case no - Stats information will be saved. - "), - type => $types{boolean}, - category => "logging", - }, - { - name => "ZM_RECORD_DIAG_IMAGES", - default => "no", - description => "Record intermediate alarm diagnostic images, can be very slow", - help => qqq(" - In addition to recording event statistics you can also record - the intermediate diagnostic images that display the results of - the various checks and processing that occur when trying to - determine if an alarm event has taken place. There are several - of these images generated for each frame and zone for each - alarm or alert frame so this can have a massive impact on - performance. Only switch this setting on for debug or analysis - purposes and remember to switch it off again once no longer - required. - "), - type => $types{boolean}, - category => "logging", - }, - { - name => "ZM_DUMP_CORES", - default => "no", - description => "Create core files on unexpected process failure.", - help => qqq(" - When an unrecoverable error occurs in a ZoneMinder binary - process is has traditionally been trapped and the details - written to logs to aid in remote analysis. However in some - cases it is easier to diagnose the error if a core file, which - is a memory dump of the process at the time of the error, is - created. This can be interactively analysed in the debugger and - may reveal more or better information than that available from - the logs. This option is recommended for advanced users only - otherwise leave at the default. Note using this option to - trigger core files will mean that there will be no indication - in the binary logs that a process has died, they will just - stop, however the zmdc log will still contain an entry. Also - note that you may have to explicitly enable core file creation - on your system via the 'ulimit -c' command or other means - otherwise no file will be created regardless of the value of - this option. - "), - type => $types{boolean}, - category => "logging", - }, - { - name => "ZM_PATH_MAP", - default => "/dev/shm", - description => "Path to the mapped memory files that that ZoneMinder can use", - help => qqq(" - ZoneMinder has historically used IPC shared memory for shared - data between processes. This has it's advantages and - limitations. This version of ZoneMinder can use an alternate - method, mapped memory, instead with can be enabled with the - --enable--mmap directive to configure. This requires less - system configuration and is generally more flexible. However it - requires each shared data segment to map onto a filesystem - file. This option indicates where those mapped files go. You - should ensure that this location has sufficient space for these - files and for the best performance it should be a tmpfs file - system or ramdisk otherwise disk access may render this method - slower than the regular shared memory one. - "), - type => $types{abs_path}, - category => "paths", - }, - { - name => "ZM_PATH_SOCKS", - default => "@ZM_SOCKDIR@", - description => "Path to the various Unix domain socket files that ZoneMinder uses", - help => qqq(" - ZoneMinder generally uses Unix domain sockets where possible. - This reduces the need for port assignments and prevents - external applications from possibly compromising the daemons. - However each Unix socket requires a .sock file to be created. - This option indicates where those socket files go. - "), - type => $types{abs_path}, - category => "paths", - }, - { - name => "ZM_PATH_LOGS", - default => "@ZM_LOGDIR@", - description => "Path to the various logs that the ZoneMinder daemons generate", - help => qqq(" - There are various daemons that are used by ZoneMinder to - perform various tasks. Most generate helpful log files and this - is where they go. They can be deleted if not required for - debugging. - "), - type => $types{abs_path}, - category => "paths", - }, - { - name => "ZM_PATH_SWAP", - default => "@ZM_TMPDIR@", - description => "Path to location for temporary swap images used in streaming", - help => qqq(" - Buffered playback requires temporary swap images to be stored - for each instance of the streaming daemons. This option - determines where these images will be stored. The images will - actually be stored in sub directories beneath this location and - will be automatically cleaned up after a period of time. - "), - type => $types{abs_path}, - category => "paths", - }, - { - name => "ZM_PATH_ARP", - default => "", - description => "Path to a supported ARP tool", - help => qqq(" - The camera probe function uses Address Resolution Protocol in - order to find known devices on the network. Optionally supply - the full path to \"ip neigh\", \"arp -a\", or any other tool on - your system that returns ip/mac address pairs. If this field is - left empty, ZoneMinder will search for the command \"arp\" and - attempt to use that. - "), - type => $types{abs_path}, - category => "paths", - }, - { - name => "ZM_WEB_TITLE_PREFIX", - default => "ZM", - description => "The title prefix displayed on each window", - help => qqq(" - If you have more than one installation of ZoneMinder it can be - helpful to display different titles for each one. Changing this - option allows you to customise the window titles to include - further information to aid identification. - "), - type => $types{string}, - category => "web", - }, - { - name => "ZM_WEB_RESIZE_CONSOLE", - default => "yes", - description => "Should the console window resize itself to fit", - help => qqq(" - Traditionally the main ZoneMinder web console window has - resized itself to shrink to a size small enough to list only - the monitors that are actually present. This is intended to - make the window more unobtrusize but may not be to everyones - tastes, especially if opened in a tab in browsers which support - this kind if layout. Switch this option off to have the console - window size left to the users preference - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_WEB_POPUP_ON_ALARM", - default => "yes", - description => "Should the monitor window jump to the top if an alarm occurs", - help => qqq(" - When viewing a live monitor stream you can specify whether you - want the window to pop to the front if an alarm occurs when the - window is minimised or behind another window. This is most - useful if your monitors are over doors for example when they - can pop up if someone comes to the doorway. - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_OPT_X10", - default => "no", - description => "Support interfacing with X10 devices", - help => qqq(" - If you have an X10 Home Automation setup in your home you can - use ZoneMinder to initiate or react to X10 signals if your - computer has the appropriate interface controller. This option - indicates whether X10 options will be available in the browser - client. - "), - type => $types{boolean}, - category => "x10", - }, - { - name => "ZM_X10_DEVICE", - default => "/dev/ttyS0", - description => "What device is your X10 controller connected on", - requires => [ { name => "ZM_OPT_X10", value => "yes" } ], - help => qqq(" - If you have an X10 controller device (e.g. XM10U) connected to - your computer this option details which port it is connected on, - the default of /dev/ttyS0 maps to serial or com port 1. - "), - type => $types{abs_path}, - category => "x10", - }, - { - name => "ZM_X10_HOUSE_CODE", - default => "A", - description => "What X10 house code should be used", - requires => [ { name => "ZM_OPT_X10", value => "yes" } ], - help => qqq(" - X10 devices are grouped together by identifying them as all - belonging to one House Code. This option details what that is. - It should be a single letter between A and P. - "), - type => { db_type=>"string", hint=>"A-P", pattern=>qr|^([A-P])|i, format=>q( uc($1) ) }, - category => "x10", - }, - { - name => "ZM_X10_DB_RELOAD_INTERVAL", - default => "60", - description => "How often (in seconds) the X10 daemon reloads the monitors from the database", - requires => [ { name => "ZM_OPT_X10", value => "yes" } ], - help => qqq(" - The zmx10 daemon periodically checks the database to find out - what X10 events trigger, or result from, alarms. This option - determines how frequently this check occurs, unless you change - this area frequently this can be a fairly large value. - "), - type => $types{integer}, - category => "x10", - }, - { - name => "ZM_WEB_SOUND_ON_ALARM", - default => "no", - description => "Should the monitor window play a sound if an alarm occurs", - help => qqq(" - When viewing a live monitor stream you can specify whether you - want the window to play a sound to alert you if an alarm - occurs. - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_WEB_ALARM_SOUND", - default => "", - description => "The sound to play on alarm, put this in the sounds directory", - help => qqq(" - You can specify a sound file to play if an alarm occurs whilst - you are watching a live monitor stream. So long as your browser - understands the format it does not need to be any particular - type. This file should be placed in the sounds directory - defined earlier. - "), - type => $types{file}, - requires => [ { name => "ZM_WEB_SOUND_ON_ALARM", value => "yes" } ], - category => "web", - }, - { - name => "ZM_WEB_COMPACT_MONTAGE", - default => "no", - description => "Compact the montage view by removing extra detail", - help => qqq(" - The montage view shows the output of all of your active - monitors in one window. This include a small menu and status - information for each one. This can increase the web traffic and - make the window larger than may be desired. Setting this option - on removes all this extraneous information and just displays - the images. - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_OPT_FAST_DELETE", - default => "yes", - description => "Delete only event database records for speed", - help => qqq(" - Normally an event created as the result of an alarm consists of - entries in one or more database tables plus the various files - associated with it. When deleting events in the browser it can - take a long time to remove all of this if your are trying to do - a lot of events at once. It is recommended that you set this - option which means that the browser client only deletes the key - entries in the events table, which means the events will no - longer appear in the listing, and leaves the zmaudit daemon to - clear up the rest later. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_STRICT_VIDEO_CONFIG", - default => "yes", - description => "Allow errors in setting video config to be fatal", - help => qqq(" - With some video devices errors can be reported in setting the - various video attributes when in fact the operation was - successful. Switching this option off will still allow these - errors to be reported but will not cause them to kill the video - capture daemon. Note however that doing this will cause all - errors to be ignored including those which are genuine and - which may cause the video capture to not function correctly. - Use this option with caution. - "), - type => $types{boolean}, - category => "config", - }, - { - name => 'ZM_LD_PRELOAD', - default => '', - description => "Path to library to preload before launching daemons", - help => qqq("Some older cameras require the use of the v4l1 compat - library. This setting allows the setting of the path - to the library, so that it can be loaded by zmdc.pl - before launching zmc."), - type => $types{abs_path}, - category => 'config', - }, - { - name => "ZM_SIGNAL_CHECK_POINTS", - default => "10", - description => "How many points in a captured image to check for signal loss", - help => qqq(" - For locally attached video cameras ZoneMinder can check for - signal loss by looking at a number of random points on each - captured image. If all of these points are set to the same - fixed colour then the camera is assumed to have lost signal. - When this happens any open events are closed and a short one - frame signal loss event is generated, as is another when the - signal returns. This option defines how many points on each - image to check. Note that this is a maximum, any points found - to not have the check colour will abort any further checks so - in most cases on a couple of points will actually be checked. - Network and file based cameras are never checked. - "), - type => $types{integer}, - category => "config", - }, - { - name => "ZM_V4L_MULTI_BUFFER", - default => "yes", - description => "Use more than one buffer for Video 4 Linux devices", - help => qqq(" - Performance when using Video 4 Linux devices is usually best if - multiple buffers are used allowing the next image to be - captured while the previous one is being processed. If you have - multiple devices on a card sharing one input that requires - switching then this approach can sometimes cause frames from - one source to be mixed up with frames from another. Switching - this option off prevents multi buffering resulting in slower - but more stable image capture. This option is ignored for - non-local cameras or if only one input is present on a capture - chip. This option addresses a similar problem to the - ZM_CAPTURES_PER_FRAME option and you should normally change the - value of only one of the options at a time. If you have - different capture cards that need different values you can - ovveride them in each individual monitor on the source page. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_CAPTURES_PER_FRAME", - default => "1", - description => "How many images are captured per returned frame, for shared local cameras", - help => qqq(" - If you are using cameras attached to a video capture card which - forces multiple inputs to share one capture chip, it can - sometimes produce images with interlaced frames reversed - resulting in poor image quality and a distinctive comb edge - appearance. Increasing this setting allows you to force - additional image captures before one is selected as the - captured frame. This allows the capture hardware to 'settle - down' and produce better quality images at the price of lesser - capture rates. This option has no effect on (a) network - cameras, or (b) where multiple inputs do not share a capture - chip. This option addresses a similar problem to the - ZM_V4L_MULTI_BUFFER option and you should normally change the - value of only one of the options at a time. If you have - different capture cards that need different values you can - ovveride them in each individual monitor on the source page. - "), - type => $types{integer}, - category => "config", - }, - { - name => "ZM_FILTER_RELOAD_DELAY", - default => "300", - description => "How often (in seconds) filters are reloaded in zmfilter", - help => qqq(" - ZoneMinder allows you to save filters to the database which - allow events that match certain criteria to be emailed, deleted - or uploaded to a remote machine etc. The zmfilter daemon loads - these and does the actual operation. This option determines how - often the filters are reloaded from the database to get the - latest versions or new filters. If you don't change filters - very often this value can be set to a large value. - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_FILTER_EXECUTE_INTERVAL", - default => "60", - description => "How often (in seconds) to run automatic saved filters", - help => qqq(" - ZoneMinder allows you to save filters to the database which - allow events that match certain criteria to be emailed, deleted - or uploaded to a remote machine etc. The zmfilter daemon loads - these and does the actual operation. This option determines how - often the filters are executed on the saved event in the - database. If you want a rapid response to new events this - should be a smaller value, however this may increase the - overall load on the system and affect performance of other - elements. - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_OPT_UPLOAD", - default => "no", - description => "Should ZoneMinder support uploading events from filters", - help => qqq(" - In ZoneMinder you can create event filters that specify whether - events that match certain criteria should be uploaded to a - remote server for archiving. This option specifies whether this - functionality should be available - "), - type => $types{boolean}, - category => "upload", - }, - { - name => "ZM_UPLOAD_ARCH_FORMAT", - default => "tar", - description => "What format the uploaded events should be created in.", - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - help => qqq(" - Uploaded events may be stored in either .tar or .zip format, - this option specifies which. Note that to use this you will - need to have the Archive::Tar and/or Archive::Zip perl modules - installed. - "), - type => { - db_type =>"string", - hint =>"tar|zip", - pattern =>qr|^([tz])|i, - format =>q( $1 =~ /^t/ ? "tar" : "zip" ) - }, - category => "upload", - }, - { - name => "ZM_UPLOAD_ARCH_COMPRESS", - default => "no", - description => "Should archive files be compressed", - help => qqq(" - When the archive files are created they can be compressed. - However in general since the images are compressed already this - saves only a minimal amount of space versus utilising more CPU - in their creation. Only enable if you have CPU to waste and are - limited in disk space on your remote server or bandwidth. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{boolean}, - category => "upload", - }, - { - name => "ZM_UPLOAD_ARCH_ANALYSE", - default => "no", - description => "Include the analysis files in the archive", - help => qqq(" - When the archive files are created they can contain either just - the captured frames or both the captured frames and, for frames - that caused an alarm, the analysed image with the changed area - highlighted. This option controls files are included. Only - include analysed frames if you have a high bandwidth connection - to the remote server or if you need help in figuring out what - caused an alarm in the first place as archives with these files - in can be considerably larger. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{boolean}, - category => "upload", - }, - { - name => "ZM_UPLOAD_PROTOCOL", - default => "ftp", - description => "What protocol to use to upload events", - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - help => qqq(" - ZoneMinder can upload events to a remote server using either - FTP or SFTP. Regular FTP is widely supported but not - necessarily very secure whereas SFTP (Secure FTP) runs over an - ssh connection and so is encrypted and uses regular ssh ports. - Note that to use this you will need to have the appropriate - perl module, either Net::FTP or Net::SFTP installed depending - on your choice. - "), - type => { - db_type =>"string", - hint =>"ftp|sftp", - pattern =>qr|^([tz])|i, - format =>q( $1 =~ /^f/ ? "ftp" : "sftp" ) - }, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_HOST", - default => "", - description => "The remote server to upload to", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the name, or ip - address, of the server to use. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{hostname}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_HOST", - default => "", - description => "The remote server to upload events to", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the name, or ip address, - of the server to use. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{hostname}, - category => "upload", - }, - { - name => "ZM_UPLOAD_PORT", - default => "", - description => "The port on the remote upload server, if not the default (SFTP only)", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. If you are using the SFTP protocol then this - option allows you to specify a particular port to use for - connection. If this option is left blank then the default, port - 22, is used. This option is ignored for FTP uploads. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{integer}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_USER", - default => "", - description => "Your ftp username", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the username that - ZoneMinder should use to log in for ftp transfer. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{alphanum}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_USER", - default => "", - description => "Remote server username", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the username that - ZoneMinder should use to log in for transfer. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{alphanum}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_PASS", - default => "", - description => "Your ftp password", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the password that - ZoneMinder should use to log in for ftp transfer. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{string}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_PASS", - default => "", - description => "Remote server password", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the password that - ZoneMinder should use to log in for transfer. If you are using - certificate based logins for SFTP servers you can leave this - option blank. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{string}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_LOC_DIR", - default => "@ZM_TMPDIR@", - description => "The local directory in which to create upload files", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the local directory - that ZoneMinder should use for temporary upload files. These - are files that are created from events, uploaded and then - deleted. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{abs_path}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_LOC_DIR", - default => "@ZM_TMPDIR@", - description => "The local directory in which to create upload files", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the local directory that - ZoneMinder should use for temporary upload files. These are - files that are created from events, uploaded and then deleted. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{abs_path}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_REM_DIR", - default => "", - description => "The remote directory to upload to", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the remote directory - that ZoneMinder should use to upload event files to. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{rel_path}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_REM_DIR", - default => "", - description => "The remote directory to upload to", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the remote directory - that ZoneMinder should use to upload event files to. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{rel_path}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_TIMEOUT", - default => "120", - description => "How long to allow the transfer to take for each file", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the maximum ftp - inactivity timeout (in seconds) that should be tolerated before - ZoneMinder determines that the transfer has failed and closes - down the connection. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{integer}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_TIMEOUT", - default => "120", - description => "How long to allow the transfer to take for each file", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the maximum inactivity - timeout (in seconds) that should be tolerated before ZoneMinder - determines that the transfer has failed and closes down the - connection. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{integer}, - category => "upload", - }, - { - name => "ZM_UPLOAD_STRICT", - default => "no", - description => "Require strict host key checking for SFTP uploads", - help => qqq(" - You can require SFTP uploads to verify the host key of the remote server - for protection against man-in-the-middle attacks. You will need to add the - server's key to the known_hosts file. On most systems, this will be - ~/.ssh/known_hosts, where ~ is the home directory of the web server running - ZoneMinder. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{boolean}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_PASSIVE", - default => "yes", - description => "Use passive ftp when uploading", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates that ftp transfers - should be done in passive mode. This uses a single connection - for all ftp activity and, whilst slower than active transfers, - is more robust and likely to work from behind filewalls. This - option is ignored for SFTP transfers. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - help => qqq(" - If your computer is behind a firewall or proxy you may need to - set FTP to passive mode. In fact for simple transfers it makes - little sense to do otherwise anyway but you can set this to - 'No' if you wish. - "), - type => $types{boolean}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_DEBUG", - default => "no", - description => "Switch ftp debugging on", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. If you are having (or expecting) troubles - with uploading events then setting this to 'yes' permits - additional information to be included in the zmfilter log file. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{boolean}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_DEBUG", - default => "no", - description => "Switch upload debugging on", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. If you are having (or expecting) troubles with - uploading events then setting this to 'yes' permits additional - information to be generated by the underlying transfer modules - and included in the logs. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{boolean}, - category => "upload", - }, - { - name => "ZM_OPT_EMAIL", - default => "no", - description => "Should ZoneMinder email you details of events that match corresponding filters", - help => qqq(" - In ZoneMinder you can create event filters that specify whether - events that match certain criteria should have their details - emailed to you at a designated email address. This will allow - you to be notified of events as soon as they occur and also to - quickly view the events directly. This option specifies whether - this functionality should be available. The email created with - this option can be any size and is intended to be sent to a - regular email reader rather than a mobile device. - "), - type => $types{boolean}, - category => "mail", - }, - { - name => "ZM_EMAIL_ADDRESS", - default => "", - description => "The email address to send matching event details to", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" - This option is used to define the email address that any events - that match the appropriate filters will be sent to. - "), - type => $types{email}, - category => "mail", - }, - { - name => "ZM_EMAIL_TEXT", - default => 'subject = "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)" -body = " -Hello, + The details are as follows :- -An alarm has been detected on your installation of the ZoneMinder. + Monitor : %MN% + Event Id : %EI% + Length : %EL% + Frames : %EF% (%EFA%) + Scores : t%EST% m%ESM% a%ESA% -The details are as follows :- + This alarm was matched by the %FN% filter and can be viewed at %EPS% - Monitor : %MN% - Event Id : %EI% - Length : %EL% - Frames : %EF% (%EFA%) - Scores : t%EST% m%ESM% a%ESA% + ZoneMinder"', + description => 'The text of the email used to send matching event details', + requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], + help => q` + This option is used to define the content of the email that is + sent for any events that match the appropriate filters. + `, + type => $types{text}, + category => 'hidden', + }, + { + name => 'ZM_EMAIL_SUBJECT', + default => 'ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)', + description => 'The subject of the email used to send matching event details', + requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], + help => q` + This option is used to define the subject of the email that is + sent for any events that match the appropriate filters. + `, + type => $types{string}, + category => 'mail', + }, + { + name => 'ZM_EMAIL_BODY', + default => ' + Hello, -This alarm was matched by the %FN% filter and can be viewed at %EPS% + An alarm has been detected on your installation of the ZoneMinder. -ZoneMinder"', - description => "The text of the email used to send matching event details", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" - This option is used to define the content of the email that is - sent for any events that match the appropriate filters. - "), - type => $types{text}, - category => "hidden", - }, - { - name => "ZM_EMAIL_SUBJECT", - default => "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)", - description => "The subject of the email used to send matching event details", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" - This option is used to define the subject of the email that is - sent for any events that match the appropriate filters. - "), - type => $types{string}, - category => "mail", - }, - { - name => "ZM_EMAIL_BODY", - default => " -Hello, + The details are as follows :- -An alarm has been detected on your installation of the ZoneMinder. + Monitor : %MN% + Event Id : %EI% + Length : %EL% + Frames : %EF% (%EFA%) + Scores : t%EST% m%ESM% a%ESA% -The details are as follows :- + This alarm was matched by the %FN% filter and can be viewed at %EPS% - Monitor : %MN% - Event Id : %EI% - Length : %EL% - Frames : %EF% (%EFA%) - Scores : t%EST% m%ESM% a%ESA% - -This alarm was matched by the %FN% filter and can be viewed at %EPS% - -ZoneMinder", - description => "The body of the email used to send matching event details", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" - This option is used to define the content of the email that is - sent for any events that match the appropriate filters. - "), - type => $types{text}, - category => "mail", - }, - { - name => "ZM_OPT_MESSAGE", - default => "no", - description => "Should ZoneMinder message you with details of events that match corresponding filters", - help => qqq(" - In ZoneMinder you can create event filters that specify whether - events that match certain criteria should have their details - sent to you at a designated short message email address. This - will allow you to be notified of events as soon as they occur. - This option specifies whether this functionality should be - available. The email created by this option will be brief and - is intended to be sent to an SMS gateway or a minimal mail - reader such as a mobile device or phone rather than a regular - email reader. - "), - type => $types{boolean}, - category => "mail", - }, - { - name => "ZM_MESSAGE_ADDRESS", - default => "", - description => "The email address to send matching event details to", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" - This option is used to define the short message email address - that any events that match the appropriate filters will be sent - to. - "), - type => $types{email}, - category => "mail", - }, - { - name => "ZM_MESSAGE_TEXT", - default => 'subject = "ZoneMinder: Alarm - %MN%-%EI%" -body = "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score."', - description => "The text of the message used to send matching event details", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" - This option is used to define the content of the message that - is sent for any events that match the appropriate filters. - "), - type => $types{text}, - category => "hidden", - }, - { - name => "ZM_MESSAGE_SUBJECT", - default => "ZoneMinder: Alarm - %MN%-%EI%", - description => "The subject of the message used to send matching event details", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" - This option is used to define the subject of the message that - is sent for any events that match the appropriate filters. - "), - type => $types{string}, - category => "mail", - }, - { - name => "ZM_MESSAGE_BODY", - default => "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.", - description => "The body of the message used to send matching event details", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" - This option is used to define the content of the message that - is sent for any events that match the appropriate filters. - "), - type => $types{text}, - category => "mail", - }, - { - name => "ZM_NEW_MAIL_MODULES", - default => "no", - description => "Use a newer perl method to send emails", - requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } - ], - help => qqq(" - Traditionally ZoneMinder has used the MIME::Entity perl module - to construct and send notification emails and messages. Some - people have reported problems with this module not being - present at all or flexible enough for their needs. If you are - one of those people this option allows you to select a new - mailing method using MIME::Lite and Net::SMTP instead. This - method was contributed by Ross Melin and should work for - everyone but has not been extensively tested so currently is - not selected by default. - "), - type => $types{boolean}, - category => "mail", - }, - { - name => "ZM_EMAIL_HOST", - default => "localhost", - description => "The host address of your SMTP mail server", - requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } - ], - help => qqq(" - If you have chosen SMTP as the method by which to send - notification emails or messages then this option allows you to - choose which SMTP server to use to send them. The default of - localhost may work if you have the sendmail, exim or a similar - daemon running however you may wish to enter your ISP's SMTP - mail server here. - "), - type => $types{hostname}, - category => "mail", - }, - { - name => "ZM_FROM_EMAIL", - default => "", - description => "The email address you wish your event notifications to originate from", - requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } - ], - help => qqq(" - The emails or messages that will be sent to you informing you - of events can appear to come from a designated email address to - help you with mail filtering etc. An address of something like - ZoneMinder\@your.domain is recommended. - "), - type => $types{email}, - category => "mail", - }, - { - name => "ZM_URL", - default => "", - description => "The URL of your ZoneMinder installation", - requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } - ], - help => qqq(" - The emails or messages that will be sent to you informing you - of events can include a link to the events themselves for easy - viewing. If you intend to use this feature then set this option - to the url of your installation as it would appear from where - you read your email, e.g. http://host.your.domain/zm.php. - "), - type => $types{url}, - category => "mail", - }, - { - name => "ZM_MAX_RESTART_DELAY", - default => "600", - description => "Maximum delay (in seconds) for daemon restart attempts.", - help => qqq(" - The zmdc (zm daemon control) process controls when processeses - are started or stopped and will attempt to restart any that - fail. If a daemon fails frequently then a delay is introduced - between each restart attempt. If the daemon stills fails then - this delay is increased to prevent extra load being placed on - the system by continual restarts. This option controls what - this maximum delay is. - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_WATCH_CHECK_INTERVAL", - default => "10", - description => "How often to check the capture daemons have not locked up", - help => qqq(" - The zmwatch daemon checks the image capture performance of the - capture daemons to ensure that they have not locked up (rarely - a sync error may occur which blocks indefinitely). This option - determines how often the daemons are checked. - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_WATCH_MAX_DELAY", - default => "5", - description => "The maximum delay allowed since the last captured image", - help => qqq(" - The zmwatch daemon checks the image capture performance of the - capture daemons to ensure that they have not locked up (rarely - a sync error may occur which blocks indefinitely). This option - determines the maximum delay to allow since the last captured - frame. The daemon will be restarted if it has not captured any - images after this period though the actual restart may take - slightly longer in conjunction with the check interval value - above. - "), - type => $types{decimal}, - category => "system", - }, - { - - name => "ZM_RUN_AUDIT", - default => "yes", - description => "Run zmaudit to check data consistency", - help => qqq(" - The zmaudit daemon exists to check that the saved information - in the database and on the filesystem match and are consistent - with each other. If an error occurs or if you are using 'fast - deletes' it may be that database records are deleted but files - remain. In this case, and similar, zmaudit will remove - redundant information to synchronise the two data stores. This - option controls whether zmaudit is run in the background and - performs these checks and fixes continuously. This is - recommended for most systems however if you have a very large - number of events the process of scanning the database and - filesystem may take a long time and impact performance. In this - case you may prefer to not have zmaudit running unconditionally - and schedule occasional checks at other, more convenient, - times. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_AUDIT_CHECK_INTERVAL", - default => "900", - description => "How often to check database and filesystem consistency", - help => qqq(" - The zmaudit daemon exists to check that the saved information - in the database and on the filesystem match and are consistent - with each other. If an error occurs or if you are using 'fast - deletes' it may be that database records are deleted but files - remain. In this case, and similar, zmaudit will remove - redundant information to synchronise the two data stores. The - default check interval of 900 seconds (15 minutes) is fine for - most systems however if you have a very large number of events - the process of scanning the database and filesystem may take a - long time and impact performance. In this case you may prefer - to make this interval much larger to reduce the impact on your - system. This option determines how often these checks are - performed. - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_AUDIT_MIN_AGE", - default => "86400", - description => "The minimum age in seconds event data must be in order to be deleted.", - help => qqq(" - The zmaudit daemon exists to check that the saved information - in the database and on the filesystem match and are consistent - with each other. Event files or db records that are younger than - this setting will not be deleted and a warning will be given. - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_FORCED_ALARM_SCORE", - default => "255", - description => "Score to give forced alarms", - help => qqq(" - The 'zmu' utility can be used to force an alarm on a monitor - rather than rely on the motion detection algorithms. This - option determines what score to give these alarms to - distinguish them from regular ones. It must be 255 or less. - "), - type => $types{integer}, - category => "config", - }, - { - name => "ZM_BULK_FRAME_INTERVAL", - default => "100", - description => "How often a bulk frame should be written to the database", - help => qqq(" - Traditionally ZoneMinder writes an entry into the Frames - database table for each frame that is captured and saved. This - works well in motion detection scenarios but when in a DVR - situation ('Record' or 'Mocord' mode) this results in a huge - number of frame writes and a lot of database and disk bandwidth - for very little additional information. Setting this to a - non-zero value will enabled ZoneMinder to group these non-alarm - frames into one 'bulk' frame entry which saves a lot of - bandwidth and space. The only disadvantage of this is that - timing information for individual frames is lost but in - constant frame rate situations this is usually not significant. - This setting is ignored in Modect mode and individual frames - are still written if an alarm occurs in Mocord mode also. - "), - type => $types{integer}, - category => "config", - }, - { - name => "ZM_EVENT_CLOSE_MODE", - default => "idle", - description => "When continuous events are closed.", - help => qqq(" - When a monitor is running in a continuous recording mode - (Record or Mocord) events are usually closed after a fixed - period of time (the section length). However in Mocord mode it - is possible that motion detection may occur near the end of a - section. This option controls what happens when an alarm occurs - in Mocord mode. The 'time' setting means that the event will be - closed at the end of the section regardless of alarm activity. - The 'idle' setting means that the event will be closed at the - end of the section if there is no alarm activity occurring at - the time otherwise it will be closed once the alarm is over - meaning the event may end up being longer than the normal - section length. The 'alarm' setting means that if an alarm - occurs during the event, the event will be closed once the - alarm is over regardless of when this occurs. This has the - effect of limiting the number of alarms to one per event and - the events will be shorter than the section length if an alarm - has occurred. - "), - type => $types{boolean}, - type => { - db_type =>"string", - hint =>"time|idle|alarm", - pattern =>qr|^([tia])|i, - format =>q( ($1 =~ /^t/) - ? "time" - : ($1 =~ /^i/ ? "idle" : "time" ) - ) - }, - category => "config", - }, - # Deprecated, superseded by event close mode - { - name => "ZM_FORCE_CLOSE_EVENTS", - default => "no", - description => "Close events at section ends.", - help => qqq(" - When a monitor is running in a continuous recording mode - (Record or Mocord) events are usually closed after a fixed - period of time (the section length). However in Mocord mode it - is possible that motion detection may occur near the end of a - section and ordinarily this will prevent the event being closed - until the motion has ceased. Switching this option on will - force the event closed at the specified time regardless of any - motion activity. - "), - type => $types{boolean}, - category => "hidden", - }, - { - name => "ZM_CREATE_ANALYSIS_IMAGES", - default => "yes", - description => "Create analysed alarm images with motion outlined", - help => qqq(" - By default during an alarm ZoneMinder records both the raw - captured image and one that has been analysed and had areas - where motion was detected outlined. This can be very useful - during zone configuration or in analysing why events occurred. - However it also incurs some overhead and in a stable system may - no longer be necessary. This parameter allows you to switch the - generation of these images off. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_WEIGHTED_ALARM_CENTRES", - default => "no", - description => "Use a weighted algorithm to calculate the centre of an alarm", - help => qqq(" - ZoneMinder will always calculate the centre point of an alarm - in a zone to give some indication of where on the screen it is. - This can be used by the experimental motion tracking feature or - your own custom extensions. In the alarmed or filtered pixels - mode this is a simple midpoint between the extents of the - detected pxiesl. However in the blob method this can instead be - calculated using weighted pixel locations to give more accurate - positioning for irregularly shaped blobs. This method, while - more precise is also slower and so is turned off by default. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_EVENT_IMAGE_DIGITS", - default => "5", - description => "How many significant digits are used in event image numbering", - help => qqq(" - As event images are captured they are stored to the filesystem - with a numerical index. By default this index has three digits - so the numbers start 001, 002 etc. This works works for most - scenarios as events with more than 999 frames are rarely - captured. However if you have extremely long events and use - external applications then you may wish to increase this to - ensure correct sorting of images in listings etc. Warning, - increasing this value on a live system may render existing - events unviewable as the event will have been saved with the - previous scheme. Decreasing this value should have no ill - effects. - "), - type => $types{integer}, - category => "config", - }, - { - name => "ZM_DEFAULT_ASPECT_RATIO", - default => "4:3", - description => "The default width:height aspect ratio used in monitors", - help => qqq(" - When specifying the dimensions of monitors you can click a - checkbox to ensure that the width stays in the correct ratio to - the height, or vice versa. This setting allows you to indicate - what the ratio of these settings should be. This should be - specified in the format : and the - default of 4:3 normally be acceptable but 11:9 is another - common setting. If the checkbox is not clicked when specifying - monitor dimensions this setting has no effect. - "), - type => $types{string}, - category => "config", - }, - { - name => "ZM_USER_SELF_EDIT", - default => "no", - description => "Allow unprivileged users to change their details", - help => qqq(" - Ordinarily only users with system edit privilege are able to - change users details. Switching this option on allows ordinary - users to change their passwords and their language settings - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_OPT_FRAME_SERVER", - default => "no", - description => "Should analysis farm out the writing of images to disk", - #requires => [ { name => "ZM_OPT_ADAPTIVE_SKIP", value => "yes" } ], - help => qqq(" - In some circumstances it is possible for a slow disk to take so - long writing images to disk that it causes the analysis daemon - to fall behind especially during high frame rate events. - Setting this option to yes enables a frame server daemon (zmf) - which will be sent the images from the analysis daemon and will - do the actual writing of images itself freeing up the analysis - daemon to get on with other things. Should this transmission - fail or other permanent or transient error occur, this function - will fall back to the analysis daemon. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_FRAME_SOCKET_SIZE", - default => "0", - description => "Specify the frame server socket buffer size if non-standard", - requires => [ { name => "ZM_OPT_FRAME_SERVER", value => "yes" } ], - help => qqq(" - For large captured images it is possible for the writes from - the analysis daemon to the frame server to fail as the amount - to be written exceeds the default buffer size. While the images - are then written by the analysis daemon so no data is lost, it - defeats the object of the frame server daemon in the first - place. You can use this option to indicate that a larger buffer - size should be used. Note that you may have to change the - existing maximum socket buffer size on your system via sysctl - (or in /proc/sys/net/core/wmem_max) to allow this new size to - be set. Alternatively you can change the default buffer size on - your system in the same way in which case that will be used - with no change necessary in this option - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_OPT_CONTROL", - default => "no", - description => "Support controllable (e.g. PTZ) cameras", - help => qqq(" - ZoneMinder includes limited support for controllable cameras. A - number of sample protocols are included and others can easily - be added. If you wish to control your cameras via ZoneMinder - then select this option otherwise if you only have static - cameras or use other control methods then leave this option - off. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_OPT_TRIGGERS", - default => "no", - description => "Interface external event triggers via socket or device files", - help => qqq(" - ZoneMinder can interact with external systems which prompt or - cancel alarms. This is done via the zmtrigger.pl script. This - option indicates whether you want to use these external - triggers. Most people will say no here. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_CHECK_FOR_UPDATES", - default => "yes", - description => "Check with zoneminder.com for updated versions", - help => qqq(" - From ZoneMinder version 1.17.0 onwards new versions are - expected to be more frequent. To save checking manually for - each new version ZoneMinder can check with the zoneminder.com - website to determine the most recent release. These checks are - infrequent, about once per week, and no personal or system - information is transmitted other than your current version - number. If you do not wish these checks to take place or your - ZoneMinder system has no internet access you can switch these - check off with this configuration variable - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_TELEMETRY_DATA", - default => "yes", - description => "Send usage information to ZoneMinder", - help => qqq(" - Enable collection of usage information of the local system and send - it to the ZoneMinder development team. This data will be used to - determine things like who and where our customers are, how big their - systems are, the underlying hardware and operating system, etc. - This is being done for the sole purpoase of creating a better - product for our target audience. This script is intended to be - completely transparent to the end user, and can be disabled from - the web console under Options. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_TELEMETRY_UUID", - default => "", - description => "Unique identifier for ZoneMinder telemetry", - help => qqq(" - This variable is auto-generated once by the system and is used to - uniquely identify it among all other ZoneMinder systems in - existence. - "), - type => $types{string}, - category => "dynamic", - }, - { - name => "ZM_TELEMETRY_LAST_UPLOAD", - default => "", - description => "When the last ZoneMinder telemetry upload ocurred", - help => "", - type => $types{integer}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_UPDATE_CHECK_PROXY", - default => "", - description => "Proxy url if required to access zoneminder.com", - help => qqq(" - If you use a proxy to access the internet then ZoneMinder needs - to know so it can access zoneminder.com to check for updates. - If you do use a proxy enter the full proxy url here in the form - of http://:/ - "), - type => $types{string}, - category => "system", - }, - { - name => "ZM_SHM_KEY", - default => "0x7a6d0000", - description => "Shared memory root key to use", - help => qqq(" - ZoneMinder uses shared memory to speed up communication between - modules. To identify the right area to use shared memory keys - are used. This option controls what the base key is, each - monitor will have it's Id or'ed with this to get the actual key - used. You will not normally need to change this value unless it - clashes with another instance of ZoneMinder on the same - machine. Only the first four hex digits are used, the lower - four will be masked out and ignored. - "), - type => $types{hexadecimal}, - category => "system", - }, - # Deprecated, really no longer necessary - { - name => "ZM_WEB_REFRESH_METHOD", - default => "javascript", - description => "What method windows should use to refresh themselves", - help => qqq(" - Many windows in Javascript need to refresh themselves to keep - their information current. This option determines what method - they should use to do this. Choosing 'javascript' means that - each window will have a short JavaScript statement in with a - timer to prompt the refresh. This is the most compatible - method. Choosing 'http' means the refresh instruction is put in - the HTTP header. This is a cleaner method but refreshes are - interrupted or cancelled when a link in the window is clicked - meaning that the window will no longer refresh and this would - have to be done manually. - "), - type => { - db_type =>"string", - hint =>"javascript|http", - pattern =>qr|^([jh])|i, - format =>q( $1 =~ /^j/ - ? "javascript" - : "http" - ) - }, - category => "hidden", - }, - { - name => "ZM_WEB_EVENT_SORT_FIELD", - default => "DateTime", - description => "Default field the event lists are sorted by", - help => qqq(" - Events in lists can be initially ordered in any way you want. - This option controls what field is used to sort them. You can - modify this ordering from filters or by clicking on headings in - the lists themselves. Bear in mind however that the 'Prev' and - 'Next' links, when scrolling through events, relate to the - ordering in the lists and so not always to time based ordering. - "), - type => { - db_type =>"string", - hint =>"Id|Name|Cause|MonitorName|DateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore", - pattern =>qr|.|, - format =>q( $1 ) - }, - category => "web", - }, - { - name => "ZM_WEB_EVENT_SORT_ORDER", - default => "asc", - description => "Default order the event lists are sorted by", - help => qqq(" - Events in lists can be initially ordered in any way you want. - This option controls what order (ascending or descending) is - used to sort them. You can modify this ordering from filters or - by clicking on headings in the lists themselves. Bear in mind - however that the 'Prev' and 'Next' links, when scrolling - through events, relate to the ordering in the lists and so not - always to time based ordering. - "), - type => { - db_type =>"string", - hint =>"asc|desc", - pattern =>qr|^([ad])|i, - format =>q( $1 =~ /^a/i ? "asc" : "desc" ) - }, - category => "web", - }, - { - name => "ZM_WEB_EVENTS_PER_PAGE", - default => "25", - description => "How many events to list per page in paged mode", - help => qqq(" - In the event list view you can either list all events or just a - page at a time. This option controls how many events are listed - per page in paged mode and how often to repeat the column - headers in non-paged mode. - "), - type => $types{integer}, - category => "web", - }, - { - name => "ZM_WEB_LIST_THUMBS", - default => "no", - description => "Display mini-thumbnails of event images in event lists", - help => qqq(" - Ordinarily the event lists just display text details of the - events to save space and time. By switching this option on you - can also display small thumbnails to help you identify events - of interest. The size of these thumbnails is controlled by the - following two options. - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_WEB_LIST_THUMB_WIDTH", - default => "48", - description => "The width of the thumbnails that appear in the event lists", - help => qqq(" - This options controls the width of the thumbnail images that - appear in the event lists. It should be fairly small to fit in - with the rest of the table. If you prefer you can specify a - height instead in the next option but you should only use one - of the width or height and the other option should be set to - zero. If both width and height are specified then width will be - used and height ignored. - "), - type => $types{integer}, - requires => [ { name => "ZM_WEB_LIST_THUMBS", value => "yes" } ], - category => "web", - }, - { - name => "ZM_WEB_LIST_THUMB_HEIGHT", - default => "0", - description => "The height of the thumbnails that appear in the event lists", - help => qqq(" - This options controls the height of the thumbnail images that - appear in the event lists. It should be fairly small to fit in - with the rest of the table. If you prefer you can specify a - width instead in the previous option but you should only use - one of the width or height and the other option should be set - to zero. If both width and height are specified then width will - be used and height ignored. - "), - type => $types{integer}, - requires => [ { name => "ZM_WEB_LIST_THUMBS", value => "yes" } ], - category => "web", - }, - { - name => "ZM_WEB_USE_OBJECT_TAGS", - default => "yes", - description => "Wrap embed in object tags for media content", - help => qqq(" - There are two methods of including media content in web pages. - The most common way is use the EMBED tag which is able to give - some indication of the type of content. However this is not a - standard part of HTML. The official method is to use OBJECT - tags which are able to give more information allowing the - correct media viewers etc to be loaded. However these are less - widely supported and content may be specifically tailored to a - particular platform or player. This option controls whether - media content is enclosed in EMBED tags only or whether, where - appropriate, it is additionally wrapped in OBJECT tags. - Currently OBJECT tags are only used in a limited number of - circumstances but they may become more widespread in the - future. It is suggested that you leave this option on unless - you encounter problems playing some content. - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_WEB_H_REFRESH_MAIN", - default => "60", - introduction => qqq(" - There are now a number of options that are grouped into - bandwidth categories, this allows you to configure the - ZoneMinder client to work optimally over the various access - methods you might to access the client.\n\nThe next few options - control what happens when the client is running in 'high' - bandwidth mode. You should set these options for when accessing - the ZoneMinder client over a local network or high speed link. - In most cases the default values will be suitable as a starting - point. - "), - description => "How often (in seconds) the main console window should refresh itself", - help => qqq(" - The main console window lists a general status and the event - totals for all monitors. This is not a trivial task and should - not be repeated too frequently or it may affect the performance - of the rest of the system. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_REFRESH_CYCLE", - default => "10", - description => "How often (in seconds) the cycle watch window swaps to the next monitor", - help => qqq(" - The cycle watch window is a method of continuously cycling - between images from all of your monitors. This option - determines how often to refresh with a new image. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_REFRESH_IMAGE", - default => "3", - description => "How often (in seconds) the watched image is refreshed (if not streaming)", - help => qqq(" - The live images from a monitor can be viewed in either streamed - or stills mode. This option determines how often a stills image - is refreshed, it has no effect if streaming is selected. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_REFRESH_STATUS", - default => "1", - description => "How often (in seconds) the status refreshes itself in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - one in the middle merely contains a monitor status which needs - to refresh fairly frequently to give a true indication. This - option determines that frequency. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_REFRESH_EVENTS", - default => "5", - description => "How often (in seconds) the event listing is refreshed in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - lower framme contains a listing of the last few events for easy - access. This option determines how often this is refreshed. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_CAN_STREAM", - default => "auto", - description => "Override the automatic detection of browser streaming capability", - help => qqq(" - If you know that your browser can handle image streams of the - type 'multipart/x-mixed-replace' but ZoneMinder does not detect - this correctly you can set this option to ensure that the - stream is delivered with or without the use of the Cambozola - plugin. Selecting 'yes' will tell ZoneMinder that your browser - can handle the streams natively, 'no' means that it can't and - so the plugin will be used while 'auto' lets ZoneMinder decide. - "), - type => $types{tristate}, - category => "highband", - }, - { - name => "ZM_WEB_H_STREAM_METHOD", - default => "jpeg", - description => "Which method should be used to send video streams to your browser.", - help => qqq(" - ZoneMinder can be configured to use either mpeg encoded video - or a series or still jpeg images when sending video streams. - This option defines which is used. If you choose mpeg you - should ensure that you have the appropriate plugins available - on your browser whereas choosing jpeg will work natively on - Mozilla and related browsers and with a Java applet on Internet - Explorer - "), - type => { - db_type =>"string", - hint =>"mpeg|jpeg", - pattern =>qr|^([mj])|i, - format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) - }, - category => "highband", - }, - { - name => "ZM_WEB_H_DEFAULT_SCALE", - default => "100", - description => "What the default scaling factor applied to 'live' or 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'live' or 'event' streams in - their native size. However if you have monitors with large - dimensions or a slow link you may prefer to reduce this size, - alternatively for small monitors you can enlarge it. This - options lets you specify what the default scaling factor will - be. It is expressed as a percentage so 100 is normal size, 200 - is double size etc. - "), - type => { - db_type =>"integer", - hint =>"25|33|50|75|100|150|200|300|400", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "highband", - }, - { - name => "ZM_WEB_H_DEFAULT_RATE", - default => "100", - description => "What the default replay rate factor applied to 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'event' streams at their - native rate, i.e. as close to real-time as possible. However if - you have long events it is often convenient to replay them at a - faster rate for review. This option lets you specify what the - default replay rate will be. It is expressed as a percentage so - 100 is normal rate, 200 is double speed etc. - "), - type => { - db_type =>"integer", - hint =>"25|50|100|150|200|400|1000|2500|5000|10000", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "highband", - }, - { - name => "ZM_WEB_H_VIDEO_BITRATE", - default => "150000", - description => "What the bitrate of the video encoded stream should be set to", - help => qqq(" - When encoding real video via the ffmpeg library a bit rate can - be specified which roughly corresponds to the available - bandwidth used for the stream. This setting effectively - corresponds to a 'quality' setting for the video. A low value - will result in a blocky image whereas a high value will produce - a clearer view. Note that this setting does not control the - frame rate of the video however the quality of the video - produced is affected both by this setting and the frame rate - that the video is produced at. A higher frame rate at a - particular bit rate result in individual frames being at a - lower quality. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_VIDEO_MAXFPS", - default => "30", - description => "What the maximum frame rate for streamed video should be", - help => qqq(" - When using streamed video the main control is the bitrate which - determines how much data can be transmitted. However a lower - bitrate at high frame rates results in a lower quality image. - This option allows you to limit the maximum frame rate to - ensure that video quality is maintained. An additional - advantage is that encoding video at high frame rates is a - processor intensive task when for the most part a very high - frame rate offers little perceptible improvement over one that - has a more manageable resource requirement. Note, this option - is implemented as a cap beyond which binary reduction takes - place. So if you have a device capturing at 15fps and set this - option to 10fps then the video is not produced at 10fps, but - rather at 7.5fps (15 divided by 2) as the final frame rate must - be the original divided by a power of 2. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_SCALE_THUMBS", - default => "no", - description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", - help => qqq(" - If unset, this option sends the whole image to the browser - which resizes it in the window. If set the image is scaled down - on the server before sending a reduced size image to the - browser to conserve bandwidth at the cost of cpu on the server. - Note that ZM can only perform the resizing if the appropriate - PHP graphics functionality is installed. This is usually - available in the php-gd package. - "), - type => $types{boolean}, - category => "highband", - }, - { - name => "ZM_WEB_H_EVENTS_VIEW", - default => "events", - description => "What the default view of multiple events should be.", - help => qqq(" - Stored events can be viewed in either an events list format or - in a timeline based one. This option sets the default view that - will be used. Choosing one view here does not prevent the other - view being used as it will always be selectable from whichever - view is currently being used. - "), - type => { - db_type =>"string", - hint =>"events|timeline", - pattern =>qr|^([lt])|i, - format =>q( $1 =~ /^e/ ? "events" : "timeline" ) - }, - category => "highband", - }, - { - name => "ZM_WEB_H_SHOW_PROGRESS", - default => "yes", - description => "Show the progress of replay in event view.", - help => qqq(" - When viewing events an event navigation panel and progress bar - is shown below the event itself. This allows you to jump to - specific points in the event, but can can also dynamically - update to display the current progress of the event replay - itself. This progress is calculated from the actual event - duration and is not directly linked to the replay itself, so on - limited bandwidth connections may be out of step with the - replay. This option allows you to turn off the progress - display, whilst still keeping the navigation aspect, where - bandwidth prevents it functioning effectively. - "), - type => $types{boolean}, - category => "highband", - }, - { - name => "ZM_WEB_H_AJAX_TIMEOUT", - default => "3000", - description => "How long to wait for Ajax request responses (ms)", - help => qqq(" - The newer versions of the live feed and event views use Ajax to - request information from the server and populate the views - dynamically. This option allows you to specify a timeout if - required after which requests are abandoned. A timeout may be - necessary if requests would overwise hang such as on a slow - connection. This would tend to consume a lot of browser memory - and make the interface unresponsive. Ordinarily no requests - should timeout so this setting should be set to a value greater - than the slowest expected response. This value is in - milliseconds but if set to zero then no timeout will be used. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_M_REFRESH_MAIN", - default => "300", - description => "How often (in seconds) the main console window should refresh itself", - help => qqq(" - The main console window lists a general status and the event - totals for all monitors. This is not a trivial task and should - not be repeated too frequently or it may affect the performance - of the rest of the system. - "), - type => $types{integer}, - introduction => qqq(" - The next few options control what happens when the client is - running in 'medium' bandwidth mode. You should set these - options for when accessing the ZoneMinder client over a slower - cable or DSL link. In most cases the default values will be - suitable as a starting point. - "), - category => "medband", - }, - { - name => "ZM_WEB_M_REFRESH_CYCLE", - default => "20", - description => "How often (in seconds) the cycle watch window swaps to the next monitor", - help => qqq(" - The cycle watch window is a method of continuously cycling - between images from all of your monitors. This option - determines how often to refresh with a new image. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_M_REFRESH_IMAGE", - default => "10", - description => "How often (in seconds) the watched image is refreshed (if not streaming)", - help => qqq(" - The live images from a monitor can be viewed in either streamed - or stills mode. This option determines how often a stills image - is refreshed, it has no effect if streaming is selected. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_M_REFRESH_STATUS", - default => "5", - description => "How often (in seconds) the status refreshes itself in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - one in the middle merely contains a monitor status which needs - to refresh fairly frequently to give a true indication. This - option determines that frequency. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_M_REFRESH_EVENTS", - default => "60", - description => "How often (in seconds) the event listing is refreshed in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - lower framme contains a listing of the last few events for easy - access. This option determines how often this is refreshed. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_M_CAN_STREAM", - default => "auto", - description => "Override the automatic detection of browser streaming capability", - help => qqq(" - If you know that your browser can handle image streams of the - type 'multipart/x-mixed-replace' but ZoneMinder does not detect - this correctly you can set this option to ensure that the - stream is delivered with or without the use of the Cambozola - plugin. Selecting 'yes' will tell ZoneMinder that your browser - can handle the streams natively, 'no' means that it can't and - so the plugin will be used while 'auto' lets ZoneMinder decide. - "), - type => $types{tristate}, - category => "medband", - }, - { - name => "ZM_WEB_M_STREAM_METHOD", - default => "jpeg", - description => "Which method should be used to send video streams to your browser.", - help => qqq(" - ZoneMinder can be configured to use either mpeg encoded video - or a series or still jpeg images when sending video streams. - This option defines which is used. If you choose mpeg you - should ensure that you have the appropriate plugins available - on your browser whereas choosing jpeg will work natively on - Mozilla and related browsers and with a Java applet on Internet - Explorer - "), - type => { - db_type =>"string", - hint =>"mpeg|jpeg", - pattern =>qr|^([mj])|i, - format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) - }, - category => "medband", - }, - { - name => "ZM_WEB_M_DEFAULT_SCALE", - default => "100", - description => "What the default scaling factor applied to 'live' or 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'live' or 'event' streams in - their native size. However if you have monitors with large - dimensions or a slow link you may prefer to reduce this size, - alternatively for small monitors you can enlarge it. This - options lets you specify what the default scaling factor will - be. It is expressed as a percentage so 100 is normal size, 200 - is double size etc. - "), - type => { - db_type =>"integer", - hint =>"25|33|50|75|100|150|200|300|400", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "medband", - }, - { - name => "ZM_WEB_M_DEFAULT_RATE", - default => "100", - description => "What the default replay rate factor applied to 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'event' streams at their - native rate, i.e. as close to real-time as possible. However if - you have long events it is often convenient to replay them at a - faster rate for review. This option lets you specify what the - default replay rate will be. It is expressed as a percentage so - 100 is normal rate, 200 is double speed etc. - "), - type => { - db_type =>"integer", - hint =>"25|50|100|150|200|400|1000|2500|5000|10000", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "medband", - }, - { - name => "ZM_WEB_M_VIDEO_BITRATE", - default => "75000", - description => "What the bitrate of the video encoded stream should be set to", - help => qqq(" - When encoding real video via the ffmpeg library a bit rate can - be specified which roughly corresponds to the available - bandwidth used for the stream. This setting effectively - corresponds to a 'quality' setting for the video. A low value - will result in a blocky image whereas a high value will produce - a clearer view. Note that this setting does not control the - frame rate of the video however the quality of the video - produced is affected both by this setting and the frame rate - that the video is produced at. A higher frame rate at a - particular bit rate result in individual frames being at a - lower quality. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_M_VIDEO_MAXFPS", - default => "10", - description => "What the maximum frame rate for streamed video should be", - help => qqq(" - When using streamed video the main control is the bitrate which - determines how much data can be transmitted. However a lower - bitrate at high frame rates results in a lower quality image. - This option allows you to limit the maximum frame rate to - ensure that video quality is maintained. An additional - advantage is that encoding video at high frame rates is a - processor intensive task when for the most part a very high - frame rate offers little perceptible improvement over one that - has a more manageable resource requirement. Note, this option - is implemented as a cap beyond which binary reduction takes - place. So if you have a device capturing at 15fps and set this - option to 10fps then the video is not produced at 10fps, but - rather at 7.5fps (15 divided by 2) as the final frame rate must - be the original divided by a power of 2. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_M_SCALE_THUMBS", - default => "yes", - description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", - help => qqq(" - If unset, this option sends the whole image to the browser - which resizes it in the window. If set the image is scaled down - on the server before sending a reduced size image to the - browser to conserve bandwidth at the cost of cpu on the server. - Note that ZM can only perform the resizing if the appropriate - PHP graphics functionality is installed. This is usually - available in the php-gd package. - "), - type => $types{boolean}, - category => "medband", - }, - { - name => "ZM_WEB_M_EVENTS_VIEW", - default => "events", - description => "What the default view of multiple events should be.", - help => qqq(" - Stored events can be viewed in either an events list format or - in a timeline based one. This option sets the default view that - will be used. Choosing one view here does not prevent the other - view being used as it will always be selectable from whichever - view is currently being used. - "), - type => { - db_type =>"string", - hint =>"events|timeline", - pattern =>qr|^([lt])|i, - format =>q( $1 =~ /^e/ ? "events" : "timeline" ) - }, - category => "medband", - }, - { - name => "ZM_WEB_M_SHOW_PROGRESS", - default => "yes", - description => "Show the progress of replay in event view.", - help => qqq(" - When viewing events an event navigation panel and progress bar - is shown below the event itself. This allows you to jump to - specific points in the event, but can can also dynamically - update to display the current progress of the event replay - itself. This progress is calculated from the actual event - duration and is not directly linked to the replay itself, so on - limited bandwidth connections may be out of step with the - replay. This option allows you to turn off the progress - display, whilst still keeping the navigation aspect, where - bandwidth prevents it functioning effectively. - "), - type => $types{boolean}, - category => "medband", - }, - { - name => "ZM_WEB_M_AJAX_TIMEOUT", - default => "5000", - description => "How long to wait for Ajax request responses (ms)", - help => qqq(" - The newer versions of the live feed and event views use Ajax to - request information from the server and populate the views - dynamically. This option allows you to specify a timeout if - required after which requests are abandoned. A timeout may be - necessary if requests would overwise hang such as on a slow - connection. This would tend to consume a lot of browser memory - and make the interface unresponsive. Ordinarily no requests - should timeout so this setting should be set to a value greater - than the slowest expected response. This value is in - milliseconds but if set to zero then no timeout will be used. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_L_REFRESH_MAIN", - default => "300", - description => "How often (in seconds) the main console window should refresh itself", - introduction => qqq(" - The next few options control what happens when the client is - running in 'low' bandwidth mode. You should set these options - for when accessing the ZoneMinder client over a modem or slow - link. In most cases the default values will be suitable as a - starting point. - "), - help => qqq(" - The main console window lists a general status and the event - totals for all monitors. This is not a trivial task and should - not be repeated too frequently or it may affect the performance - of the rest of the system. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_REFRESH_CYCLE", - default => "30", - description => "How often (in seconds) the cycle watch window swaps to the next monitor", - help => qqq(" - The cycle watch window is a method of continuously cycling - between images from all of your monitors. This option - determines how often to refresh with a new image. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_REFRESH_IMAGE", - default => "15", - description => "How often (in seconds) the watched image is refreshed (if not streaming)", - help => qqq(" - The live images from a monitor can be viewed in either streamed - or stills mode. This option determines how often a stills image - is refreshed, it has no effect if streaming is selected. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_REFRESH_STATUS", - default => "10", - description => "How often (in seconds) the status refreshes itself in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - one in the middle merely contains a monitor status which needs - to refresh fairly frequently to give a true indication. This - option determines that frequency. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_REFRESH_EVENTS", - default => "180", - description => "How often (in seconds) the event listing is refreshed in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - lower framme contains a listing of the last few events for easy - access. This option determines how often this is refreshed. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_CAN_STREAM", - default => "auto", - description => "Override the automatic detection of browser streaming capability", - help => qqq(" - If you know that your browser can handle image streams of the - type 'multipart/x-mixed-replace' but ZoneMinder does not detect - this correctly you can set this option to ensure that the - stream is delivered with or without the use of the Cambozola - plugin. Selecting 'yes' will tell ZoneMinder that your browser - can handle the streams natively, 'no' means that it can't and - so the plugin will be used while 'auto' lets ZoneMinder decide. - "), - type => $types{tristate}, - category => "lowband", - }, - { - name => "ZM_WEB_L_STREAM_METHOD", - default => "jpeg", - description => "Which method should be used to send video streams to your browser.", - help => qqq(" - ZoneMinder can be configured to use either mpeg encoded video - or a series or still jpeg images when sending video streams. - This option defines which is used. If you choose mpeg you - should ensure that you have the appropriate plugins available - on your browser whereas choosing jpeg will work natively on - Mozilla and related browsers and with a Java applet on Internet - Explorer - "), - type => { - db_type =>"string", - hint =>"mpeg|jpeg", - pattern =>qr|^([mj])|i, - format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) - }, - category => "lowband", - }, - { - name => "ZM_WEB_L_DEFAULT_SCALE", - default => "100", - description => "What the default scaling factor applied to 'live' or 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'live' or 'event' streams in - their native size. However if you have monitors with large - dimensions or a slow link you may prefer to reduce this size, - alternatively for small monitors you can enlarge it. This - options lets you specify what the default scaling factor will - be. It is expressed as a percentage so 100 is normal size, 200 - is double size etc. - "), - type => { - db_type =>"integer", - hint =>"25|33|50|75|100|150|200|300|400", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "lowband", - }, - { - name => "ZM_WEB_L_DEFAULT_RATE", - default => "100", - description => "What the default replay rate factor applied to 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'event' streams at their - native rate, i.e. as close to real-time as possible. However if - you have long events it is often convenient to replay them at a - faster rate for review. This option lets you specify what the - default replay rate will be. It is expressed as a percentage so - 100 is normal rate, 200 is double speed etc. - "), - type => { - db_type =>"integer", - hint =>"25|50|100|150|200|400|1000|2500|5000|10000", - pattern =>qr|^(\d+)$|, format=>q( $1 ) - }, - category => "lowband", - }, - { - name => "ZM_WEB_L_VIDEO_BITRATE", - default => "25000", - description => "What the bitrate of the video encoded stream should be set to", - help => qqq(" - When encoding real video via the ffmpeg library a bit rate can - be specified which roughly corresponds to the available - bandwidth used for the stream. This setting effectively - corresponds to a 'quality' setting for the video. A low value - will result in a blocky image whereas a high value will produce - a clearer view. Note that this setting does not control the - frame rate of the video however the quality of the video - produced is affected both by this setting and the frame rate - that the video is produced at. A higher frame rate at a - particular bit rate result in individual frames being at a - lower quality. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_VIDEO_MAXFPS", - default => "5", - description => "What the maximum frame rate for streamed video should be", - help => qqq(" - When using streamed video the main control is the bitrate which - determines how much data can be transmitted. However a lower - bitrate at high frame rates results in a lower quality image. - This option allows you to limit the maximum frame rate to - ensure that video quality is maintained. An additional - advantage is that encoding video at high frame rates is a - processor intensive task when for the most part a very high - frame rate offers little perceptible improvement over one that - has a more manageable resource requirement. Note, this option - is implemented as a cap beyond which binary reduction takes - place. So if you have a device capturing at 15fps and set this - option to 10fps then the video is not produced at 10fps, but - rather at 7.5fps (15 divided by 2) as the final frame rate must - be the original divided by a power of 2. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_SCALE_THUMBS", - default => "yes", - description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", - help => qqq(" - If unset, this option sends the whole image to the browser - which resizes it in the window. If set the image is scaled down - on the server before sending a reduced size image to the - browser to conserve bandwidth at the cost of cpu on the server. - Note that ZM can only perform the resizing if the appropriate - PHP graphics functionality is installed. This is usually - available in the php-gd package. - "), - type => $types{boolean}, - category => "lowband", - }, - { - name => "ZM_WEB_L_EVENTS_VIEW", - default => "events", - description => "What the default view of multiple events should be.", - help => qqq(" - Stored events can be viewed in either an events list format or - in a timeline based one. This option sets the default view that - will be used. Choosing one view here does not prevent the other - view being used as it will always be selectable from whichever - view is currently being used. - "), - type => { - db_type =>"string", - hint =>"events|timeline", - pattern =>qr|^([lt])|i, - format =>q( $1 =~ /^e/ ? "events" : "timeline" ) - }, - category => "lowband", - }, - { - name => "ZM_WEB_L_SHOW_PROGRESS", - default => "no", - description => "Show the progress of replay in event view.", - help => qqq(" - When viewing events an event navigation panel and progress bar - is shown below the event itself. This allows you to jump to - specific points in the event, but can can also dynamically - update to display the current progress of the event replay - itself. This progress is calculated from the actual event - duration and is not directly linked to the replay itself, so on - limited bandwidth connections may be out of step with the - replay. This option allows you to turn off the progress - display, whilst still keeping the navigation aspect, where - bandwidth prevents it functioning effectively. - "), - type => $types{boolean}, - category => "lowband", - }, - { - name => "ZM_WEB_L_AJAX_TIMEOUT", - default => "10000", - description => "How long to wait for Ajax request responses (ms)", - help => qqq(" - The newer versions of the live feed and event views use Ajax to - request information from the server and populate the views - dynamically. This option allows you to specify a timeout if - required after which requests are abandoned. A timeout may be - necessary if requests would overwise hang such as on a slow - connection. This would tend to consume a lot of browser memory - and make the interface unresponsive. Ordinarily no requests - should timeout so this setting should be set to a value greater - than the slowest expected response. This value is in - milliseconds but if set to zero then no timeout will be used. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_DYN_LAST_VERSION", - default => "", - description => "What the last version of ZoneMinder recorded from zoneminder.com is", - help => "", - type => $types{string}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_DYN_CURR_VERSION", - default => "@VERSION@", - description => qqq(" - What the effective current version of ZoneMinder is, might be - different from actual if versions ignored - "), - help => "", - type => $types{string}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_DYN_DB_VERSION", - default => "@VERSION@", - description => "What the version of the database is, from zmupdate", - help => "", - type => $types{string}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_DYN_LAST_CHECK", - default => "", - description => "When the last check for version from zoneminder.com was", - help => "", - type => $types{integer}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_DYN_NEXT_REMINDER", - default => "", - description => "When the earliest time to remind about versions will be", - help => "", - type => $types{string}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_DYN_DONATE_REMINDER_TIME", - default => 0, - description => "When the earliest time to remind about donations will be", - help => "", - type => $types{integer}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_DYN_SHOW_DONATE_REMINDER", - default => "yes", - description => "Remind about donations or not", - help => "", - type => $types{boolean}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_SSMTP_MAIL", - default => "no", - description => qqq(" - Use a SSMTP mail server if available. - NEW_MAIL_MODULES must be enabled - "), - requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" }, - { name => "ZM_NEW_MAIL_MODULES", value => "yes" } - ], - help => qqq(" - SSMTP is a lightweight and efficient method to send email. - The SSMTP application is not installed by default. - NEW_MAIL_MODULES must also be enabled. - Please visit: http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder - for setup and configuration help. - "), - type => $types{boolean}, - category => "mail", - }, - { - name => "ZM_SSMTP_PATH", - default => "", - description => "SSMTP executable path", - requires => [{ name => "ZM_SSMTP_MAIL", value => "yes" }], - help => qqq(" - Recommend setting the path to the SSMTP application. - If path is not defined. Zoneminder will try to determine - the path via shell command. Example path: /usr/sbin/ssmtp. - "), - type => $types{string}, - category => "mail", - }, -); + ZoneMinder', + description => 'The body of the email used to send matching event details', + requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], + help => q` + This option is used to define the content of the email that is + sent for any events that match the appropriate filters. + `, + type => $types{text}, + category => 'mail', + }, + { + name => 'ZM_OPT_MESSAGE', + default => 'no', + description => 'Should ZoneMinder message you with details of events that match corresponding filters', + help => q` + In ZoneMinder you can create event filters that specify whether + events that match certain criteria should have their details + sent to you at a designated short message email address. This + will allow you to be notified of events as soon as they occur. + This option specifies whether this functionality should be + available. The email created by this option will be brief and + is intended to be sent to an SMS gateway or a minimal mail + reader such as a mobile device or phone rather than a regular + email reader. + `, + type => $types{boolean}, + category => 'mail', + }, + { + name => 'ZM_MESSAGE_ADDRESS', + default => '', + description => 'The email address to send matching event details to', + requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], + help => q` + This option is used to define the short message email address + that any events that match the appropriate filters will be sent + to. + `, + type => $types{email}, + category => 'mail', + }, + { + name => 'ZM_MESSAGE_TEXT', + default => 'subject = "ZoneMinder: Alarm - %MN%-%EI%" + body = "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score."', + description => 'The text of the message used to send matching event details', + requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], + help => q` + This option is used to define the content of the message that + is sent for any events that match the appropriate filters. + `, + type => $types{text}, + category => 'hidden', + }, + { + name => 'ZM_MESSAGE_SUBJECT', + default => 'ZoneMinder: Alarm - %MN%-%EI%', + description => 'The subject of the message used to send matching event details', + requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], + help => q` + This option is used to define the subject of the message that + is sent for any events that match the appropriate filters. + `, + type => $types{string}, + category => 'mail', + }, + { + name => 'ZM_MESSAGE_BODY', + default => 'ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.', + description => 'The body of the message used to send matching event details', + requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], + help => q` + This option is used to define the content of the message that + is sent for any events that match the appropriate filters. + `, + type => $types{text}, + category => 'mail', + }, + { + name => 'ZM_NEW_MAIL_MODULES', + default => 'no', + description => 'Use a newer perl method to send emails', + requires => [ + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' } + ], + help => q` + Traditionally ZoneMinder has used the MIME::Entity perl module + to construct and send notification emails and messages. Some + people have reported problems with this module not being + present at all or flexible enough for their needs. If you are + one of those people this option allows you to select a new + mailing method using MIME::Lite and Net::SMTP instead. This + method was contributed by Ross Melin and should work for + everyone but has not been extensively tested so currently is + not selected by default. + `, + type => $types{boolean}, + category => 'mail', + }, + { + name => 'ZM_EMAIL_HOST', + default => 'localhost', + description => 'The host address of your SMTP mail server', + requires => [ + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' } + ], + help => q` + If you have chosen SMTP as the method by which to send + notification emails or messages then this option allows you to + choose which SMTP server to use to send them. The default of + localhost may work if you have the sendmail, exim or a similar + daemon running however you may wish to enter your ISP's SMTP + mail server here. + `, + type => $types{hostname}, + category => 'mail', + }, + { + name => 'ZM_FROM_EMAIL', + default => '', + description => 'The email address you wish your event notifications to originate from', + requires => [ + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' } + ], + help => q` + The emails or messages that will be sent to you informing you + of events can appear to come from a designated email address to + help you with mail filtering etc. An address of something like + ZoneMinder\@your.domain is recommended. + `, + type => $types{email}, + category => 'mail', + }, + { + name => 'ZM_URL', + default => '', + description => 'The URL of your ZoneMinder installation', + requires => [ + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' } + ], + help => q` + The emails or messages that will be sent to you informing you + of events can include a link to the events themselves for easy + viewing. If you intend to use this feature then set this option + to the url of your installation as it would appear from where + you read your email, e.g. http://host.your.domain/zm.php. + `, + type => $types{url}, + category => 'mail', + }, + { + name => 'ZM_MAX_RESTART_DELAY', + default => '600', + description => 'Maximum delay (in seconds) for daemon restart attempts.', + help => q` + The zmdc (zm daemon control) process controls when processeses + are started or stopped and will attempt to restart any that + fail. If a daemon fails frequently then a delay is introduced + between each restart attempt. If the daemon stills fails then + this delay is increased to prevent extra load being placed on + the system by continual restarts. This option controls what + this maximum delay is. + `, + type => $types{integer}, + category => 'system', + }, + { + name => 'ZM_WATCH_CHECK_INTERVAL', + default => '10', + description => 'How often to check the capture daemons have not locked up', + help => q` + The zmwatch daemon checks the image capture performance of the + capture daemons to ensure that they have not locked up (rarely + a sync error may occur which blocks indefinitely). This option + determines how often the daemons are checked. + `, + type => $types{integer}, + category => 'system', + }, + { + name => 'ZM_WATCH_MAX_DELAY', + default => '5', + description => 'The maximum delay allowed since the last captured image', + help => q` + The zmwatch daemon checks the image capture performance of the + capture daemons to ensure that they have not locked up (rarely + a sync error may occur which blocks indefinitely). This option + determines the maximum delay to allow since the last captured + frame. The daemon will be restarted if it has not captured any + images after this period though the actual restart may take + slightly longer in conjunction with the check interval value + above. + `, + type => $types{decimal}, + category => 'system', + }, + { + name => 'ZM_RUN_AUDIT', + default => 'yes', + description => 'Run zmaudit to check data consistency', + help => q` + The zmaudit daemon exists to check that the saved information + in the database and on the filesystem match and are consistent + with each other. If an error occurs or if you are using 'fast + deletes' it may be that database records are deleted but files + remain. In this case, and similar, zmaudit will remove + redundant information to synchronise the two data stores. This + option controls whether zmaudit is run in the background and + performs these checks and fixes continuously. This is + recommended for most systems however if you have a very large + number of events the process of scanning the database and + filesystem may take a long time and impact performance. In this + case you may prefer to not have zmaudit running unconditionally + and schedule occasional checks at other, more convenient, + times. + `, + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_AUDIT_CHECK_INTERVAL', + default => '900', + description => 'How often to check database and filesystem consistency', + help => q` + The zmaudit daemon exists to check that the saved information + in the database and on the filesystem match and are consistent + with each other. If an error occurs or if you are using 'fast + deletes' it may be that database records are deleted but files + remain. In this case, and similar, zmaudit will remove + redundant information to synchronise the two data stores. The + default check interval of 900 seconds (15 minutes) is fine for + most systems however if you have a very large number of events + the process of scanning the database and filesystem may take a + long time and impact performance. In this case you may prefer + to make this interval much larger to reduce the impact on your + system. This option determines how often these checks are + performed. + `, + type => $types{integer}, + category => 'system', + }, + { + name => 'ZM_AUDIT_MIN_AGE', + default => '86400', + description => 'The minimum age in seconds event data must be in order to be deleted.', + help => q` + The zmaudit daemon exists to check that the saved information + in the database and on the filesystem match and are consistent + with each other. Event files or db records that are younger than + this setting will not be deleted and a warning will be given. + `, + type => $types{integer}, + category => 'system', + }, + { + name => 'ZM_FORCED_ALARM_SCORE', + default => '255', + description => 'Score to give forced alarms', + help => q` + The 'zmu' utility can be used to force an alarm on a monitor + rather than rely on the motion detection algorithms. This + option determines what score to give these alarms to + distinguish them from regular ones. It must be 255 or less. + `, + type => $types{integer}, + category => 'config', + }, + { + name => 'ZM_BULK_FRAME_INTERVAL', + default => '100', + description => 'How often a bulk frame should be written to the database', + help => q` + Traditionally ZoneMinder writes an entry into the Frames + database table for each frame that is captured and saved. This + works well in motion detection scenarios but when in a DVR + situation ('Record' or 'Mocord' mode) this results in a huge + number of frame writes and a lot of database and disk bandwidth + for very little additional information. Setting this to a + non-zero value will enabled ZoneMinder to group these non-alarm + frames into one 'bulk' frame entry which saves a lot of + bandwidth and space. The only disadvantage of this is that + timing information for individual frames is lost but in + constant frame rate situations this is usually not significant. + This setting is ignored in Modect mode and individual frames + are still written if an alarm occurs in Mocord mode also. + `, + type => $types{integer}, + category => 'config', + }, + { + name => 'ZM_EVENT_CLOSE_MODE', + default => 'idle', + description => 'When continuous events are closed.', + help => q` + When a monitor is running in a continuous recording mode + (Record or Mocord) events are usually closed after a fixed + period of time (the section length). However in Mocord mode it + is possible that motion detection may occur near the end of a + section. This option controls what happens when an alarm occurs + in Mocord mode. The 'time' setting means that the event will be + closed at the end of the section regardless of alarm activity. + The 'idle' setting means that the event will be closed at the + end of the section if there is no alarm activity occurring at + the time otherwise it will be closed once the alarm is over + meaning the event may end up being longer than the normal + section length. The 'alarm' setting means that if an alarm + occurs during the event, the event will be closed once the + alarm is over regardless of when this occurs. This has the + effect of limiting the number of alarms to one per event and + the events will be shorter than the section length if an alarm + has occurred. + `, + type => $types{boolean}, + type => { + db_type =>'string', + hint =>'time|idle|alarm', + pattern =>qr|^([tia])|i, + format =>q( ($1 =~ /^t/) + ? 'time' + : ($1 =~ /^i/ ? 'idle' : 'time' ) + ) + }, + category => 'config', + }, + # Deprecated, superseded by event close mode + { + name => 'ZM_FORCE_CLOSE_EVENTS', + default => 'no', + description => 'Close events at section ends.', + help => q` + When a monitor is running in a continuous recording mode + (Record or Mocord) events are usually closed after a fixed + period of time (the section length). However in Mocord mode it + is possible that motion detection may occur near the end of a + section and ordinarily this will prevent the event being closed + until the motion has ceased. Switching this option on will + force the event closed at the specified time regardless of any + motion activity. + `, + type => $types{boolean}, + category => 'hidden', + }, + { + name => 'ZM_CREATE_ANALYSIS_IMAGES', + default => 'yes', + description => 'Create analysed alarm images with motion outlined', + help => q` + By default during an alarm ZoneMinder records both the raw + captured image and one that has been analysed and had areas + where motion was detected outlined. This can be very useful + during zone configuration or in analysing why events occurred. + However it also incurs some overhead and in a stable system may + no longer be necessary. This parameter allows you to switch the + generation of these images off. + `, + type => $types{boolean}, + category => 'config', + }, + { + name => 'ZM_WEIGHTED_ALARM_CENTRES', + default => 'no', + description => 'Use a weighted algorithm to calculate the centre of an alarm', + help => q` + ZoneMinder will always calculate the centre point of an alarm + in a zone to give some indication of where on the screen it is. + This can be used by the experimental motion tracking feature or + your own custom extensions. In the alarmed or filtered pixels + mode this is a simple midpoint between the extents of the + detected pixels. However in the blob method this can instead be + calculated using weighted pixel locations to give more accurate + positioning for irregularly shaped blobs. This method, while + more precise is also slower and so is turned off by default. + `, + type => $types{boolean}, + category => 'config', + }, + { + name => 'ZM_EVENT_IMAGE_DIGITS', + default => '5', + description => 'How many significant digits are used in event image numbering', + help => q` + As event images are captured they are stored to the filesystem + with a numerical index. By default this index has three digits + so the numbers start 001, 002 etc. This works works for most + scenarios as events with more than 999 frames are rarely + captured. However if you have extremely long events and use + external applications then you may wish to increase this to + ensure correct sorting of images in listings etc. Warning, + increasing this value on a live system may render existing + events unviewable as the event will have been saved with the + previous scheme. Decreasing this value should have no ill + effects. + `, + type => $types{integer}, + category => 'config', + }, + { + name => 'ZM_DEFAULT_ASPECT_RATIO', + default => '4:3', + description => 'The default width:height aspect ratio used in monitors', + help => q` + When specifying the dimensions of monitors you can click a + checkbox to ensure that the width stays in the correct ratio to + the height, or vice versa. This setting allows you to indicate + what the ratio of these settings should be. This should be + specified in the format : and the + default of 4:3 normally be acceptable but 11:9 is another + common setting. If the checkbox is not clicked when specifying + monitor dimensions this setting has no effect. + `, + type => $types{string}, + category => 'config', + }, + { + name => 'ZM_USER_SELF_EDIT', + default => 'no', + description => 'Allow unprivileged users to change their details', + help => q` + Ordinarily only users with system edit privilege are able to + change users details. Switching this option on allows ordinary + users to change their passwords and their language settings + `, + type => $types{boolean}, + category => 'config', + }, + { + name => 'ZM_OPT_CONTROL', + default => 'no', + description => 'Support controllable (e.g. PTZ) cameras', + help => q` + ZoneMinder includes limited support for controllable cameras. A + number of sample protocols are included and others can easily + be added. If you wish to control your cameras via ZoneMinder + then select this option otherwise if you only have static + cameras or use other control methods then leave this option + off. + `, + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_OPT_TRIGGERS', + default => 'no', + description => 'Interface external event triggers via socket or device files', + help => q` + ZoneMinder can interact with external systems which prompt or + cancel alarms. This is done via the zmtrigger.pl script. This + option indicates whether you want to use these external + triggers. Most people will say no here. + `, + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_CHECK_FOR_UPDATES', + default => 'yes', + description => 'Check with zoneminder.com for updated versions', + help => q` + From ZoneMinder version 1.17.0 onwards new versions are + expected to be more frequent. To save checking manually for + each new version ZoneMinder can check with the zoneminder.com + website to determine the most recent release. These checks are + infrequent, about once per week, and no personal or system + information is transmitted other than your current version + number. If you do not wish these checks to take place or your + ZoneMinder system has no internet access you can switch these + check off with this configuration variable + `, + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_TELEMETRY_DATA', + default => 'yes', + description => 'Send usage information to ZoneMinder', + help => q` + Enable collection of usage information of the local system and send + it to the ZoneMinder development team. This data will be used to + determine things like who and where our customers are, how big their + systems are, the underlying hardware and operating system, etc. + This is being done for the sole purpoase of creating a better + product for our target audience. This script is intended to be + completely transparent to the end user, and can be disabled from + the web console under Options. + `, + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_TELEMETRY_UUID', + default => '', + description => 'Unique identifier for ZoneMinder telemetry', + help => q` + This variable is auto-generated once by the system and is used to + uniquely identify it among all other ZoneMinder systems in + existence. + `, + type => $types{string}, + category => 'dynamic', + }, + { + name => 'ZM_TELEMETRY_LAST_UPLOAD', + default => '', + description => 'When the last ZoneMinder telemetry upload ocurred', + help => '', + type => $types{integer}, + readonly => 1, + category => 'dynamic', + }, + { + name => 'ZM_UPDATE_CHECK_PROXY', + default => '', + description => 'Proxy url if required to access zoneminder.com', + help => q` + If you use a proxy to access the internet then ZoneMinder needs + to know so it can access zoneminder.com to check for updates. + If you do use a proxy enter the full proxy url here in the form + of http://:/ + `, + type => $types{string}, + category => 'system', + }, + { + name => 'ZM_SHM_KEY', + default => '0x7a6d0000', + description => 'Shared memory root key to use', + help => q` + ZoneMinder uses shared memory to speed up communication between + modules. To identify the right area to use shared memory keys + are used. This option controls what the base key is, each + monitor will have it's Id or'ed with this to get the actual key + used. You will not normally need to change this value unless it + clashes with another instance of ZoneMinder on the same + machine. Only the first four hex digits are used, the lower + four will be masked out and ignored. + `, + type => $types{hexadecimal}, + category => 'system', + }, + # Deprecated, really no longer necessary + { + name => 'ZM_WEB_REFRESH_METHOD', + default => 'javascript', + description => 'What method windows should use to refresh themselves', + help => q` + Many windows in Javascript need to refresh themselves to keep + their information current. This option determines what method + they should use to do this. Choosing 'javascript' means that + each window will have a short JavaScript statement in with a + timer to prompt the refresh. This is the most compatible + method. Choosing 'http' means the refresh instruction is put in + the HTTP header. This is a cleaner method but refreshes are + interrupted or cancelled when a link in the window is clicked + meaning that the window will no longer refresh and this would + have to be done manually. + `, + type => { + db_type =>'string', + hint =>'javascript|http', + pattern =>qr|^([jh])|i, + format =>q( $1 =~ /^j/ + ? 'javascript' + : 'http' + ) + }, + category => 'hidden', + }, + { + name => 'ZM_WEB_EVENT_SORT_FIELD', + default => 'DateTime', + description => 'Default field the event lists are sorted by', + help => q` + Events in lists can be initially ordered in any way you want. + This option controls what field is used to sort them. You can + modify this ordering from filters or by clicking on headings in + the lists themselves. Bear in mind however that the 'Prev' and + 'Next' links, when scrolling through events, relate to the + ordering in the lists and so not always to time based ordering. + `, + type => { + db_type =>'string', + hint =>'Id|Name|Cause|MonitorName|DateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore', + pattern =>qr|.|, + format =>q( $1 ) + }, + category => 'web', + }, + { + name => 'ZM_WEB_EVENT_SORT_ORDER', + default => 'asc', + description => 'Default order the event lists are sorted by', + help => q` + Events in lists can be initially ordered in any way you want. + This option controls what order (ascending or descending) is + used to sort them. You can modify this ordering from filters or + by clicking on headings in the lists themselves. Bear in mind + however that the 'Prev' and 'Next' links, when scrolling + through events, relate to the ordering in the lists and so not + always to time based ordering. + `, + type => { + db_type =>'string', + hint =>'asc|desc', + pattern =>qr|^([ad])|i, + format =>q( $1 =~ /^a/i ? 'asc' : 'desc' ) + }, + category => 'web', + }, + { + name => 'ZM_WEB_EVENTS_PER_PAGE', + default => '25', + description => 'How many events to list per page in paged mode', + help => q` + In the event list view you can either list all events or just a + page at a time. This option controls how many events are listed + per page in paged mode and how often to repeat the column + headers in non-paged mode. + `, + type => $types{integer}, + category => 'web', + }, + { + name => 'ZM_WEB_LIST_THUMBS', + default => 'no', + description => 'Display mini-thumbnails of event images in event lists', + help => q` + Ordinarily the event lists just display text details of the + events to save space and time. By switching this option on you + can also display small thumbnails to help you identify events + of interest. The size of these thumbnails is controlled by the + following two options. + `, + type => $types{boolean}, + category => 'web', + }, + { + name => 'ZM_WEB_LIST_THUMB_WIDTH', + default => '48', + description => 'The width of the thumbnails that appear in the event lists', + help => q` + This options controls the width of the thumbnail images that + appear in the event lists. It should be fairly small to fit in + with the rest of the table. If you prefer you can specify a + height instead in the next option but you should only use one + of the width or height and the other option should be set to + zero. If both width and height are specified then width will be + used and height ignored. + `, + type => $types{integer}, + requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ], + category => 'web', + }, + { + name => 'ZM_WEB_LIST_THUMB_HEIGHT', + default => '0', + description => 'The height of the thumbnails that appear in the event lists', + help => q` + This options controls the height of the thumbnail images that + appear in the event lists. It should be fairly small to fit in + with the rest of the table. If you prefer you can specify a + width instead in the previous option but you should only use + one of the width or height and the other option should be set + to zero. If both width and height are specified then width will + be used and height ignored. + `, + type => $types{integer}, + requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ], + category => 'web', + }, + { + name => 'ZM_WEB_USE_OBJECT_TAGS', + default => 'yes', + description => 'Wrap embed in object tags for media content', + help => q` + There are two methods of including media content in web pages. + The most common way is use the EMBED tag which is able to give + some indication of the type of content. However this is not a + standard part of HTML. The official method is to use OBJECT + tags which are able to give more information allowing the + correct media viewers etc to be loaded. However these are less + widely supported and content may be specifically tailored to a + particular platform or player. This option controls whether + media content is enclosed in EMBED tags only or whether, where + appropriate, it is additionally wrapped in OBJECT tags. + Currently OBJECT tags are only used in a limited number of + circumstances but they may become more widespread in the + future. It is suggested that you leave this option on unless + you encounter problems playing some content. + `, + type => $types{boolean}, + category => 'web', + }, + { + name => 'ZM_WEB_H_REFRESH_MAIN', + default => '60', + introduction => q` + There are now a number of options that are grouped into + bandwidth categories, this allows you to configure the + ZoneMinder client to work optimally over the various access + methods you might to access the client.\n\nThe next few options + control what happens when the client is running in 'high' + bandwidth mode. You should set these options for when accessing + the ZoneMinder client over a local network or high speed link. + In most cases the default values will be suitable as a starting + point. + `, + description => 'How often (in seconds) the main console window should refresh itself', + help => q` + The main console window lists a general status and the event + totals for all monitors. This is not a trivial task and should + not be repeated too frequently or it may affect the performance + of the rest of the system. + `, + type => $types{integer}, + category => 'highband', + }, + { + name => 'ZM_WEB_H_REFRESH_CYCLE', + default => '10', + description => 'How often (in seconds) the cycle watch window swaps to the next monitor', + help => q` + The cycle watch window is a method of continuously cycling + between images from all of your monitors. This option + determines how often to refresh with a new image. + `, + type => $types{integer}, + category => 'highband', + }, + { + name => 'ZM_WEB_H_REFRESH_IMAGE', + default => '3', + description => 'How often (in seconds) the watched image is refreshed (if not streaming)', + help => q` + The live images from a monitor can be viewed in either streamed + or stills mode. This option determines how often a stills image + is refreshed, it has no effect if streaming is selected. + `, + type => $types{integer}, + category => 'highband', + }, + { + name => 'ZM_WEB_H_REFRESH_STATUS', + default => '1', + description => 'How often (in seconds) the status refreshes itself in the watch window', + help => q` + The monitor window is actually made from several frames. The + one in the middle merely contains a monitor status which needs + to refresh fairly frequently to give a true indication. This + option determines that frequency. + `, + type => $types{integer}, + category => 'highband', + }, + { + name => 'ZM_WEB_H_REFRESH_EVENTS', + default => '5', + description => 'How often (in seconds) the event listing is refreshed in the watch window', + help => q` + The monitor window is actually made from several frames. The + lower framme contains a listing of the last few events for easy + access. This option determines how often this is refreshed. + `, + type => $types{integer}, + category => 'highband', + }, + { + name => 'ZM_WEB_H_CAN_STREAM', + default => 'auto', + description => 'Override the automatic detection of browser streaming capability', + help => q` + If you know that your browser can handle image streams of the + type 'multipart/x-mixed-replace' but ZoneMinder does not detect + this correctly you can set this option to ensure that the + stream is delivered with or without the use of the Cambozola + plugin. Selecting 'yes' will tell ZoneMinder that your browser + can handle the streams natively, 'no' means that it can't and + so the plugin will be used while 'auto' lets ZoneMinder decide. + `, + type => $types{tristate}, + category => 'highband', + }, + { + name => 'ZM_WEB_H_STREAM_METHOD', + default => 'jpeg', + description => 'Which method should be used to send video streams to your browser.', + help => q` + ZoneMinder can be configured to use either mpeg encoded video + or a series or still jpeg images when sending video streams. + This option defines which is used. If you choose mpeg you + should ensure that you have the appropriate plugins available + on your browser whereas choosing jpeg will work natively on + Mozilla and related browsers and with a Java applet on Internet + Explorer + `, + type => { + db_type =>'string', + hint =>'mpeg|jpeg', + pattern =>qr|^([mj])|i, + format =>q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) + }, + category => 'highband', + }, + { + name => 'ZM_WEB_H_DEFAULT_SCALE', + default => '100', + description => 'What the default scaling factor applied to \'live\' or \'event\' views is (%)', + help => q` + Normally ZoneMinder will display 'live' or 'event' streams in + their native size. However if you have monitors with large + dimensions or a slow link you may prefer to reduce this size, + alternatively for small monitors you can enlarge it. This + options lets you specify what the default scaling factor will + be. It is expressed as a percentage so 100 is normal size, 200 + is double size etc. + `, + type => { + db_type =>'integer', + hint =>'25|33|50|75|100|150|200|300|400', + pattern =>qr|^(\d+)$|, + format =>q( $1 ) + }, + category => 'highband', + }, + { + name => 'ZM_WEB_H_DEFAULT_RATE', + default => '100', + description => 'What the default replay rate factor applied to \'event\' views is (%)', + help => q` + Normally ZoneMinder will display 'event' streams at their + native rate, i.e. as close to real-time as possible. However if + you have long events it is often convenient to replay them at a + faster rate for review. This option lets you specify what the + default replay rate will be. It is expressed as a percentage so + 100 is normal rate, 200 is double speed etc. + `, + type => { + db_type =>'integer', + hint =>'25|50|100|150|200|400|1000|2500|5000|10000', + pattern =>qr|^(\d+)$|, + format =>q( $1 ) + }, + category => 'highband', + }, + { + name => 'ZM_WEB_H_VIDEO_BITRATE', + default => '150000', + description => 'What the bitrate of the video encoded stream should be set to', + help => q` + When encoding real video via the ffmpeg library a bit rate can + be specified which roughly corresponds to the available + bandwidth used for the stream. This setting effectively + corresponds to a 'quality' setting for the video. A low value + will result in a blocky image whereas a high value will produce + a clearer view. Note that this setting does not control the + frame rate of the video however the quality of the video + produced is affected both by this setting and the frame rate + that the video is produced at. A higher frame rate at a + particular bit rate result in individual frames being at a + lower quality. + `, + type => $types{integer}, + category => 'highband', + }, + { + name => 'ZM_WEB_H_VIDEO_MAXFPS', + default => '30', + description => 'What the maximum frame rate for streamed video should be', + help => q` + When using streamed video the main control is the bitrate which + determines how much data can be transmitted. However a lower + bitrate at high frame rates results in a lower quality image. + This option allows you to limit the maximum frame rate to + ensure that video quality is maintained. An additional + advantage is that encoding video at high frame rates is a + processor intensive task when for the most part a very high + frame rate offers little perceptible improvement over one that + has a more manageable resource requirement. Note, this option + is implemented as a cap beyond which binary reduction takes + place. So if you have a device capturing at 15fps and set this + option to 10fps then the video is not produced at 10fps, but + rather at 7.5fps (15 divided by 2) as the final frame rate must + be the original divided by a power of 2. + `, + type => $types{integer}, + category => 'highband', + }, + { + name => 'ZM_WEB_H_SCALE_THUMBS', + default => 'no', + description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', + help => q` + If unset, this option sends the whole image to the browser + which resizes it in the window. If set the image is scaled down + on the server before sending a reduced size image to the + browser to conserve bandwidth at the cost of cpu on the server. + Note that ZM can only perform the resizing if the appropriate + PHP graphics functionality is installed. This is usually + available in the php-gd package. + `, + type => $types{boolean}, + category => 'highband', + }, + { + name => 'ZM_WEB_H_EVENTS_VIEW', + default => 'events', + description => 'What the default view of multiple events should be.', + help => q` + Stored events can be viewed in either an events list format or + in a timeline based one. This option sets the default view that + will be used. Choosing one view here does not prevent the other + view being used as it will always be selectable from whichever + view is currently being used. + `, + type => { + db_type =>'string', + hint =>'events|timeline', + pattern =>qr|^([lt])|i, + format =>q( $1 =~ /^e/ ? 'events' : 'timeline' ) + }, + category => 'highband', + }, + { + name => 'ZM_WEB_H_SHOW_PROGRESS', + default => 'yes', + description => 'Show the progress of replay in event view.', + help => q` + When viewing events an event navigation panel and progress bar + is shown below the event itself. This allows you to jump to + specific points in the event, but can can also dynamically + update to display the current progress of the event replay + itself. This progress is calculated from the actual event + duration and is not directly linked to the replay itself, so on + limited bandwidth connections may be out of step with the + replay. This option allows you to turn off the progress + display, whilst still keeping the navigation aspect, where + bandwidth prevents it functioning effectively. + `, + type => $types{boolean}, + category => 'highband', + }, + { + name => 'ZM_WEB_H_AJAX_TIMEOUT', + default => '3000', + description => 'How long to wait for Ajax request responses (ms)', + help => q` + The newer versions of the live feed and event views use Ajax to + request information from the server and populate the views + dynamically. This option allows you to specify a timeout if + required after which requests are abandoned. A timeout may be + necessary if requests would overwise hang such as on a slow + connection. This would tend to consume a lot of browser memory + and make the interface unresponsive. Ordinarily no requests + should timeout so this setting should be set to a value greater + than the slowest expected response. This value is in + milliseconds but if set to zero then no timeout will be used. + `, + type => $types{integer}, + category => 'highband', + }, + { + name => 'ZM_WEB_M_REFRESH_MAIN', + default => '300', + description => 'How often (in seconds) the main console window should refresh itself', + help => q` + The main console window lists a general status and the event + totals for all monitors. This is not a trivial task and should + not be repeated too frequently or it may affect the performance + of the rest of the system. + `, + type => $types{integer}, + introduction => q` + The next few options control what happens when the client is + running in 'medium' bandwidth mode. You should set these + options for when accessing the ZoneMinder client over a slower + cable or DSL link. In most cases the default values will be + suitable as a starting point. + `, + category => 'medband', + }, + { + name => 'ZM_WEB_M_REFRESH_CYCLE', + default => '20', + description => 'How often (in seconds) the cycle watch window swaps to the next monitor', + help => q` + The cycle watch window is a method of continuously cycling + between images from all of your monitors. This option + determines how often to refresh with a new image. + `, + type => $types{integer}, + category => 'medband', + }, + { + name => 'ZM_WEB_M_REFRESH_IMAGE', + default => '10', + description => 'How often (in seconds) the watched image is refreshed (if not streaming)', + help => q` + The live images from a monitor can be viewed in either streamed + or stills mode. This option determines how often a stills image + is refreshed, it has no effect if streaming is selected. + `, + type => $types{integer}, + category => 'medband', + }, + { + name => 'ZM_WEB_M_REFRESH_STATUS', + default => '5', + description => 'How often (in seconds) the status refreshes itself in the watch window', + help => q` + The monitor window is actually made from several frames. The + one in the middle merely contains a monitor status which needs + to refresh fairly frequently to give a true indication. This + option determines that frequency. + `, + type => $types{integer}, + category => 'medband', + }, + { + name => 'ZM_WEB_M_REFRESH_EVENTS', + default => '60', + description => 'How often (in seconds) the event listing is refreshed in the watch window', + help => q` + The monitor window is actually made from several frames. The + lower framme contains a listing of the last few events for easy + access. This option determines how often this is refreshed. + `, + type => $types{integer}, + category => 'medband', + }, + { + name => 'ZM_WEB_M_CAN_STREAM', + default => 'auto', + description => 'Override the automatic detection of browser streaming capability', + help => q` + If you know that your browser can handle image streams of the + type 'multipart/x-mixed-replace' but ZoneMinder does not detect + this correctly you can set this option to ensure that the + stream is delivered with or without the use of the Cambozola + plugin. Selecting 'yes' will tell ZoneMinder that your browser + can handle the streams natively, 'no' means that it can't and + so the plugin will be used while 'auto' lets ZoneMinder decide. + `, + type => $types{tristate}, + category => 'medband', + }, + { + name => 'ZM_WEB_M_STREAM_METHOD', + default => 'jpeg', + description => 'Which method should be used to send video streams to your browser.', + help => q` + ZoneMinder can be configured to use either mpeg encoded video + or a series or still jpeg images when sending video streams. + This option defines which is used. If you choose mpeg you + should ensure that you have the appropriate plugins available + on your browser whereas choosing jpeg will work natively on + Mozilla and related browsers and with a Java applet on Internet + Explorer + `, + type => { + db_type =>'string', + hint =>'mpeg|jpeg', + pattern =>qr|^([mj])|i, + format =>q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) + }, + category => 'medband', + }, + { + name => 'ZM_WEB_M_DEFAULT_SCALE', + default => '100', + description => q`What the default scaling factor applied to 'live' or 'event' views is (%)`, + help => q` + Normally ZoneMinder will display 'live' or 'event' streams in + their native size. However if you have monitors with large + dimensions or a slow link you may prefer to reduce this size, + alternatively for small monitors you can enlarge it. This + options lets you specify what the default scaling factor will + be. It is expressed as a percentage so 100 is normal size, 200 + is double size etc. + `, + type => { + db_type =>'integer', + hint =>'25|33|50|75|100|150|200|300|400', + pattern =>qr|^(\d+)$|, + format =>q( $1 ) + }, + category => 'medband', + }, + { + name => 'ZM_WEB_M_DEFAULT_RATE', + default => '100', + description => q`What the default replay rate factor applied to 'event' views is (%)`, + help => q` + Normally ZoneMinder will display 'event' streams at their + native rate, i.e. as close to real-time as possible. However if + you have long events it is often convenient to replay them at a + faster rate for review. This option lets you specify what the + default replay rate will be. It is expressed as a percentage so + 100 is normal rate, 200 is double speed etc. + `, + type => { + db_type =>'integer', + hint =>'25|50|100|150|200|400|1000|2500|5000|10000', + pattern =>qr|^(\d+)$|, + format =>q( $1 ) + }, + category => 'medband', + }, + { + name => 'ZM_WEB_M_VIDEO_BITRATE', + default => '75000', + description => 'What the bitrate of the video encoded stream should be set to', + help => q` + When encoding real video via the ffmpeg library a bit rate can + be specified which roughly corresponds to the available + bandwidth used for the stream. This setting effectively + corresponds to a 'quality' setting for the video. A low value + will result in a blocky image whereas a high value will produce + a clearer view. Note that this setting does not control the + frame rate of the video however the quality of the video + produced is affected both by this setting and the frame rate + that the video is produced at. A higher frame rate at a + particular bit rate result in individual frames being at a + lower quality. + `, + type => $types{integer}, + category => 'medband', + }, + { + name => 'ZM_WEB_M_VIDEO_MAXFPS', + default => '10', + description => 'What the maximum frame rate for streamed video should be', + help => q` + When using streamed video the main control is the bitrate which + determines how much data can be transmitted. However a lower + bitrate at high frame rates results in a lower quality image. + This option allows you to limit the maximum frame rate to + ensure that video quality is maintained. An additional + advantage is that encoding video at high frame rates is a + processor intensive task when for the most part a very high + frame rate offers little perceptible improvement over one that + has a more manageable resource requirement. Note, this option + is implemented as a cap beyond which binary reduction takes + place. So if you have a device capturing at 15fps and set this + option to 10fps then the video is not produced at 10fps, but + rather at 7.5fps (15 divided by 2) as the final frame rate must + be the original divided by a power of 2. + `, + type => $types{integer}, + category => 'medband', + }, + { + name => 'ZM_WEB_M_SCALE_THUMBS', + default => 'yes', + description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', + help => q` + If unset, this option sends the whole image to the browser + which resizes it in the window. If set the image is scaled down + on the server before sending a reduced size image to the + browser to conserve bandwidth at the cost of cpu on the server. + Note that ZM can only perform the resizing if the appropriate + PHP graphics functionality is installed. This is usually + available in the php-gd package. + `, + type => $types{boolean}, + category => 'medband', + }, + { + name => 'ZM_WEB_M_EVENTS_VIEW', + default => 'events', + description => 'What the default view of multiple events should be.', + help => q` + Stored events can be viewed in either an events list format or + in a timeline based one. This option sets the default view that + will be used. Choosing one view here does not prevent the other + view being used as it will always be selectable from whichever + view is currently being used. + `, + type => { + db_type =>'string', + hint =>'events|timeline', + pattern =>qr|^([lt])|i, + format =>q( $1 =~ /^e/ ? 'events' : 'timeline' ) + }, + category => 'medband', + }, + { + name => 'ZM_WEB_M_SHOW_PROGRESS', + default => 'yes', + description => 'Show the progress of replay in event view.', + help => q` + When viewing events an event navigation panel and progress bar + is shown below the event itself. This allows you to jump to + specific points in the event, but can can also dynamically + update to display the current progress of the event replay + itself. This progress is calculated from the actual event + duration and is not directly linked to the replay itself, so on + limited bandwidth connections may be out of step with the + replay. This option allows you to turn off the progress + display, whilst still keeping the navigation aspect, where + bandwidth prevents it functioning effectively. + `, + type => $types{boolean}, + category => 'medband', + }, + { + name => 'ZM_WEB_M_AJAX_TIMEOUT', + default => '5000', + description => 'How long to wait for Ajax request responses (ms)', + help => q` + The newer versions of the live feed and event views use Ajax to + request information from the server and populate the views + dynamically. This option allows you to specify a timeout if + required after which requests are abandoned. A timeout may be + necessary if requests would overwise hang such as on a slow + connection. This would tend to consume a lot of browser memory + and make the interface unresponsive. Ordinarily no requests + should timeout so this setting should be set to a value greater + than the slowest expected response. This value is in + milliseconds but if set to zero then no timeout will be used. + `, + type => $types{integer}, + category => 'medband', + }, + { + name => 'ZM_WEB_L_REFRESH_MAIN', + default => '300', + description => 'How often (in seconds) the main console window should refresh itself', + introduction => q` + The next few options control what happens when the client is + running in 'low' bandwidth mode. You should set these options + for when accessing the ZoneMinder client over a modem or slow + link. In most cases the default values will be suitable as a + starting point. + `, + help => q` + The main console window lists a general status and the event + totals for all monitors. This is not a trivial task and should + not be repeated too frequently or it may affect the performance + of the rest of the system. + `, + type => $types{integer}, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_REFRESH_CYCLE', + default => '30', + description => 'How often (in seconds) the cycle watch window swaps to the next monitor', + help => q` + The cycle watch window is a method of continuously cycling + between images from all of your monitors. This option + determines how often to refresh with a new image. + `, + type => $types{integer}, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_REFRESH_IMAGE', + default => '15', + description => 'How often (in seconds) the watched image is refreshed (if not streaming)', + help => q` + The live images from a monitor can be viewed in either streamed + or stills mode. This option determines how often a stills image + is refreshed, it has no effect if streaming is selected. + `, + type => $types{integer}, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_REFRESH_STATUS', + default => '10', + description => 'How often (in seconds) the status refreshes itself in the watch window', + help => q` + The monitor window is actually made from several frames. The + one in the middle merely contains a monitor status which needs + to refresh fairly frequently to give a true indication. This + option determines that frequency. + `, + type => $types{integer}, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_REFRESH_EVENTS', + default => '180', + description => 'How often (in seconds) the event listing is refreshed in the watch window', + help => q` + The monitor window is actually made from several frames. The + lower framme contains a listing of the last few events for easy + access. This option determines how often this is refreshed. + `, + type => $types{integer}, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_CAN_STREAM', + default => 'auto', + description => 'Override the automatic detection of browser streaming capability', + help => q` + If you know that your browser can handle image streams of the + type 'multipart/x-mixed-replace' but ZoneMinder does not detect + this correctly you can set this option to ensure that the + stream is delivered with or without the use of the Cambozola + plugin. Selecting 'yes' will tell ZoneMinder that your browser + can handle the streams natively, 'no' means that it can't and + so the plugin will be used while 'auto' lets ZoneMinder decide. + `, + type => $types{tristate}, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_STREAM_METHOD', + default => 'jpeg', + description => 'Which method should be used to send video streams to your browser.', + help => q` + ZoneMinder can be configured to use either mpeg encoded video + or a series or still jpeg images when sending video streams. + This option defines which is used. If you choose mpeg you + should ensure that you have the appropriate plugins available + on your browser whereas choosing jpeg will work natively on + Mozilla and related browsers and with a Java applet on Internet + Explorer + `, + type => { + db_type =>'string', + hint =>'mpeg|jpeg', + pattern =>qr|^([mj])|i, + format =>q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) + }, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_DEFAULT_SCALE', + default => '100', + description => q`What the default scaling factor applied to 'live' or 'event' views is (%)`, + help => q` + Normally ZoneMinder will display 'live' or 'event' streams in + their native size. However if you have monitors with large + dimensions or a slow link you may prefer to reduce this size, + alternatively for small monitors you can enlarge it. This + options lets you specify what the default scaling factor will + be. It is expressed as a percentage so 100 is normal size, 200 + is double size etc. + `, + type => { + db_type =>'integer', + hint =>'25|33|50|75|100|150|200|300|400', + pattern =>qr|^(\d+)$|, + format =>q( $1 ) + }, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_DEFAULT_RATE', + default => '100', + description => q`What the default replay rate factor applied to 'event' views is (%)`, + help => q` + Normally ZoneMinder will display 'event' streams at their + native rate, i.e. as close to real-time as possible. However if + you have long events it is often convenient to replay them at a + faster rate for review. This option lets you specify what the + default replay rate will be. It is expressed as a percentage so + 100 is normal rate, 200 is double speed etc. + `, + type => { + db_type =>'integer', + hint =>'25|50|100|150|200|400|1000|2500|5000|10000', + pattern =>qr|^(\d+)$|, format=>q( $1 ) + }, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_VIDEO_BITRATE', + default => '25000', + description => 'What the bitrate of the video encoded stream should be set to', + help => q` + When encoding real video via the ffmpeg library a bit rate can + be specified which roughly corresponds to the available + bandwidth used for the stream. This setting effectively + corresponds to a 'quality' setting for the video. A low value + will result in a blocky image whereas a high value will produce + a clearer view. Note that this setting does not control the + frame rate of the video however the quality of the video + produced is affected both by this setting and the frame rate + that the video is produced at. A higher frame rate at a + particular bit rate result in individual frames being at a + lower quality. + `, + type => $types{integer}, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_VIDEO_MAXFPS', + default => '5', + description => 'What the maximum frame rate for streamed video should be', + help => q` + When using streamed video the main control is the bitrate which + determines how much data can be transmitted. However a lower + bitrate at high frame rates results in a lower quality image. + This option allows you to limit the maximum frame rate to + ensure that video quality is maintained. An additional + advantage is that encoding video at high frame rates is a + processor intensive task when for the most part a very high + frame rate offers little perceptible improvement over one that + has a more manageable resource requirement. Note, this option + is implemented as a cap beyond which binary reduction takes + place. So if you have a device capturing at 15fps and set this + option to 10fps then the video is not produced at 10fps, but + rather at 7.5fps (15 divided by 2) as the final frame rate must + be the original divided by a power of 2. + `, + type => $types{integer}, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_SCALE_THUMBS', + default => 'yes', + description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', + help => q` + If unset, this option sends the whole image to the browser + which resizes it in the window. If set the image is scaled down + on the server before sending a reduced size image to the + browser to conserve bandwidth at the cost of cpu on the server. + Note that ZM can only perform the resizing if the appropriate + PHP graphics functionality is installed. This is usually + available in the php-gd package. + `, + type => $types{boolean}, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_EVENTS_VIEW', + default => 'events', + description => 'What the default view of multiple events should be.', + help => q` + Stored events can be viewed in either an events list format or + in a timeline based one. This option sets the default view that + will be used. Choosing one view here does not prevent the other + view being used as it will always be selectable from whichever + view is currently being used. + `, + type => { + db_type =>'string', + hint =>'events|timeline', + pattern =>qr|^([lt])|i, + format =>q( $1 =~ /^e/ ? 'events' : 'timeline' ) + }, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_SHOW_PROGRESS', + default => 'no', + description => 'Show the progress of replay in event view.', + help => q` + When viewing events an event navigation panel and progress bar + is shown below the event itself. This allows you to jump to + specific points in the event, but can can also dynamically + update to display the current progress of the event replay + itself. This progress is calculated from the actual event + duration and is not directly linked to the replay itself, so on + limited bandwidth connections may be out of step with the + replay. This option allows you to turn off the progress + display, whilst still keeping the navigation aspect, where + bandwidth prevents it functioning effectively. + `, + type => $types{boolean}, + category => 'lowband', + }, + { + name => 'ZM_WEB_L_AJAX_TIMEOUT', + default => '10000', + description => 'How long to wait for Ajax request responses (ms)', + help => q` + The newer versions of the live feed and event views use Ajax to + request information from the server and populate the views + dynamically. This option allows you to specify a timeout if + required after which requests are abandoned. A timeout may be + necessary if requests would overwise hang such as on a slow + connection. This would tend to consume a lot of browser memory + and make the interface unresponsive. Ordinarily no requests + should timeout so this setting should be set to a value greater + than the slowest expected response. This value is in + milliseconds but if set to zero then no timeout will be used. + `, + type => $types{integer}, + category => 'lowband', + }, + { + name => 'ZM_DYN_LAST_VERSION', + default => '', + description => 'What the last version of ZoneMinder recorded from zoneminder.com is', + help => '', + type => $types{string}, + readonly => 1, + category => 'dynamic', + }, + { + name => 'ZM_DYN_CURR_VERSION', + default => '@VERSION@', + description => q` + What the effective current version of ZoneMinder is, might be + different from actual if versions ignored + `, + help => '', + type => $types{string}, + readonly => 1, + category => 'dynamic', + }, + { + name => 'ZM_DYN_DB_VERSION', + default => '@VERSION@', + description => 'What the version of the database is, from zmupdate', + help => '', + type => $types{string}, + readonly => 1, + category => 'dynamic', + }, + { + name => 'ZM_DYN_LAST_CHECK', + default => '', + description => 'When the last check for version from zoneminder.com was', + help => '', + type => $types{integer}, + readonly => 1, + category => 'dynamic', + }, + { + name => 'ZM_DYN_NEXT_REMINDER', + default => '', + description => 'When the earliest time to remind about versions will be', + help => '', + type => $types{string}, + readonly => 1, + category => 'dynamic', + }, + { + name => 'ZM_DYN_DONATE_REMINDER_TIME', + default => 0, + description => 'When the earliest time to remind about donations will be', + help => '', + type => $types{integer}, + readonly => 1, + category => 'dynamic', + }, + { + name => 'ZM_DYN_SHOW_DONATE_REMINDER', + default => 'yes', + description => 'Remind about donations or not', + help => '', + type => $types{boolean}, + readonly => 1, + category => 'dynamic', + }, + { + name => 'ZM_SSMTP_MAIL', + default => 'no', + description => q` + Use a SSMTP mail server if available. + NEW_MAIL_MODULES must be enabled + `, + requires => [ + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' }, + { name => 'ZM_NEW_MAIL_MODULES', value => 'yes' } + ], + help => q` + SSMTP is a lightweight and efficient method to send email. + The SSMTP application is not installed by default. + NEW_MAIL_MODULES must also be enabled. + Please visit: http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder + for setup and configuration help. + `, + type => $types{boolean}, + category => 'mail', + }, + { + name => 'ZM_SSMTP_PATH', + default => '', + description => 'SSMTP executable path', + requires => [{ name => 'ZM_SSMTP_MAIL', value => 'yes' }], + help => q` + Recommend setting the path to the SSMTP application. + If path is not defined. Zoneminder will try to determine + the path via shell command. Example path: /usr/sbin/ssmtp. + `, + type => $types{string}, + category => 'mail', + }, + ); our %options_hash = map { ( $_->{name}, $_ ) } @options; -# This function should never need to be called explicitly, except if +# This function should never need to be called explicitly, except if # this module is 'require'd rather than 'use'd. See zmconfgen.pl. -sub initialiseConfig -{ - return if ( $configInitialised ); +sub initialiseConfig { + return if ( $configInitialised ); - # Do some initial data munging to finish the data structures - # Create option ids - my $option_id = 0; - foreach my $option ( @options ) - { - if ( defined($option->{default}) ) - { - $option->{value} = $option->{default} - } - else - { - $option->{value} = ''; - } - #next if ( $option->{category} eq 'hidden' ); - $option->{id} = $option_id++; +# Do some initial data munging to finish the data structures +# Create option ids + my $option_id = 0; + foreach my $option ( @options ) { + if ( defined($option->{default}) ) { + $option->{value} = $option->{default} + } else { + $option->{value} = ''; } - $configInitialised = 1; +#next if ( $option->{category} eq 'hidden' ); + $option->{id} = $option_id++; + } + $configInitialised = 1; } 1; @@ -3909,11 +3916,11 @@ ZoneMinder::ConfigData - ZoneMinder Configuration Data module =head1 SYNOPSIS - use ZoneMinder::ConfigData; - use ZoneMinder::ConfigData qw(:all); +use ZoneMinder::ConfigData; +use ZoneMinder::ConfigData qw(:all); - loadConfigFromDB(); - saveConfigToDB(); +loadConfigFromDB(); +saveConfigToDB(); =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm index 0822dc3d3..4603645fa 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -43,112 +43,95 @@ use ZoneMinder::Database qw(:all); our $AUTOLOAD; -sub new -{ - my $class = shift; - my $id = shift; - my $self = {}; - $self->{name} = "PelcoD"; - if ( !defined($id) ) - { - Fatal( "No monitor defined when invoking protocol ".$self->{name} ); +sub new { + my $class = shift; + my $id = shift; + my $self = {}; + $self->{name} = "PelcoD"; + if ( !defined($id) ) { + Fatal( "No monitor defined when invoking protocol ".$self->{name} ); + } + $self->{id} = $id; + bless( $self, $class ); + return $self; +} + +sub DESTROY { +} + +sub AUTOLOAD { + my $self = shift; + my $class = ref($self) || croak( "$self not object" ); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( exists($self->{$name}) ) + { + return( $self->{$name} ); + } + croak( "Can't access $name member of object of class $class" ); +} + +sub getKey { + my $self = shift; + return( $self->{id} ); +} + +sub open { + my $self = shift; + Fatal( "No open method defined for protocol ".$self->{name} ); +} + +sub close { + my $self = shift; + Fatal( "No close method defined for protocol ".$self->{name} ); +} + +sub loadMonitor { + my $self = shift; + if ( !$self->{Monitor} ) { + if ( !($self->{Monitor} = zmDbGetMonitor( $self->{id} )) ) { + Fatal( "Monitor id ".$self->{id}." not found or not controllable" ); } - $self->{id} = $id; - bless( $self, $class ); - return $self; -} - -sub DESTROY -{ -} - -sub AUTOLOAD -{ - my $self = shift; - my $class = ref($self) || croak( "$self not object" ); - my $name = $AUTOLOAD; - $name =~ s/.*://; - if ( exists($self->{$name}) ) - { - return( $self->{$name} ); + if ( defined($self->{Monitor}->{AutoStopTimeout}) ) { +# Convert to microseconds. + $self->{Monitor}->{AutoStopTimeout} = int(1000000*$self->{Monitor}->{AutoStopTimeout}); } - croak( "Can't access $name member of object of class $class" ); + } } -sub getKey -{ - my $self = shift; - return( $self->{id} ); +sub getParam { + my $self = shift; + my $params = shift; + my $name = shift; + my $default = shift; + + if ( defined($params->{$name}) ) { + return( $params->{$name} ); + } elsif ( defined($default) ) { + return( $default ); + } + Fatal( "Missing mandatory parameter '$name'" ); } -sub open -{ - my $self = shift; - Fatal( "No open method defined for protocol ".$self->{name} ); +sub executeCommand { + my $self = shift; + my $params = shift; + + $self->loadMonitor(); + + my $command = $params->{command}; + delete $params->{command}; + +#if ( !defined($self->{$command}) ) +#{ +#Fatal( "Unsupported command '$command'" ); +#} + &{$self->{$command}}( $self, $params ); } -sub close -{ - my $self = shift; - Fatal( "No close method defined for protocol ".$self->{name} ); -} - -sub loadMonitor -{ - my $self = shift; - if ( !$self->{Monitor} ) - { - if ( !($self->{Monitor} = zmDbGetMonitor( $self->{id} )) ) - { - Fatal( "Monitor id ".$self->{id}." not found or not controllable" ); - } - if ( defined($self->{Monitor}->{AutoStopTimeout}) ) - { - # Convert to microseconds. - $self->{Monitor}->{AutoStopTimeout} = int(1000000*$self->{Monitor}->{AutoStopTimeout}); - } - } -} - -sub getParam -{ - my $self = shift; - my $params = shift; - my $name = shift; - my $default = shift; - - if ( defined($params->{$name}) ) - { - return( $params->{$name} ); - } - elsif ( defined($default) ) - { - return( $default ); - } - Fatal( "Missing mandatory parameter '$name'" ); -} - -sub executeCommand -{ - my $self = shift; - my $params = shift; - - $self->loadMonitor(); - - my $command = $params->{command}; - delete $params->{command}; - - #if ( !defined($self->{$command}) ) - #{ - #Fatal( "Unsupported command '$command'" ); - #} - &{$self->{$command}}( $self, $params ); -} - -sub printMsg -{ - my $self = shift; - Fatal( "No printMsg method defined for protocol ".$self->{name} ); +sub printMsg { + my $self = shift; + Fatal( "No printMsg method defined for protocol ".$self->{name} ); } 1; @@ -161,8 +144,8 @@ ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS - use ZoneMinder::Database; - blah blah blah +use ZoneMinder::Database; +blah blah blah =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/3S.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/3S.pm index 47d783bff..2ccd275ce 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/3S.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/3S.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/AxisV2.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/AxisV2.pm index 2372a2d81..daa39ed72 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/AxisV2.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/AxisV2.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8608W_Y2k.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8608W_Y2k.pm index 2e5b21f76..c8a77aa2c 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8608W_Y2k.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8608W_Y2k.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # V1.0 ==================================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8620_Y2k.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8620_Y2k.pm index 3e2addf85..5eb06219d 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8620_Y2k.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8620_Y2k.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # V1.1 ==================================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8908W.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8908W.pm index dd30484c3..9cf00d077 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8908W.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8908W.pm @@ -16,7 +16,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -215,6 +215,55 @@ sub presetHome $self->sendCmd( 'decoder_control.cgi?command=25&' ); } +sub moveRelUp +{ + my $self = shift; + Debug( "Move Up" ); + $self->sendCmd( 'decoder_control.cgi?command=0&onestep=1&' ); +} + +#Down Arrow +sub moveRelDown +{ + my $self = shift; + Debug( "Move Down" ); + $self->sendCmd( 'decoder_control.cgi?command=2&onestep=1&' ); +} + +#Left Arrow +sub moveRelLeft +{ + my $self = shift; + Debug( "Move Left" ); + $self->sendCmd( 'decoder_control.cgi?command=6&onestep=1&' ); +} + +#Right Arrow +sub moveRelRight +{ + my $self = shift; + Debug( "Move Right" ); + $self->sendCmd( 'decoder_control.cgi?command=4&onestep=1&' ); +} + +#Go to preset +sub presetGoto +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + my $result = undef; + if ( $preset > 0 && $preset <= 32 ) { + my $command=31+(($preset-1) * 2); + Debug( "Goto Preset $preset with command $command" ); + $result=$self->sendCmd( 'decoder_control.cgi?command=' . $command . '&' ); + } + else { + Error( "Unsupported preset $preset : must be between 1 and 32" ); + } + return $result; +} + 1; __END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8918W.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8918W.pm index c9997526d..9d023320a 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8918W.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI8918W.pm @@ -28,7 +28,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI9821W_Y2k.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI9821W_Y2k.pm index dd73a04e6..7165fd686 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI9821W_Y2k.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI9821W_Y2k.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================================= # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI9831W.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI9831W.pm index 739ff1175..37022a188 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/FI9831W.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/FI9831W.pm @@ -23,7 +23,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================================= # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm new file mode 100644 index 000000000..f9cabd5fa --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm @@ -0,0 +1,411 @@ +# ========================================================================== +# +# ZoneMinder HikVision Control Protocol Module +# Copyright (C) 2016 Terry Sanders +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ========================================================================== +# +# This module contains an implementation of the HikVision ISAPI camera control +# protocol +# +package ZoneMinder::Control::HikVision; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Control; + +our @ISA = qw(ZoneMinder::Control); + +# ========================================================================== +# +# HiKVision ISAPI Control Protocol +# +# Set the following: +# ControlAddress: username:password@camera_webaddress:port +# ControlDevice: IP Camera Model +# +# ========================================================================== + +use ZoneMinder::Logger qw(:all); + +use Time::HiRes qw( usleep ); + +use LWP::UserAgent; +use HTTP::Cookies; + +my $ChannelID = 1; # Usually... +my $DefaultFocusSpeed = 50; # Should be between 1 and 100 +my $DefaultIrisSpeed = 50; # Should be between 1 and 100 + +sub new { + my $class = shift; + my $id = shift; + my $self = ZoneMinder::Control->new( $id ); + bless( $self, $class ); + srand( time() ); + return $self; +} + +our $AUTOLOAD; + +sub AUTOLOAD { + my $self = shift; + my $class = ref($self) || croak( "$self not object" ); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( exists($self->{$name}) ) + { + return( $self->{$name} ); + } + Fatal( "Can't access $name member of object of class $class" ); +} +sub open { + my $self = shift; + $self->loadMonitor(); + # + # Create a UserAgent for the requests + # + $self->{UA} = LWP::UserAgent->new(); + $self->{UA}->cookie_jar( {} ); + # + # Extract the username/password host/port from ControlAddress + # + my ($user,$pass,$host,$port); + if( $self->{Monitor}{ControlAddress} =~ /^([^:]+):([^@]+)@(.+)/ ) { # user:pass@host... + $user = $1; + $pass = $2; + $host = $3; + } + elsif( $self->{Monitor}{ControlAddress} =~ /^([^@]+)@(.+)/ ) { # user@host... + $user = $1; + $host = $2; + } + else { # Just a host + $host = $self->{Monitor}{ControlAddress}; + } + # Check if it is a host and port or just a host + if( $host =~ /([^:]+):(.+)/ ) { + $host = $1; + $port = $2; + } + else { + $port = 80; + } + # Save the credentials + if( defined($user) ) { + $self->{UA}->credentials( "$host:$port", $self->{Monitor}{ControlDevice}, $user, $pass ); + } + # Save the base url + $self->{BaseURL} = "http://$host:$port"; +} +sub PutCmd { + my $self = shift; + my $cmd = shift; + my $content = shift; + my $req = HTTP::Request->new(PUT => "$self->{BaseURL}/$cmd"); + if(defined($content)) { + $req->content_type("application/x-www-form-urlencoded; charset=UTF-8"); + $req->content('' . "\n" . $content); + } + my $res = $self->{UA}->request($req); + unless( $res->is_success ) { + # + # The camera timeouts connections at short intervals. When this + # happens the user agent connects again and uses the same auth tokens. + # The camera rejects this and asks for another token but the UserAgent + # just gives up. Because of this I try the request again and it should + # succeed the second time if the credentials are correct. + # + if($res->code == 401) { + $res = $self->{UA}->request($req); + unless( $res->is_success ) { + # + # It has failed authentication. The odds are + # that the user has set some paramater incorrectly + # so check the realm against the ControlDevice + # entry and send a message if different + # + my $auth = $res->headers->www_authenticate; + foreach (split(/\s*,\s*/,$auth)) { + if( $_ =~ /^realm\s*=\s*"([^"]+)"/i ) { + if( $self->{Monitor}{ControlDevice} ne $1 ) { + Info "Control Device appears to be incorrect."; + Info "Control Device should be set to \"$1\"."; + Info "Control Device currently set to \"$self->{Monitor}{ControlDevice}\"."; + } + } + } + # + # Check for username/password + # + if( $self->{Monitor}{ControlAddress} =~ /.+:(.+)@.+/ ) { + Info "Check username/password is correct"; + } elsif ( $self->{Monitor}{ControlAddress} =~ /^[^:]+@.+/ ) { + Info "No password in Control Address. Should there be one?"; + } elsif ( $self->{Monitor}{ControlAddress} =~ /^:.+@.+/ ) { + Info "Password but no username in Control Address."; + } else { + Info "Missing username and password in Control Address."; + } + Fatal $res->status_line; + } + } + else { + Fatal $res->status_line; + } + } +} +# +# The move continuous functions all call moveVector +# with the direction to move in. This includes zoom +# +sub moveVector { + my $self = shift; + my $pandirection = shift; + my $tiltdirection = shift; + my $zoomdirection = shift; + my $params = shift; + my $command; # The ISAPI/PTZ command + + # Calculate autostop time + my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; + # Change from microseconds to milliseconds + $duration = int($duration/1000); + my $momentxml; + if( $duration ) { + $momentxml = "$duration"; + $command = "ISAPI/PTZCtrl/channels/$ChannelID/momentary"; + } + else { + $momentxml = ""; + $command = "ISAPI/PTZCtrl/channels/$ChannelID/continuous"; + } + # Calculate movement speeds + my $x = $pandirection * $self->getParam( $params, 'panspeed', 0 ); + my $y = $tiltdirection * $self->getParam( $params, 'tiltspeed', 0 ); + my $z = $zoomdirection * $self->getParam( $params, 'speed', 0 ); + # Create the XML + my $xml = "$x$y$z$momentxml"; + # Send it to the camera + $self->PutCmd($command,$xml); +} +sub moveStop { $_[0]->moveVector( 0, 0, 0, splice(@_,1)); } +sub moveConUp { $_[0]->moveVector( 0, 1, 0, splice(@_,1)); } +sub moveConUpRight { $_[0]->moveVector( 1, 1, 0, splice(@_,1)); } +sub moveConRight { $_[0]->moveVector( 1, 0, 0, splice(@_,1)); } +sub moveConDownRight { $_[0]->moveVector( 1, -1, 0, splice(@_,1)); } +sub moveConDown { $_[0]->moveVector( 0, -1, 0, splice(@_,1)); } +sub moveConDownLeft { $_[0]->moveVector( -1, -1, 0, splice(@_,1)); } +sub moveConLeft { $_[0]->moveVector( -1, 0, 0, splice(@_,1)); } +sub moveConUpLeft { $_[0]->moveVector( -1, 1, 0, splice(@_,1)); } +sub zoomConTele { $_[0]->moveVector( 0, 0, 1, splice(@_,1)); } +sub zoomConWide { $_[0]->moveVector( 0, 0,-1, splice(@_,1)); } +# +# Presets including Home set and clear +# +sub presetGoto { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params,'preset'); + $self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/presets/$preset/goto"); +} +sub presetSet { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params,'preset'); + my $xml = "$preset"; + $self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/presets/$preset",$xml); +} +sub presetHome { + my $self = shift; + my $params = shift; + $self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/homeposition/goto"); +} +# +# Focus controls all call Focus with a +/- speed +# +sub Focus { + my $self = shift; + my $speed = shift; + my $xml = "$speed"; + $self->PutCmd("ISAPI/System/Video/inputs/channels/$ChannelID/focus",$xml); +} +sub focusConNear { + my $self = shift; + my $params = shift; + + # Calculate autostop time + my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; + # Get the focus speed + my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); + $self->Focus(-$speed); + if($duration) { + usleep($duration); + $self->moveStop($params); + } +} +sub Near { + my $self = shift; + my $params = shift; + $self->Focus(-$DefaultFocusSpeed); +} +sub focusAbsNear { + my $self = shift; + my $params = shift; + + # Get the focus speed + my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); + $self->Focus(-$speed); +} +sub focusRelNear { + my $self = shift; + my $params = shift; + # Get the focus speed + my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); + $self->Focus(-$speed); +} +sub focusConFar { + my $self = shift; + my $params = shift; + + # Calculate autostop time + my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; + # Get the focus speed + my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); + $self->Focus($speed); + if($duration) { + usleep($duration); + $self->moveStop($params); + } +} +sub Far { + my $self = shift; + my $params = shift; + $self->Focus($DefaultFocusSpeed); +} +sub focusAbsFar { + my $self = shift; + my $params = shift; + + # Get the focus speed + my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); + $self->Focus($speed); +} +sub focusRelFar { + my $self = shift; + my $params = shift; + + # Get the focus speed + my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); + $self->Focus($speed); +} +# +# Iris controls all call Iris with a +/- speed +# +sub Iris { + my $self = shift; + my $speed = shift; + + my $xml = "$speed"; + $self->PutCmd("ISAPI/System/Video/inputs/channels/$ChannelID/iris",$xml); +} +sub irisConClose { + my $self = shift; + my $params = shift; + + # Calculate autostop time + my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; + # Get the iris speed + my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); + $self->Iris(-$speed); + if($duration) { + usleep($duration); + $self->moveStop($params); + } +} +sub Close { + my $self = shift; + my $params = shift; + + $self->Iris(-$DefaultIrisSpeed); +} +sub irisAbsClose { + my $self = shift; + my $params = shift; + + # Get the iris speed + my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); + $self->Iris(-$speed); +} +sub irisRelClose { + my $self = shift; + my $params = shift; + + # Get the iris speed + my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); + $self->Iris(-$speed); +} +sub irisConOpen { + my $self = shift; + my $params = shift; + + # Calculate autostop time + my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; + # Get the iris speed + my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); + $self->Iris($speed); + if($duration) { + usleep($duration); + $self->moveStop($params); + } +} +sub Open { + my $self = shift; + my $params = shift; + + $self->Iris($DefaultIrisSpeed); +} +sub irisAbsOpen { + my $self = shift; + my $params = shift; + + # Get the iris speed + my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); + $self->Iris($speed); +} +sub irisRelOpen { + my $self = shift; + my $params = shift; + + # Get the iris speed + my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); + $self->Iris($speed); +} +# +# reset (reboot) the device +# +sub reset { + my $self = shift; + + $self->PutCmd("ISAPI/System/reboot"); +} + +1; + diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/IPCC7210W.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/IPCC7210W.pm old mode 100755 new mode 100644 index cd21316d3..01cff6c62 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/IPCC7210W.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/IPCC7210W.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Keekoon.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Keekoon.pm new file mode 100644 index 000000000..0fc14135d --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Keekoon.pm @@ -0,0 +1,297 @@ +# ========================================================================== +# +# ZoneMinder Keekoon Control Protocol Module +# This code was mostly derived from other ZM Control modules +# +# ========================================================================== +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# ========================================================================== +# +# Tested: KK002 (22 July 2016) +# +# Usage: +# ====== +# +# Copy this file to say /usr/share/perl5/ZoneMinder/Control (Debian/Ubuntu) +# +# Create a new Control Capabilities: +# Main: Name Keekoon, Type = Remote, Protocol = Keekoon +# Move: Can Move, Can Move Diagonally, Can Move Continous +# Pan: Can Pan +# Tilt: Can Tilt +# Presets: Has Presets, Num Presets = 6, Can Set Presets +# +# Set the ControlAddress in the camera definition, use the format: +# http(s)://username:password@address:port +# +# eg : http://admin:adminpass@10.10.10.1:80 +# or : https://admin:password@mycamera.example.co.uk:80 +# +# Return Location to Preset 1 +# Auto Stop Timeout = 0.5 is a good starting point +# +# =========================================================================== +# Problems: Enable debug and watch /tmp/zm_debug.log. The +# correct debug log can be found by date stamp. +# Enable/disable the Source for the camera in the web GUI +# each time you edit this script. If the pid doesn't +# change then you have not restarted it. +# Errors like this: +# [Error in response to Request:'400 URL must be absolute'] +# means that you have not specified all the parts in ControlAddress or the +# Regex has failed to parse it correctly +# +# ========================================================================= +# Notes: +# Example command from docs, at http://www.keekoonvision.com/for-developers-a: +# Up: http://camera_ip:web_port/decoder_control.cgi?command=0&user=username&pwd=password +# However the camera actually uses basic auth and not user= etc +# +# Test URLs with something like this +# curl -XGET -u user:pass "http://cam.example.co.uk:80/decoder_control.cgi?command=1 +# +# These cameras have a default admin user but can have six more defined +# with membership of three groups +# https is not directly supported but could be via say HA Proxy, so that +# is included rather than hardstrapping http:// +# ========================================================================== + +package ZoneMinder::Control::Keekoon; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Control; + +our @ISA = qw(ZoneMinder::Control); + +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Config qw(:all); +use Time::HiRes qw( usleep ); + +sub new +{ + + my $class = shift; + my $id = shift; + my $self = ZoneMinder::Control->new( $id ); + my $logindetails = ""; + bless( $self, $class ); + srand( time() ); + return $self; +} + +our $AUTOLOAD; + +sub AUTOLOAD +{ + my $self = shift; + my $class = ref( ) || croak( "$self not object" ); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( exists($self->{$name}) ) + { + return( $self->{$name} ); + } + Fatal( "Can't access $name member of object of class $class" ); +} + +sub open +{ + my $self = shift; + + $self->loadMonitor(); + + use LWP::UserAgent; + + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); + + $self->{state} = 'open'; + + Info( "Open" ); + +} + +sub close +{ + my $self = shift; + $self->{state} = 'closed'; +} + +sub printMsg +{ + my $self = shift; + my $msg = shift; + my $msg_len = length($msg); + + Debug( $msg."[".$msg_len."]" ); +} + +sub sendCmd +{ + my $self = shift; + my $cmd = shift; + my $result = undef; + + my ( $PROTOCOL, $USER, $PASS, $ADDR, $PORT ) + = $self->{Monitor}->{ControlAddress} =~ /^(https?):\/\/(.*):(.*)@(.*):(\d+)$/; + my $URL = $PROTOCOL."://".$ADDR.":".$PORT."/decoder_control.cgi?command=".$cmd; + + Debug( "ControlAddress from camera Control setting:".$self->{Monitor}->{ControlAddress} ); + Debug( "URL parsed from ControlAddress:".$URL); + + my $req = HTTP::Request->new( GET=>$URL ); + + # Do Basic Auth + $req->authorization_basic($USER, $PASS); + + my $res = $self->{ua}->request($req); + + if ( $res->is_success ) + { + $result = !undef; + } + else + { + Error( "Error in response to Request:'".$res->status_line()."'" ); + } + + return( $result ); +} + +# Set autoStop timeout on the Control tab for the camera +sub autoStop +{ + my $self = shift; + my $stop_command = shift; + my $autostop = shift; + + if( $stop_command && $autostop) + { + Debug( "Auto Stop" ); + usleep( $autostop ); + my $cmd = $stop_command; + $self->sendCmd( $cmd ); + } +} + +sub moveConUp +{ + my $self = shift; + my $cmd = "0"; + my $stop_command = "1"; + Debug( "Move Up" ); + $self->sendCmd( $cmd ); + $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +sub moveConDown +{ + my $self = shift; + my $cmd = "2"; + my $stop_command = "3"; + Debug( "Move Down" ); + $self->sendCmd( $cmd ); + $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +sub moveConLeft +{ + my $self = shift; + my $cmd = "4"; + my $stop_command = "5"; + Debug( "Move Left" ); + $self->sendCmd( $cmd ); + $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +sub moveConRight +{ + my $self = shift; + my $cmd = "6"; + my $stop_command = "7"; + Debug( "Move Right" ); + $self->sendCmd( $cmd ); + $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); +} + +sub moveConUpRight +{ + my $self = shift; + Debug( "Move Diagonally Up Right" ); + $self->moveConUp( ); + $self->moveConRight( ); +} + +sub moveConDownRight +{ + my $self = shift; + Debug( "Move Diagonally Down Right" ); + $self->moveConDown( ); + $self->moveConRight( ); +} + +sub moveConUpLeft +{ + my $self = shift; + Debug( "Move Diagonally Up Left" ); + $self->moveConUp( ); + $self->moveConLeft( ); +} + +sub moveConDownLeft +{ + my $self = shift; + Debug( "Move Diagonally Down Left" ); + $self->moveConDown( ); + $self->moveConLeft( ); +} + +# SET: 30,32,34,36,38,40 for presets 1-6 +sub presetSet +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + + Debug( "Set Preset No: " . $preset ); + + if (( $preset >= 1 ) && ( $preset <= 6 )) { + my $cmd = (($preset*2) + 28); + $self->sendCmd( $cmd ); + Debug( "Set preset cmd: " . $cmd ); + } + +} + +# GOTO: 31,33,35,37,39,41 for presets 1-6 +sub presetGoto +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Goto Preset No: " . $preset ); + + if (( $preset >= 1 ) && ( $preset <= 6 )) { + my $cmd = (($preset*2) + 29); + $self->sendCmd( $cmd ); + Debug( "Goto Preset cmd: " . $cmd ); + } + +} + +1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/LoftekSentinel.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/LoftekSentinel.pm index 610b8d78c..a5500e2b0 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/LoftekSentinel.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/LoftekSentinel.pm @@ -18,7 +18,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/M8640.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/M8640.pm index 10bbea5de..bacfc77aa 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/M8640.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/M8640.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Ncs370.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Ncs370.pm index c69b4f29d..32ac9a50c 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Ncs370.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Ncs370.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm index 0e273b5b7..6aefbb6c3 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Netcat.pm @@ -16,7 +16,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/PanasonicIP.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/PanasonicIP.pm index d3e19e3aa..070f5fb18 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/PanasonicIP.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/PanasonicIP.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/PelcoD.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/PelcoD.pm index 8e5d07f74..68547da6f 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/PelcoD.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/PelcoD.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/PelcoP.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/PelcoP.pm index 794393644..9c411c0cd 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/PelcoP.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/PelcoP.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm index 77d2da751..38838237b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm @@ -1,26 +1,33 @@ +# ========================================================================== +# +# ZoneMinder SunEyes SP-P1802SWPTZ IP Control Protocol Module, $Date: 2017-03-19 23:00:00 +1000 (Sat, 19 March 2017) $, $Revision: 0002 $ +# Copyright (C) 2001-2008 Philip Coombes +# Modified for use with Foscam FI8918W IP Camera by Dave Harris +# Modified Feb 2011 by Howard Durdle (http://durdl.es/x) to: +# fix horizontal panning, add presets and IR on/off +# use Control Device field to pass username and password +# Modified May 2014 by Arun Horne (http://arunhorne.co.uk) to: +# use HTTP basic auth as required by firmware 11.37.x.x upward # Modified on Sep 28 2015 by Bobby Billingsley # Changes made # - Copied FI8918W.pm to SPP1802SWPTZ.pm # - modified to control a SunEyes SP-P1802SWPTZ - -# ========================================================================== -# ZoneMinder SunEyes SP-P1802SWPTZ IP Control Protocol Module +# Modified on 13 March 2017 by Steve Gilvarry +# -Address license and copyright issues # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. # -# This program 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 General Public License for more -# details. +# This program 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 General Public License for more details. # -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. -# -# ========================================================================== +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # This module contains the implementation of the SunEyes SP-P1802SWPTZ IP # camera control protocol diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm index f3fc94754..b3f9418ce 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -45,265 +45,239 @@ use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); -sub new -{ - my $class = shift; - my $id = shift; - my $self = ZoneMinder::Control->new( $id ); - bless( $self, $class ); - srand( time() ); - return $self; +sub new { + my $class = shift; + my $id = shift; + my $self = ZoneMinder::Control->new( $id ); + bless( $self, $class ); + srand( time() ); + return $self; } our $AUTOLOAD; -sub AUTOLOAD -{ - my $self = shift; - my $class = ref($self) || croak( "$self not object" ); - my $name = $AUTOLOAD; - $name =~ s/.*://; - if ( exists($self->{$name}) ) - { - return( $self->{$name} ); - } - Fatal( "Can't access $name member of object of class $class" ); +sub AUTOLOAD { + my $self = shift; + my $class = ref($self) || croak( "$self not object" ); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( exists($self->{$name}) ) { + return( $self->{$name} ); + } + Fatal( "Can't access $name member of object of class $class" ); } -sub open -{ - my $self = shift; +sub open { + my $self = shift; - $self->loadMonitor(); + $self->loadMonitor(); - use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); - $self->{state} = 'open'; + $self->{state} = 'open'; } -sub close -{ - my $self = shift; - $self->{state} = 'closed'; +sub close { + my $self = shift; + $self->{state} = 'closed'; } -sub printMsg -{ - my $self = shift; - my $msg = shift; - my $msg_len = length($msg); +sub printMsg { + my $self = shift; + my $msg = shift; + my $msg_len = length($msg); - Debug( $msg."[".$msg_len."]" ); + Debug( $msg."[".$msg_len."]" ); } -sub sendCmd -{ - my $self = shift; - my $cmd = shift; +sub sendCmd { + my $self = shift; + my $cmd = shift; - my $result = undef; + my $result = undef; - printMsg( $cmd, "Tx" ); + printMsg( $cmd, "Tx" ); - my $url; - if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) { - $url = $self->{Monitor}->{ControlAddress}.$cmd; - } else { - $url = 'http://'.$self->{Monitor}->{ControlAddress}.$cmd; - } # en dif - my $req = HTTP::Request->new( GET=>$url ); + my $url; + if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) { + $url = $self->{Monitor}->{ControlAddress}.$cmd; + } else { + $url = 'http://'.$self->{Monitor}->{ControlAddress}.$cmd; + } # en dif + my $req = HTTP::Request->new( GET=>$url ); - my $res = $self->{ua}->request($req); + my $res = $self->{ua}->request($req); - if ( $res->is_success ) - { - $result = !undef; - } - else - { - Error( "Error check failed: '".$res->status_line()."'" ); - } + if ( $res->is_success ) { + $result = !undef; + } else { + Error( "Error check failed: '".$res->status_line()."'" ); + } - return( $result ); + return( $result ); } -sub reset -{ - my $self = shift; - Debug( "Camera Reset" ); - my $cmd = "/admin/ptctl.cgi?move=reset"; - $self->sendCmd( $cmd ); +sub reset { + my $self = shift; + Debug( "Camera Reset" ); + my $cmd = "/admin/ptctl.cgi?move=reset"; + $self->sendCmd( $cmd ); } -sub moveMap -{ - my $self = shift; - my $params = shift; - my $xcoord = $self->getParam( $params, 'xcoord' ); - my $ycoord = $self->getParam( $params, 'ycoord' ); +sub moveMap { + my $self = shift; + my $params = shift; + my $xcoord = $self->getParam( $params, 'xcoord' ); + my $ycoord = $self->getParam( $params, 'ycoord' ); - my $hor = $xcoord * 100 / $self->{Monitor}->{Width}; - my $ver = $ycoord * 100 / $self->{Monitor}->{Height}; + my $hor = $xcoord * 100 / $self->{Monitor}->{Width}; + my $ver = $ycoord * 100 / $self->{Monitor}->{Height}; - my $maxver = 8; - my $maxhor = 30; - - my $horDir = "right"; - my $verDir = "up"; - my $horSteps = 0; - my $verSteps = 0; + my $maxver = 8; + my $maxhor = 30; - # Horizontal movement - if ($hor < 50) { - # left - $horSteps = ((50 - $hor) / 50) * $maxhor; - $horDir = "left"; - } - elsif ($hor > 50) { - # right - $horSteps = (($hor - 50) / 50) * $maxhor; - $horDir = "right"; - } - - # Vertical movement - if ($ver < 50) { - # up - $verSteps = ((50 - $ver) / 50) * $maxver; - $verDir = "up"; - } - elsif ($ver > 50) { - # down - $verSteps = (($ver - 50) / 50) * $maxver; - $verDir = "down"; - } + my $horDir = "right"; + my $verDir = "up"; + my $horSteps = 0; + my $verSteps = 0; - my $v = int($verSteps); - my $h = int($horSteps); +# Horizontal movement + if ( $hor < 50 ) { +# left + $horSteps = ((50 - $hor) / 50) * $maxhor; + $horDir = "left"; + } + elsif ( $hor > 50 ) { +# right + $horSteps = (($hor - 50) / 50) * $maxhor; + $horDir = "right"; + } - Debug( "Move Map to $xcoord,$ycoord, hor=$h $horDir, ver=$v $verDir"); - my $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$horDir&Degree=$h"; - $self->sendCmd( $cmd ); - $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$verDir&Degree=$v"; - $self->sendCmd( $cmd ); +# Vertical movement + if ( $ver < 50 ) { +# up + $verSteps = ((50 - $ver) / 50) * $maxver; + $verDir = "up"; + } + elsif ( $ver > 50 ) { +# down + $verSteps = (($ver - 50) / 50) * $maxver; + $verDir = "down"; + } + + my $v = int($verSteps); + my $h = int($horSteps); + + Debug( "Move Map to $xcoord,$ycoord, hor=$h $horDir, ver=$v $verDir"); + my $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$horDir&Degree=$h"; + $self->sendCmd( $cmd ); + $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$verDir&Degree=$v"; + $self->sendCmd( $cmd ); } -sub moveRelUp -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Up $step" ); - my $cmd = "/admin/ptctl.cgi?move=up"; - $self->sendCmd( $cmd ); +sub moveRelUp { + my $self = shift; + my $params = shift; + my $step = $self->getParam( $params, 'tiltstep' ); + Debug( "Step Up $step" ); + my $cmd = "/admin/ptctl.cgi?move=up"; + $self->sendCmd( $cmd ); } -sub moveRelDown -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Down $step" ); - my $cmd = "/admin/ptctl.cgi?move=down"; - $self->sendCmd( $cmd ); +sub moveRelDown { + my $self = shift; + my $params = shift; + my $step = $self->getParam( $params, 'tiltstep' ); + Debug( "Step Down $step" ); + my $cmd = "/admin/ptctl.cgi?move=down"; + $self->sendCmd( $cmd ); } -sub moveRelLeft -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'panstep' ); - Debug( "Step Left $step" ); - my $cmd = "/admin/ptctl.cgi?move=left"; - $self->sendCmd( $cmd ); +sub moveRelLeft { + my $self = shift; + my $params = shift; + my $step = $self->getParam( $params, 'panstep' ); + + if ( $self->{Monitor}->{Orientation} eq "hori" ) { + Debug( "Stepping Right because flipped horizontally " ); + $self->sendCmd( "/admin/ptctl.cgi?move=right" ); + } else { + Debug( "Step Left" ); + $self->sendCmd( "/admin/ptctl.cgi?move=left" ); + } } -sub moveRelRight -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'panstep' ); - Debug( "Step Right $step" ); - my $cmd = "/admin/ptctl.cgi?move=right"; - $self->sendCmd( $cmd ); +sub moveRelRight { + my $self = shift; + my $params = shift; + my $step = $self->getParam( $params, 'panstep' ); + if ( $self->{Monitor}->{Orientation} eq "hori" ) { + Debug( "Stepping Left because flipped horizontally " ); + $self->sendCmd( "/admin/ptctl.cgi?move=left" ); + } else { + Debug( "Step Right" ); + $self->sendCmd( "/admin/ptctl.cgi?move=right" ); + } } -sub presetClear -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Clear Preset $preset" ); - #my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; - #$self->sendCmd( $cmd ); +sub presetClear { + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Clear Preset $preset" ); +#my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; +#$self->sendCmd( $cmd ); } -sub presetSet -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Set Preset $preset" ); - my $cmd = "/admin/ptctl.cgi?position=" . ($preset - 1) . "&positionname=zm$preset"; - $self->sendCmd( $cmd ); +sub presetSet { + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Set Preset $preset" ); + my $cmd = "/admin/ptctl.cgi?position=" . ($preset - 1) . "&positionname=zm$preset"; + $self->sendCmd( $cmd ); } -sub presetGoto -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Goto Preset $preset" ); - my $cmd = "/admin/ptctl.cgi?move=p" . ($preset - 1); - $self->sendCmd( $cmd ); +sub presetGoto { + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Goto Preset $preset" ); + my $cmd = "/admin/ptctl.cgi?move=p" . ($preset - 1); + $self->sendCmd( $cmd ); } -sub presetHome -{ - my $self = shift; - Debug( "Home Preset" ); - my $cmd = "/admin/ptctl.cgi?move=h"; - $self->sendCmd( $cmd ); +sub presetHome { + my $self = shift; + Debug( "Home Preset" ); + my $cmd = "/admin/ptctl.cgi?move=h"; + $self->sendCmd( $cmd ); } 1; __END__ -# Below is stub documentation for your module. You'd better edit it! =head1 NAME -ZoneMinder::Database - Perl extension for blah blah blah +ZoneMinder::Control::SkyIPCam7xx.pm - Module for controlling AirLink101 SkyIPams =head1 SYNOPSIS - use ZoneMinder::Database; - blah blah blah +use ZoneMinder::Control::SkyIPCam7xx; =head1 DESCRIPTION -Stub documentation for ZoneMinder, created by h2xs. It looks like the -author of the extension was negligent enough to leave the stub -unedited. - -Blah blah blah. +Module for controlling AirLink101 Cameras. =head2 EXPORT None by default. - - =head1 SEE ALSO -Mention other useful documentation such as the documentation of -related modules or operating system documentation (such as man pages -in UNIX), or any relevant external documentation such as RFCs or -standards. - -If you have a mailing list set up for your module, mention it here. - -If you have a web site set up for your module, mention it here. +ZoneMinder::Control =head1 AUTHOR @@ -318,5 +292,4 @@ This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. - =cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP862.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP862.pm index d7c669583..eb083eb60 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP862.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP862.pm @@ -13,7 +13,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Toshiba_IK_WB11A.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Toshiba_IK_WB11A.pm index e96b30c85..562365e95 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Toshiba_IK_WB11A.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Toshiba_IK_WB11A.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Visca.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Visca.pm index 749beae21..e356c77f0 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Visca.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Visca.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm index 3649b35bc..7ddf2dc6d 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Wanscam.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Wanscam.pm index f419aacfc..894fe970d 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Wanscam.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Wanscam.pm @@ -31,7 +31,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/WanscamHW0025.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/WanscamHW0025.pm old mode 100755 new mode 100644 index 3dd9c2cc5..3459c097f --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/WanscamHW0025.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/WanscamHW0025.pm @@ -19,7 +19,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/mjpgStreamer.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/mjpgStreamer.pm index 18a73cae8..7ac00c995 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/mjpgStreamer.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/mjpgStreamer.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm index 76b161582..8214b9f5d 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/onvif.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index 7dfe0654b..19374543e 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -42,13 +42,13 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( 'functions' => [ qw( - zmDbConnect - zmDbDisconnect - zmDbGetMonitors - zmDbGetMonitor - zmDbGetMonitorAndControl - ) ] -); + zmDbConnect + zmDbDisconnect + zmDbGetMonitors + zmDbGetMonitor + zmDbGetMonitorAndControl + ) ] + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); @@ -70,46 +70,41 @@ use Carp; our $dbh = undef; -sub zmDbConnect -{ - my $force = shift; - if ( $force ) - { - zmDbDisconnect(); - } - if ( !defined( $dbh ) ) - { - my ( $host, $port ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); +sub zmDbConnect { + my $force = shift; + if ( $force ) { + zmDbDisconnect(); + } + my $options = shift; - if ( defined($port) ) - { - $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .";host=".$host - .";port=".$port - , $Config{ZM_DB_USER} - , $Config{ZM_DB_PASS} - ); - } - else - { - $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .";host=".$Config{ZM_DB_HOST} - , $Config{ZM_DB_USER} - , $Config{ZM_DB_PASS} - ); - } - $dbh->trace( 0 ); + if ( ( ! defined( $dbh ) ) or ! $dbh->ping() ) { + my ( $host, $portOrSocket ) = ( $ZoneMinder::Config::Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); + my $socket; + + if ( defined($portOrSocket) ) { + if ( $portOrSocket =~ /^\// ) { + $socket = ";mysql_socket=".$portOrSocket; + } else { + $socket = ";host=".$host.";port=".$portOrSocket; + } + } else { + $socket = ";host=".$Config{ZM_DB_HOST}; } - return( $dbh ); + $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} + .$socket . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '' ) + , $Config{ZM_DB_USER} + , $Config{ZM_DB_PASS} + ); + $dbh->trace( 0 ); + } + return( $dbh ); } -sub zmDbDisconnect -{ - if ( defined( $dbh ) ) - { - $dbh->disconnect(); - $dbh = undef; - } +sub zmDbDisconnect { + if ( defined( $dbh ) ) { + $dbh->disconnect(); + $dbh = undef; + } } use constant DB_MON_ALL => 0; # All monitors @@ -119,88 +114,74 @@ use constant DB_MON_MOTION => 3; # All monitors that are doing motion detection use constant DB_MON_RECORD => 4; # All monitors that are doing unconditional recording use constant DB_MON_PASSIVE => 5; # All monitors that are in nodect state -sub zmDbGetMonitors -{ - zmDbConnect(); +sub zmDbGetMonitors { + zmDbConnect(); - my $function = shift || DB_MON_ALL; - my $sql = "select * from Monitors"; + my $function = shift || DB_MON_ALL; + my $sql = "select * from Monitors"; - if ( $function ) - { - if ( $function == DB_MON_CAPT ) - { - $sql .= " where Function >= 'Monitor'"; - } - elsif ( $function == DB_MON_ACTIVE ) - { - $sql .= " where Function > 'Monitor'"; - } - elsif ( $function == DB_MON_MOTION ) - { - $sql .= " where Function = 'Modect' or Function = 'Mocord'"; - } - elsif ( $function == DB_MON_RECORD ) - { - $sql .= " where Function = 'Record' or Function = 'Mocord'"; - } - elsif ( $function == DB_MON_PASSIVE ) - { - $sql .= " where Function = 'Nodect'"; - } + if ( $function ) { + if ( $function == DB_MON_CAPT ) { + $sql .= " where Function >= 'Monitor'"; + } elsif ( $function == DB_MON_ACTIVE ) { + $sql .= " where Function > 'Monitor'"; + } elsif ( $function == DB_MON_MOTION ) { + $sql .= " where Function = 'Modect' or Function = 'Mocord'"; + } elsif ( $function == DB_MON_RECORD ) { + $sql .= " where Function = 'Record' or Function = 'Mocord'"; + } elsif ( $function == DB_MON_PASSIVE ) { + $sql .= " where Function = 'Nodect'"; } - my $sth = $dbh->prepare_cached( $sql ) - or croak( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() - or croak( "Can't execute '$sql': ".$sth->errstr() ); + } + my $sth = $dbh->prepare_cached( $sql ) + or croak( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() + or croak( "Can't execute '$sql': ".$sth->errstr() ); - my @monitors; - while( my $monitor = $sth->fetchrow_hashref() ) - { - push( @monitors, $monitor ); - } - $sth->finish(); - return( \@monitors ); + my @monitors; + while( my $monitor = $sth->fetchrow_hashref() ) { + push( @monitors, $monitor ); + } + $sth->finish(); + return( \@monitors ); } -sub zmDbGetMonitor -{ - zmDbConnect(); +sub zmDbGetMonitor { + zmDbConnect(); - my $id = shift; + my $id = shift; - return( undef ) if ( !defined($id) ); + return( undef ) if ( !defined($id) ); - my $sql = "select * from Monitors where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or croak( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $id ) - or croak( "Can't execute '$sql': ".$sth->errstr() ); - my $monitor = $sth->fetchrow_hashref(); + my $sql = "select * from Monitors where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) + or croak( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $id ) + or croak( "Can't execute '$sql': ".$sth->errstr() ); + my $monitor = $sth->fetchrow_hashref(); - return( $monitor ); + return( $monitor ); } -sub zmDbGetMonitorAndControl -{ - zmDbConnect(); +sub zmDbGetMonitorAndControl { + zmDbConnect(); - my $id = shift; + my $id = shift; - return( undef ) if ( !defined($id) ); + return( undef ) if ( !defined($id) ); - my $sql = "SELECT C.*,M.*,C.Protocol - FROM Monitors as M - INNER JOIN Controls as C on (M.ControlId = C.Id) - WHERE M.Id = ?" + my $sql = "SELECT C.*,M.*,C.Protocol + FROM Monitors as M + INNER JOIN Controls as C on (M.ControlId = C.Id) + WHERE M.Id = ?" ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $id ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - my $monitor = $sth->fetchrow_hashref(); + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $id ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $monitor = $sth->fetchrow_hashref(); - return( $monitor ); + return( $monitor ); } 1; @@ -213,8 +194,8 @@ ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS - use ZoneMinder::Database; - blah blah blah +use ZoneMinder::Database; +blah blah blah =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 67c117460..d616d893f 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -28,30 +28,12 @@ use 5.006; use strict; use warnings; -require Exporter; require ZoneMinder::Base; +require ZoneMinder::Object; require Date::Manip; -our @ISA = qw(Exporter ZoneMinder::Base); - -# Items to export into callers namespace by default. Note: do not export -# names by default without a very good reason. Use EXPORT_OK instead. -# Do not simply export all your public functions/methods/constants. - -# This allows declaration use ZoneMinder ':all'; -# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK -# will save memory. -our %EXPORT_TAGS = ( - 'functions' => [ qw( - ) ] -); -push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; - -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); - -our @EXPORT = qw(); - -our $VERSION = $ZoneMinder::Base::VERSION; +#our @ISA = qw(ZoneMinder::Object); +use parent qw(ZoneMinder::Object); # ========================================================================== # @@ -62,265 +44,333 @@ our $VERSION = $ZoneMinder::Base::VERSION; use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); +require Date::Parse; + +use vars qw/ $table $primary_key /; +$table = 'Events'; +$primary_key = 'Id'; use POSIX; -sub new { - my ( $parent, $id, $data ) = @_; +sub Time { + if ( @_ > 1 ) { + $_[0]{Time} = $_[1]; + } + if ( ! defined $_[0]{Time} ) { - my $self = {}; - bless $self, $parent; - $$self{dbh} = $ZoneMinder::Database::dbh; -#zmDbConnect(); - if ( ( $$self{Id} = $id ) or $data ) { -#$log->debug("loading $parent $id") if $debug or DEBUG_ALL; - $self->load( $data ); - } - return $self; -} # end sub new - -sub load { - my ( $self, $data ) = @_; - my $type = ref $self; - if ( ! $data ) { -#$log->debug("Object::load Loading from db $type"); - $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Events WHERE Id=?', {}, $$self{Id} ); - if ( ! $data ) { - Error( "Failure to load Event record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); - } else { - Debug( 3, "Loaded Event $$self{Id}" ); - } # end if - } # end if ! $data - if ( $data and %$data ) { - @$self{keys %$data} = values %$data; - } # end if -} # end sub load + $_[0]{Time} = Date::Parse::str2time( $_[0]{StartTime} ); + } + return $_[0]{Time}; +} sub Name { - if ( @_ > 1 ) { - $_[0]{Name} = $_[1]; - } - return $_[0]{Name}; + if ( @_ > 1 ) { + $_[0]{Name} = $_[1]; + } + return $_[0]{Name}; } # end sub Path sub find { - shift if $_[0] eq 'ZoneMinder::Event'; - my %sql_filters = @_; + shift if $_[0] eq 'ZoneMinder::Event'; + my %sql_filters = @_; - my $sql = 'SELECT * FROM Events'; - my @sql_filters; - my @sql_values; + my $sql = 'SELECT * FROM Events'; + my @sql_filters; + my @sql_values; - if ( exists $sql_filters{Name} ) { - push @sql_filters , ' Name = ? '; - push @sql_values, $sql_filters{Name}; - } + if ( exists $sql_filters{Name} ) { + push @sql_filters , ' Name = ? '; + push @sql_values, $sql_filters{Name}; + } - $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; - $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; + $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; + $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; - my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); - my $res = $sth->execute( @sql_values ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( @sql_values ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - my @results; + my @results; - while( my $db_filter = $sth->fetchrow_hashref() ) { - my $filter = new ZoneMinder::Event( $$db_filter{Id}, $db_filter ); - push @results, $filter; - } # end while - return @results; + while( my $db_filter = $sth->fetchrow_hashref() ) { + my $filter = new ZoneMinder::Event( $$db_filter{Id}, $db_filter ); + push @results, $filter; + } # end while + $sth->finish(); + return @results; } sub find_one { - my @results = find(@_); - return $results[0] if @results; + my @results = find(@_); + return $results[0] if @results; } -sub getEventPath -{ - my $event = shift; +sub getPath { + return Path( @_ ); +} +sub Path { + my $event = shift; - my $event_path = ""; - if ( $Config{ZM_USE_DEEP_STORAGE} ) - { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.strftime( "%y/%m/%d/%H/%M/%S", - localtime($event->{Time}) - ) - ; - } - else - { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.$event->{Id} - ; + if ( @_ > 1 ) { + $$event{Path} = $_[1]; + if ( ! -e $$event{Path} ) { + Error("Setting path for event $$event{Id} to $_[1] but does not exist!"); } + } - if ( index($Config{ZM_DIR_EVENTS},'/') != 0 ){ - $event_path = $Config{ZM_PATH_WEB} - .'/'.$event_path - ; + if ( ! $$event{Path} ) { + my $path = ($Config{ZM_DIR_EVENTS}=~/^\//) ? $Config{ZM_DIR_EVENTS} : $Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}; + + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + if ( $event->Time() ) { + $$event{Path} = join('/', + $path, + $event->{MonitorId}, + strftime( "%y/%m/%d/%H/%M/%S", + localtime($event->Time()) + ), + ); + } else { + Error("Event $$event{Id} has no value for Time(), unable to determine path"); + $$event{Path} = ''; + } + } else { + $$event{Path} = join('/', + $path, + $event->{MonitorId}, + $event->{Id}, + ); } - return( $event_path ); + } # end if + + return $$event{Path}; } sub GenerateVideo { - my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_; + my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_; - my $event_path = getEventPath( $self ); - chdir( $event_path ); - ( my $video_name = $self->{Name} ) =~ s/\s/_/g; + my $event_path = $self->getPath( ); + chdir( $event_path ); + ( my $video_name = $self->{Name} ) =~ s/\s/_/g; - my @file_parts; - if ( $rate ) - { - my $file_rate = $rate; - $file_rate =~ s/\./_/; - $file_rate =~ s/_00//; - $file_rate =~ s/(_\d+)0+$/$1/; - $file_rate = 'r'.$file_rate; - push( @file_parts, $file_rate ); - } - elsif ( $fps ) - { - my $file_fps = $fps; - $file_fps =~ s/\./_/; - $file_fps =~ s/_00//; - $file_fps =~ s/(_\d+)0+$/$1/; - $file_fps = 'R'.$file_fps; - push( @file_parts, $file_fps ); - } + my @file_parts; + if ( $rate ) { + my $file_rate = $rate; + $file_rate =~ s/\./_/; + $file_rate =~ s/_00//; + $file_rate =~ s/(_\d+)0+$/$1/; + $file_rate = 'r'.$file_rate; + push( @file_parts, $file_rate ); + } elsif ( $fps ) { + my $file_fps = $fps; + $file_fps =~ s/\./_/; + $file_fps =~ s/_00//; + $file_fps =~ s/(_\d+)0+$/$1/; + $file_fps = 'R'.$file_fps; + push( @file_parts, $file_fps ); + } - if ( $scale ) - { - my $file_scale = $scale; - $file_scale =~ s/\./_/; - $file_scale =~ s/_00//; - $file_scale =~ s/(_\d+)0+$/$1/; - $file_scale = 's'.$file_scale; - push( @file_parts, $file_scale ); - } - elsif ( $size ) - { - my $file_size = 'S'.$size; - push( @file_parts, $file_size ); - } - my $video_file = "$video_name-".$file_parts[0]."-".$file_parts[1].".$format"; - if ( $overwrite || !-s $video_file ) - { - Info( "Creating video file $video_file for event $self->{Id}\n" ); + if ( $scale ) { + my $file_scale = $scale; + $file_scale =~ s/\./_/; + $file_scale =~ s/_00//; + $file_scale =~ s/(_\d+)0+$/$1/; + $file_scale = 's'.$file_scale; + push( @file_parts, $file_scale ); + } elsif ( $size ) { + my $file_size = 'S'.$size; + push( @file_parts, $file_size ); + } + my $video_file = "$video_name-".$file_parts[0]."-".$file_parts[1].".$format"; + if ( $overwrite || !-s $video_file ) { + Info( "Creating video file $video_file for event $self->{Id}\n" ); - my $frame_rate = sprintf( "%.2f", $self->{Frames}/$self->{FullLength} ); - if ( $rate ) - { - if ( $rate != 1.0 ) - { - $frame_rate *= $rate; - } - } - elsif ( $fps ) - { - $frame_rate = $fps; - } + my $frame_rate = sprintf( "%.2f", $self->{Frames}/$self->{FullLength} ); + if ( $rate ) { + if ( $rate != 1.0 ) { + $frame_rate *= $rate; + } + } elsif ( $fps ) { + $frame_rate = $fps; + } - my $width = $self->{MonitorWidth}; - my $height = $self->{MonitorHeight}; - my $video_size = " ${width}x${height}"; + my $width = $self->{MonitorWidth}; + my $height = $self->{MonitorHeight}; + my $video_size = " ${width}x${height}"; - if ( $scale ) - { - if ( $scale != 1.0 ) - { - $width = int($width*$scale); - $height = int($height*$scale); - $video_size = " ${width}x${height}"; - } - } - elsif ( $size ) - { - $video_size = $size; - } - my $command = $Config{ZM_PATH_FFMPEG} - ." -y -r $frame_rate " - .$Config{ZM_FFMPEG_INPUT_OPTIONS} - ." -i %0" - .$Config{ZM_EVENT_IMAGE_DIGITS} - ."d-capture.jpg -s $video_size " + if ( $scale ) { + if ( $scale != 1.0 ) { + $width = int($width*$scale); + $height = int($height*$scale); + $video_size = " ${width}x${height}"; + } + } elsif ( $size ) { + $video_size = $size; + } + my $command = $Config{ZM_PATH_FFMPEG} + ." -y -r $frame_rate " + .$Config{ZM_FFMPEG_INPUT_OPTIONS} + .' -i ' . ( $$self{DefaultVideo} ? $$self{DefaultVideo} : '%0'.$Config{ZM_EVENT_IMAGE_DIGITS} .'d-capture.jpg' ) #. " -f concat -i /tmp/event_files.txt" - ." -s $video_size " - .$Config{ZM_FFMPEG_OUTPUT_OPTIONS} - ." '$video_file' > ffmpeg.log 2>&1" - ; - Debug( $command."\n" ); - my $output = qx($command); + ." -s $video_size " + .$Config{ZM_FFMPEG_OUTPUT_OPTIONS} + ." '$video_file' > ffmpeg.log 2>&1" + ; + Debug( $command."\n" ); + my $output = qx($command); - my $status = $? >> 8; - if ( $status ) - { - Error( "Unable to generate video, check " - .$event_path."/ffmpeg.log for details" - ); - return; - } + my $status = $? >> 8; + if ( $status ) { + Error( "Unable to generate video, check " + .$event_path."/ffmpeg.log for details" + ); + return; + } - Info( "Finished $video_file\n" ); - return $event_path.'/'.$video_file; - } else { - Info( "Video file $video_file already exists for event $self->{Id}\n" ); - return $event_path.'/'.$video_file; - } - return; + Info( "Finished $video_file\n" ); + return $event_path.'/'.$video_file; + } else { + Info( "Video file $video_file already exists for event $self->{Id}\n" ); + return $event_path.'/'.$video_file; + } + return; } # end sub GenerateVideo +sub delete { + my $event = $_[0]; + Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId} $event->{StartTime}\n" ); + $ZoneMinder::Database::dbh->ping(); +# Do it individually to avoid locking up the table for new events + my $sql = 'delete from Events where Id = ?'; + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $sth->finish(); + + if ( ! $Config{ZM_OPT_FAST_DELETE} ) { + my $sql = 'delete from Frames where EventId = ?'; + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $sth->finish(); + + $sql = 'delete from Stats where EventId = ?'; + $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $sth->finish(); + + $event->delete_files( ); + } else { + Debug('Not deleting frames, stats and files for speed.'); + } +} # end sub delete + + +sub delete_files { + + my $storage_path = ($Config{ZM_DIR_EVENTS}=~/^\//) ? $Config{ZM_DIR_EVENTS} : $Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}; + + if ( ! $storage_path ) { + Fatal("Empty path when deleting files for event $_[0]{Id} "); + return; + } + + chdir ( $storage_path ); + + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + if ( ! $_[0]{MonitorId} ) { + Error("No monitor id assigned to event $_[0]{Id}"); + return; + } + Debug("Deleting files for Event $_[0]{Id} from $storage_path."); + my $link_path = $_[0]{MonitorId}.'/*/*/*/.'.$_[0]{Id}; +#Debug( "LP1:$link_path" ); + my @links = glob($link_path); +#Debug( "L:".$links[0].": $!" ); + if ( @links ) { + ( $link_path ) = ( $links[0] =~ /^(.*)$/ ); # De-taint +#Debug( "LP2:$link_path" ); + + ( my $day_path = $link_path ) =~ s/\.\d+//; +#Debug( "DP:$day_path" ); + my $event_path = $day_path.readlink( $link_path ); + ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint +#Debug( "EP:$event_path" ); + my $command = "/bin/rm -rf $event_path"; +#Debug( "C:$command" ); + ZoneMinder::General::executeShellCommand( $command ); + + unlink( $link_path ) or Error( "Unable to unlink '$link_path': $!" ); + + my @path_parts = split( /\//, $event_path ); + for ( my $i = int(@path_parts)-2; $i >= 1; $i-- ) { + my $delete_path = join( '/', @path_parts[0..$i] ); +#Debug( "DP$i:$delete_path" ); + my @has_files = glob( join('/', $storage_path,$delete_path,'*' ) ); +#Debug( "HF1:".$has_files[0] ) if ( @has_files ); + last if ( @has_files ); + @has_files = glob( join('/', $storage_path, $delete_path, '.[0-9]*' ) ); +#Debug( "HF2:".$has_files[0] ) if ( @has_files ); + last if ( @has_files ); + my $command = "/bin/rm -rf $storage_path/$delete_path"; + ZoneMinder::General::executeShellCommand( $command ); + } + } + } else { + my $command = "/bin/rm -rf $storage_path/$_[0]{MonitorId}/$_[0]{Id}"; + ZoneMinder::General::executeShellCommand( $command ); + } +} # end sub delete_files + +sub Storage { + return new ZoneMinder::Storage( $_[0]{StorageId} ); +} + +sub check_for_in_filesystem { + my $path = $_[0]->Path(); + if ( $path ) { + my @files = glob( $path . '/*' ); +Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found " . scalar @files . " files"); + return 1 if @files; + } +Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found no files"); + return 0; +} + +sub age { + if ( ! $_[0]{age} ) { + $_[0]{age} = (time() - ($^T - ((-M $_[0]->Path() ) * 24*60*60))); + } + return $_[0]{age}; +} + 1; __END__ -# Below is stub documentation for your module. You'd better edit it! =head1 NAME -ZoneMinder::Database - Perl extension for blah blah blah +ZoneMinder::Event - Perl Class for events =head1 SYNOPSIS - use ZoneMinder::Event; - blah blah blah +use ZoneMinder::Event; =head1 DESCRIPTION -Stub documentation for ZoneMinder, created by h2xs. It looks like the -author of the extension was negligent enough to leave the stub -unedited. - -Blah blah blah. - -=head2 EXPORT - -None by default. - - - -=head1 SEE ALSO - -Mention other useful documentation such as the documentation of -related modules or operating system documentation (such as man pages -in UNIX), or any relevant external documentation such as RFCs or -standards. - -If you have a mailing list set up for your module, mention it here. - -If you have a web site set up for your module, mention it here. +The Event class has everything you need to deal with events from Perl. =head1 AUTHOR -Philip Coombes, Ephilip.coombes@zoneminder.comE +Isaac Connor, Eisaac@zoneminder.comE =head1 COPYRIGHT AND LICENSE -Copyright (C) 2001-2008 Philip Coombes +Copyright (C) 2001-2017 ZoneMinder LLC This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index b7806ec4a..75d0e6ad4 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -28,31 +28,14 @@ use 5.006; use strict; use warnings; -require Exporter; require ZoneMinder::Base; require Date::Manip; -our @ISA = qw(Exporter ZoneMinder::Base); - -# Items to export into callers namespace by default. Note: do not export -# names by default without a very good reason. Use EXPORT_OK instead. -# Do not simply export all your public functions/methods/constants. - -# This allows declaration use ZoneMinder ':all'; -# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK -# will save memory. -our %EXPORT_TAGS = ( - 'functions' => [ qw( - ) ] -); -push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; - -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); - -our @EXPORT = qw(); - -our $VERSION = $ZoneMinder::Base::VERSION; +use parent qw(ZoneMinder::Object); +use vars qw/ $table $primary_key /; +$table = 'Filters'; +$primary_key = 'Id'; # ========================================================================== # # General Utility Functions @@ -62,414 +45,338 @@ our $VERSION = $ZoneMinder::Base::VERSION; use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); +require ZoneMinder::Server; use POSIX; -sub new { - my ( $parent, $id, $data ) = @_; - - my $self = {}; - bless $self, $parent; - $$self{dbh} = $ZoneMinder::Database::dbh; -#zmDbConnect(); - if ( ( $$self{Id} = $id ) or $data ) { -#$log->debug("loading $parent $id") if $debug or DEBUG_ALL; - $self->load( $data ); - } - return $self; -} # end sub new - -sub load { - my ( $self, $data ) = @_; - my $type = ref $self; - if ( ! $data ) { -#$log->debug("Object::load Loading from db $type"); - $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Filter WHERE Id=?', {}, $$self{Id} ); - if ( ! $data ) { - Error( "Failure to load Filter record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); - } else { - Debug( 3, "Loaded Filter $$self{Id}" ); - } # end if - } # end if ! $data - if ( $data and %$data ) { - @$self{keys %$data} = values %$data; - } # end if -} # end sub load - sub Name { - if ( @_ > 1 ) { - $_[0]{Name} = $_[1]; - } - return $_[0]{Name}; + if ( @_ > 1 ) { + $_[0]{Name} = $_[1]; + } + return $_[0]{Name}; } # end sub Path sub find { - shift if $_[0] eq 'ZoneMinder::Filter'; - my %sql_filters = @_; + shift if $_[0] eq 'ZoneMinder::Filter'; + my %sql_filters = @_; - my $sql = 'SELECT * FROM Filters'; - my @sql_filters; - my @sql_values; + my $sql = 'SELECT * FROM Filters'; + my @sql_filters; + my @sql_values; - if ( exists $sql_filters{Name} ) { - push @sql_filters , ' Name = ? '; - push @sql_values, $sql_filters{Name}; - } + if ( exists $sql_filters{Name} ) { + push @sql_filters , ' Name = ? '; + push @sql_values, $sql_filters{Name}; + } - $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; - $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; + $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; + $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; - my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); - my $res = $sth->execute( @sql_values ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( @sql_values ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - my @results; + my @results; - while( my $db_filter = $sth->fetchrow_hashref() ) { - my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); - push @results, $filter; - } # end while - return @results; + while( my $db_filter = $sth->fetchrow_hashref() ) { + my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); + push @results, $filter; + } # end while + $sth->finish(); + + return @results; } sub find_one { - my @results = find(@_); - return $results[0] if @results; + my @results = find(@_); + return $results[0] if @results; } sub Execute { - my $self = $_[0]; + my $self = $_[0]; + my $sql = $self->Sql(); - my $sql = $self->Sql(); + if ( $self->{HasDiskPercent} ) { + my $disk_percent = getDiskPercent(); + $sql =~ s/zmDiskPercent/$disk_percent/g; + } + if ( $self->{HasDiskBlocks} ) { + my $disk_blocks = getDiskBlocks(); + $sql =~ s/zmDiskBlocks/$disk_blocks/g; + } + if ( $self->{HasSystemLoad} ) { + my $load = getLoad(); + $sql =~ s/zmSystemLoad/$load/g; + } - if ( $self->{HasDiskPercent} ) - { - my $disk_percent = getDiskPercent(); - $sql =~ s/zmDiskPercent/$disk_percent/g; - } - if ( $self->{HasDiskBlocks} ) - { - my $disk_blocks = getDiskBlocks(); - $sql =~ s/zmDiskBlocks/$disk_blocks/g; - } - if ( $self->{HasSystemLoad} ) - { - my $load = getLoad(); - $sql =~ s/zmSystemLoad/$load/g; - } + Debug("Filter::Execute SQL ($sql)"); + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute(); + if ( !$res ) { + Error( "Can't execute filter '$sql', ignoring: ".$sth->errstr() ); + return; + } + my @results; - my $sth = $$self{dbh}->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$$self{dbh}->errstr() ); - my $res = $sth->execute(); - if ( !$res ) - { - Error( "Can't execute filter '$sql', ignoring: ".$sth->errstr() ); - return; - } - my @results; - - while( my $event = $sth->fetchrow_hashref() ) { - push @results, $event; - } - $sth->finish(); - return @results; + while( my $event = $sth->fetchrow_hashref() ) { + push @results, $event; + } + $sth->finish(); + Debug("Loaded " . @results . " events for filter $_[0]{Name} using query ($sql)"); + return @results; } sub Sql { - my $self = $_[0]; - if ( ! $$self{Sql} ) { - my $filter_expr = ZoneMinder::General::jsonDecode( $self->{Query} ); - my $sql = "SELECT E.Id, - E.MonitorId, - M.Name as MonitorName, - M.DefaultRate, - M.DefaultScale, - E.Name, - E.Cause, - E.Notes, - E.StartTime, - unix_timestamp(E.StartTime) as Time, - E.Length, - E.Frames, - E.AlarmFrames, - E.TotScore, - E.AvgScore, - E.MaxScore, - E.Archived, - E.Videoed, - E.Uploaded, - E.Emailed, - E.Messaged, - E.Executed - FROM Events as E - INNER JOIN Monitors as M on M.Id = E.MonitorId - "; - $self->{Sql} = ''; + my $self = $_[0]; + if ( ! $$self{Sql} ) { + my $filter_expr = ZoneMinder::General::jsonDecode( $self->{Query} ); + my $sql = "SELECT E.*, + unix_timestamp(E.StartTime) as Time, + M.Name as MonitorName, + M.DefaultRate, + M.DefaultScale + FROM Events as E + INNER JOIN Monitors as M on M.Id = E.MonitorId + "; + $self->{Sql} = ''; - if ( $filter_expr->{terms} ) { - for ( my $i = 0; $i < @{$filter_expr->{terms}}; $i++ ) { - if ( exists($filter_expr->{terms}[$i]->{cnj}) ) { - $self->{Sql} .= " ".$filter_expr->{terms}[$i]->{cnj}." "; - } - if ( exists($filter_expr->{terms}[$i]->{obr}) ) { - $self->{Sql} .= " ".str_repeat( "(", $filter_expr->{terms}[$i]->{obr} )." "; - } - my $value = $filter_expr->{terms}[$i]->{val}; - my @value_list; - if ( $filter_expr->{terms}[$i]->{attr} ) { - if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) { - my ( $temp_attr_name ) = $filter_expr->{terms}[$i]->{attr} =~ /^Monitor(.+)$/; - $self->{Sql} .= "M.".$temp_attr_name; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) { - $self->{Sql} .= "E.StartTime"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) { - $self->{Sql} .= "to_days( E.StartTime )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) { - $self->{Sql} .= "extract( hour_second from E.StartTime )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Weekday' ) { - $self->{Sql} .= "weekday( E.StartTime )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskPercent' ) { - $self->{Sql} .= "zmDiskPercent"; - $self->{HasDiskPercent} = !undef; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskBlocks' ) { - $self->{Sql} .= "zmDiskBlocks"; - $self->{HasDiskBlocks} = !undef; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'SystemLoad' ) { - $self->{Sql} .= "zmSystemLoad"; - $self->{HasSystemLoad} = !undef; - } else { - $self->{Sql} .= "E.".$filter_expr->{terms}[$i]->{attr}; - } + if ( $filter_expr->{terms} ) { + foreach my $term ( @{$filter_expr->{terms}} ) { - ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; - foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { - if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) { - $value = "'$temp_value'"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Name' - || $filter_expr->{terms}[$i]->{attr} eq 'Cause' - || $filter_expr->{terms}[$i]->{attr} eq 'Notes' - ) { - $value = "'$temp_value'"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) { - $value = DateTimeToSQL( $temp_value ); - if ( !$value ) { - Error( "Error parsing date/time '$temp_value', " - ."skipping filter '$self->{Name}'\n" ); - return; - } - $value = "'$value'"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) { - $value = DateTimeToSQL( $temp_value ); - if ( !$value ) { - Error( "Error parsing date/time '$temp_value', " - ."skipping filter '$self->{Name}'\n" ); - return; - } - $value = "to_days( '$value' )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) { - $value = DateTimeToSQL( $temp_value ); - if ( !$value ) { - Error( "Error parsing date/time '$temp_value', " - ."skipping filter '$self->{Name}'\n" ); - return; - } - $value = "extract( hour_second from '$value' )"; - } else { - $value = $temp_value; - } - push( @value_list, $value ); - } # end foreach temp_value - } # end if has an attr - if ( $filter_expr->{terms}[$i]->{op} ) { - if ( $filter_expr->{terms}[$i]->{op} eq '=~' ) { - $self->{Sql} .= " regexp $value"; - } elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) { - $self->{Sql} .= " not regexp $value"; - } elsif ( $filter_expr->{terms}[$i]->{op} eq '=[]' ) { - $self->{Sql} .= " in (".join( ",", @value_list ).")"; - } elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) { - $self->{Sql} .= " not in (".join( ",", @value_list ).")"; - } else { - $self->{Sql} .= " ".$filter_expr->{terms}[$i]->{op}." $value"; - } - } # end if has an operator - if ( exists($filter_expr->{terms}[$i]->{cbr}) ) { - $self->{Sql} .= " ".str_repeat( ")", $filter_expr->{terms}[$i]->{cbr} )." "; - } - } # end foreach term - } # end if terms + if ( exists($term->{cnj}) ) { + $self->{Sql} .= " ".$term->{cnj}." "; + } + if ( exists($term->{obr}) ) { + $self->{Sql} .= " ".str_repeat( "(", $term->{obr} )." "; + } + my $value = $term->{val}; + my @value_list; + if ( $term->{attr} ) { + if ( $term->{attr} =~ /^Monitor/ ) { + my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/; + $self->{Sql} .= "M.".$temp_attr_name; + } elsif ( $term->{attr} =~ /^Server/ ) { + $self->{Sql} .= "M.".$term->{attr}; + } elsif ( $term->{attr} eq 'DateTime' ) { + $self->{Sql} .= "E.StartTime"; + } elsif ( $term->{attr} eq 'Date' ) { + $self->{Sql} .= "to_days( E.StartTime )"; + } elsif ( $term->{attr} eq 'Time' ) { + $self->{Sql} .= "extract( hour_second from E.StartTime )"; + } elsif ( $term->{attr} eq 'Weekday' ) { + $self->{Sql} .= "weekday( E.StartTime )"; + } elsif ( $term->{attr} eq 'DiskPercent' ) { + $self->{Sql} .= "zmDiskPercent"; + $self->{HasDiskPercent} = !undef; + } elsif ( $term->{attr} eq 'DiskBlocks' ) { + $self->{Sql} .= "zmDiskBlocks"; + $self->{HasDiskBlocks} = !undef; + } elsif ( $term->{attr} eq 'SystemLoad' ) { + $self->{Sql} .= "zmSystemLoad"; + $self->{HasSystemLoad} = !undef; + } else { + $self->{Sql} .= "E.".$term->{attr}; + } - if ( $self->{Sql} ) - { - if ( $self->{AutoMessage} ) - { - # Include all events, including events that are still ongoing - # and have no EndTime yet - $sql .= " and ( ".$self->{Sql}." )"; - } - else - { - # Only include closed events (events with valid EndTime) - $sql .= " where not isnull(E.EndTime) and ( ".$self->{Sql}." )"; + ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; + foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { + if ( $term->{attr} =~ /^Monitor/ ) { + $value = "'$temp_value'"; + } elsif ( $term->{attr} eq 'ServerId' ) { + if ( $temp_value eq 'ZM_SERVER_ID' ) { + $value = "'$Config{ZM_SERVER_ID}'"; + # This gets used later, I forget for what + $$self{Server} = new ZoneMinder::Server( $Config{ZM_SERVER_ID} ); + } else { + $value = "'$temp_value'"; + # This gets used later, I forget for what + $$self{Server} = new ZoneMinder::Server( $temp_value ); + } + } elsif ( $term->{attr} eq 'Name' + || $term->{attr} eq 'Cause' + || $term->{attr} eq 'Notes' + ) { + $value = "'$temp_value'"; + } elsif ( $term->{attr} eq 'DateTime' ) { + $value = DateTimeToSQL( $temp_value ); + if ( !$value ) { + Error( "Error parsing date/time '$temp_value', " + ."skipping filter '$self->{Name}'\n" ); + return; + } + $value = "'$value'"; + } elsif ( $term->{attr} eq 'Date' ) { + $value = DateTimeToSQL( $temp_value ); + if ( !$value ) { + Error( "Error parsing date/time '$temp_value', " + ."skipping filter '$self->{Name}'\n" ); + return; + } + $value = "to_days( '$value' )"; + } elsif ( $term->{attr} eq 'Time' ) { + $value = DateTimeToSQL( $temp_value ); + if ( !$value ) { + Error( "Error parsing date/time '$temp_value', " + ."skipping filter '$self->{Name}'\n" ); + return; + } + $value = "extract( hour_second from '$value' )"; + } else { + $value = $temp_value; } + push( @value_list, $value ); + } # end foreach temp_value + } # end if has an attr + if ( $term->{op} ) { + if ( $term->{op} eq '=~' ) { + $self->{Sql} .= " regexp $value"; + } elsif ( $term->{op} eq '!~' ) { + $self->{Sql} .= " not regexp $value"; + } elsif ( $term->{op} eq '=[]' ) { + $self->{Sql} .= " in (".join( ",", @value_list ).")"; + } elsif ( $term->{op} eq '!~' ) { + $self->{Sql} .= " not in (".join( ",", @value_list ).")"; + } else { + $self->{Sql} .= " ".$term->{op}." $value"; + } + } # end if has an operator + if ( exists($term->{cbr}) ) { + $self->{Sql} .= " ".str_repeat( ")", $term->{cbr} )." "; } - my @auto_terms; - if ( $self->{AutoArchive} ) - { - push( @auto_terms, "E.Archived = 0" ) - } - if ( $self->{AutoVideo} ) - { - push( @auto_terms, "E.Videoed = 0" ) - } - if ( $self->{AutoUpload} ) - { - push( @auto_terms, "E.Uploaded = 0" ) - } - if ( $self->{AutoEmail} ) - { - push( @auto_terms, "E.Emailed = 0" ) - } - if ( $self->{AutoMessage} ) - { - push( @auto_terms, "E.Messaged = 0" ) - } - if ( $self->{AutoExecute} ) - { - push( @auto_terms, "E.Executed = 0" ) - } - if ( @auto_terms ) - { - $sql .= " and ( ".join( " or ", @auto_terms )." )"; - } - if ( !$filter_expr->{sort_field} ) - { - $filter_expr->{sort_field} = 'StartTime'; - $filter_expr->{sort_asc} = 0; - } - my $sort_column = ''; - if ( $filter_expr->{sort_field} eq 'Id' ) - { - $sort_column = "E.Id"; - } - elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) - { - $sort_column = "M.Name"; - } - elsif ( $filter_expr->{sort_field} eq 'Name' ) - { - $sort_column = "E.Name"; - } - elsif ( $filter_expr->{sort_field} eq 'StartTime' ) - { - $sort_column = "E.StartTime"; - } - elsif ( $filter_expr->{sort_field} eq 'Secs' ) - { - $sort_column = "E.Length"; - } - elsif ( $filter_expr->{sort_field} eq 'Frames' ) - { - $sort_column = "E.Frames"; - } - elsif ( $filter_expr->{sort_field} eq 'AlarmFrames' ) - { - $sort_column = "E.AlarmFrames"; - } - elsif ( $filter_expr->{sort_field} eq 'TotScore' ) - { - $sort_column = "E.TotScore"; - } - elsif ( $filter_expr->{sort_field} eq 'AvgScore' ) - { - $sort_column = "E.AvgScore"; - } - elsif ( $filter_expr->{sort_field} eq 'MaxScore' ) - { - $sort_column = "E.MaxScore"; - } - else - { - $sort_column = "E.StartTime"; - } - my $sort_order = $filter_expr->{sort_asc}?"asc":"desc"; - $sql .= " order by ".$sort_column." ".$sort_order; - if ( $filter_expr->{limit} ) - { - $sql .= " limit 0,".$filter_expr->{limit}; - } - Debug( "SQL:$sql\n" ); - $self->{Sql} = $sql; - } # end if has Sql - return $self->{Sql}; + } # end foreach term + } # end if terms + + if ( $self->{Sql} ) { + if ( $self->{AutoMessage} ) { +# Include all events, including events that are still ongoing +# and have no EndTime yet + $sql .= " and ( ".$self->{Sql}." )"; + } else { +# Only include closed events (events with valid EndTime) + $sql .= " where not isnull(E.EndTime) and ( ".$self->{Sql}." )"; + } + } + my @auto_terms; + if ( $self->{AutoArchive} ) { + push @auto_terms, "E.Archived = 0"; + } + # Don't do this, it prevents re-generation and concatenation. + # If the file already exists, then the video won't be re-recreated + if ( $self->{AutoVideo} ) { + push @auto_terms, "E.Videoed = 0"; + } + if ( $self->{AutoUpload} ) { + push @auto_terms, "E.Uploaded = 0"; + } + if ( $self->{AutoEmail} ) { + push @auto_terms, "E.Emailed = 0"; + } + if ( $self->{AutoMessage} ) { + push @auto_terms, "E.Messaged = 0"; + } + if ( $self->{AutoExecute} ) { + push @auto_terms, "E.Executed = 0"; + } + if ( @auto_terms ) { + $sql .= " and ( ".join( " or ", @auto_terms )." )"; + } + if ( !$filter_expr->{sort_field} ) { + $filter_expr->{sort_field} = 'StartTime'; + $filter_expr->{sort_asc} = 0; + } + my $sort_column = ''; + if ( $filter_expr->{sort_field} eq 'Id' ) { + $sort_column = "E.Id"; + } elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) { + $sort_column = "M.Name"; + } elsif ( $filter_expr->{sort_field} eq 'Name' ) { + $sort_column = "E.Name"; + } elsif ( $filter_expr->{sort_field} eq 'StartTime' ) { + $sort_column = "E.StartTime"; + } elsif ( $filter_expr->{sort_field} eq 'Secs' ) { + $sort_column = "E.Length"; + } elsif ( $filter_expr->{sort_field} eq 'Frames' ) { + $sort_column = "E.Frames"; + } elsif ( $filter_expr->{sort_field} eq 'AlarmFrames' ) { + $sort_column = "E.AlarmFrames"; + } elsif ( $filter_expr->{sort_field} eq 'TotScore' ) { + $sort_column = "E.TotScore"; + } elsif ( $filter_expr->{sort_field} eq 'AvgScore' ) { + $sort_column = "E.AvgScore"; + } elsif ( $filter_expr->{sort_field} eq 'MaxScore' ) { + $sort_column = "E.MaxScore"; + } else { + $sort_column = "E.StartTime"; + } + my $sort_order = $filter_expr->{sort_asc}?"asc":"desc"; + $sql .= " order by ".$sort_column." ".$sort_order; + if ( $filter_expr->{limit} ) { + $sql .= " limit 0,".$filter_expr->{limit}; + } + $self->{Sql} = $sql; + } # end if has Sql + return $self->{Sql}; } # end sub Sql -sub getDiskPercent -{ - my $command = "df ."; - my $df = qx( $command ); - my $space = -1; - if ( $df =~ /\s(\d+)%/ms ) - { - $space = $1; - } - return( $space ); +sub getDiskPercent { + my $command = "df " . ($_[0] ? $_[0] : '.'); + my $df = qx( $command ); + my $space = -1; + if ( $df =~ /\s(\d+)%/ms ) { + $space = $1; + } + return( $space ); } -sub getDiskBlocks -{ - my $command = "df ."; - my $df = qx( $command ); - my $space = -1; - if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) - { - $space = $1; - } - return( $space ); + +sub getDiskBlocks { + my $command = "df ."; + my $df = qx( $command ); + my $space = -1; + if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) { + $space = $1; + } + return( $space ); } -sub getLoad -{ - my $command = "uptime ."; - my $uptime = qx( $command ); - my $load = -1; - if ( $uptime =~ /load average:\s+([\d.]+)/ms ) - { - $load = $1; - Info( "Load: $load" ); - } - return( $load ); + +sub getLoad { + my $command = "uptime ."; + my $uptime = qx( $command ); + my $load = -1; + if ( $uptime =~ /load average:\s+([\d.]+)/ms ) { + $load = $1; + Info( "Load: $load" ); + } + return( $load ); } # # More or less replicates the equivalent PHP function # -sub strtotime -{ - my $dt_str = shift; - return( Date::Manip::UnixDate( $dt_str, '%s' ) ); +sub strtotime { + my $dt_str = shift; + return( Date::Manip::UnixDate( $dt_str, '%s' ) ); } # # More or less replicates the equivalent PHP function # -sub str_repeat -{ - my $string = shift; - my $count = shift; - return( ${string}x${count} ); +sub str_repeat { + my $string = shift; + my $count = shift; + return( ${string}x${count} ); } # Formats a date into MySQL format -sub DateTimeToSQL -{ - my $dt_str = shift; - my $dt_val = strtotime( $dt_str ); - if ( !$dt_val ) - { - Error( "Unable to parse date string '$dt_str'\n" ); - return( undef ); - } - return( strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) ); +sub DateTimeToSQL { + my $dt_str = shift; + my $dt_val = strtotime( $dt_str ); + if ( !$dt_val ) { + Error( "Unable to parse date string '$dt_str'\n" ); + return( undef ); + } + return( strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) ); } 1; @@ -482,8 +389,8 @@ ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS - use ZoneMinder::Filter; - blah blah blah +use ZoneMinder::Filter; +blah blah blah =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index 944781f6b..3d004725b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -42,19 +42,19 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( 'functions' => [ qw( - executeShellCommand - getCmdFormat - runCommand - setFileOwner - getEventPath - createEventPath - createEvent - deleteEventFiles - makePath - jsonEncode - jsonDecode - ) ] -); + executeShellCommand + getCmdFormat + runCommand + setFileOwner + getEventPath + createEventPath + createEvent + deleteEventFiles + makePath + jsonEncode + jsonDecode + ) ] + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); @@ -76,212 +76,190 @@ use ZoneMinder::Database qw(:all); use POSIX; # For running general shell commands -sub executeShellCommand -{ - my $command = shift; - my $output = qx( $command ); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - Debug( "Command: $command\n" ); - chomp( $output ); - Debug( "Output: $output\n" ); - } - return( $status ); +sub executeShellCommand { + my $command = shift; + my $output = qx( $command ); + my $status = $? >> 8; + if ( $status || logDebugging() ) { + Debug( "Command: $command\n" ); + chomp( $output ); + Debug( "Output: $output\n" ); + } + return( $status ); } -sub getCmdFormat -{ - Debug( "Testing valid shell syntax\n" ); +sub getCmdFormat { + Debug( "Testing valid shell syntax\n" ); - my ( $name ) = getpwuid( $> ); - if ( $name eq $Config{ZM_WEB_USER} ) - { - Debug( "Running as '$name', su commands not needed\n" ); - return( "" ); - } + my ( $name ) = getpwuid( $> ); + if ( $name eq $Config{ZM_WEB_USER} ) { + Debug( "Running as '$name', su commands not needed\n" ); + return( "" ); + } - my $null_command = "true"; + my $null_command = "true"; - my $prefix = "sudo -u ".$Config{ZM_WEB_USER}." "; - my $suffix = ""; - my $command = $prefix.$null_command.$suffix; + my $prefix = "sudo -u ".$Config{ZM_WEB_USER}." "; + my $suffix = ""; + my $command = $prefix.$null_command.$suffix; + Debug( "Testing \"$command\"\n" ); + my $output = qx($command 2>&1); + my $status = $? >> 8; + $output //= $!; + + if ( !$status ) { + Debug( "Test ok, using format \"$prefix$suffix\"\n" ); + return( $prefix, $suffix ); + } else { + chomp( $output ); + Debug( "Test failed, '$output'\n" ); + + $prefix = "su ".$Config{ZM_WEB_USER}." --shell=/bin/sh --command='"; + $suffix = "'"; + $command = $prefix.$null_command.$suffix; Debug( "Testing \"$command\"\n" ); - my $output = qx($command); + my $output = qx($command 2>&1); my $status = $? >> 8; - if ( !$status ) - { + $output //= $!; + + if ( !$status ) { + Debug( "Test ok, using format \"$prefix$suffix\"\n" ); + return( $prefix, $suffix ); + } else { + chomp( $output ); + Debug( "Test failed, '$output'\n" ); + + $prefix = "su ".$Config{ZM_WEB_USER}." -c '"; + $suffix = "'"; + $command = $prefix.$null_command.$suffix; + Debug( "Testing \"$command\"\n" ); + $output = qx($command 2>&1); + $status = $? >> 8; + $output //= $!; + + if ( !$status ) { Debug( "Test ok, using format \"$prefix$suffix\"\n" ); return( $prefix, $suffix ); - } - else - { + } else { chomp( $output ); Debug( "Test failed, '$output'\n" ); - - $prefix = "su ".$Config{ZM_WEB_USER}." --shell=/bin/sh --command='"; - $suffix = "'"; - $command = $prefix.$null_command.$suffix; - Debug( "Testing \"$command\"\n" ); - my $output = qx($command); - my $status = $? >> 8; - if ( !$status ) - { - Debug( "Test ok, using format \"$prefix$suffix\"\n" ); - return( $prefix, $suffix ); - } - else - { - chomp( $output ); - Debug( "Test failed, '$output'\n" ); - - $prefix = "su ".$Config{ZM_WEB_USER}." -c '"; - $suffix = "'"; - $command = $prefix.$null_command.$suffix; - Debug( "Testing \"$command\"\n" ); - $output = qx($command); - $status = $? >> 8; - if ( !$status ) - { - Debug( "Test ok, using format \"$prefix$suffix\"\n" ); - return( $prefix, $suffix ); - } - else - { - chomp( $output ); - Debug( "Test failed, '$output'\n" ); - } - } + } } - Error( "Unable to find valid 'su' syntax\n" ); - exit( -1 ); + } + Error( "Unable to find valid 'su' syntax\n" ); + exit( -1 ); } our $testedShellSyntax = 0; our ( $cmdPrefix, $cmdSuffix ); # For running ZM daemons etc -sub runCommand -{ - if ( !$testedShellSyntax ) - { - # Determine the appropriate syntax for the su command - ( $cmdPrefix, $cmdSuffix ) = getCmdFormat(); - $testedShellSyntax = !undef; - } +sub runCommand { + if ( !$testedShellSyntax ) { +# Determine the appropriate syntax for the su command + ( $cmdPrefix, $cmdSuffix ) = getCmdFormat(); + $testedShellSyntax = !undef; + } - my $command = shift; - $command = $Config{ZM_PATH_BIN}."/".$command; - if ( $cmdPrefix ) - { - $command = $cmdPrefix.$command.$cmdSuffix; + my $command = shift; + $command = $Config{ZM_PATH_BIN}."/".$command; + if ( $cmdPrefix ) { + $command = $cmdPrefix.$command.$cmdSuffix; + } + Debug( "Command: $command\n" ); + my $output = qx($command); + my $status = $? >> 8; + chomp( $output ); + if ( $status || logDebugging() ) { + if ( $status ) { + Error( "Unable to run \"$command\", output is \"$output\"\n" ); + exit( -1 ); + } else { + Debug( "Output: $output\n" ); } - Debug( "Command: $command\n" ); - my $output = qx($command); - my $status = $? >> 8; - chomp( $output ); - if ( $status || logDebugging() ) - { - if ( $status ) - { - Error( "Unable to run \"$command\", output is \"$output\"\n" ); - exit( -1 ); - } - else - { - Debug( "Output: $output\n" ); - } - } - return( $output ); + } + return( $output ); } -sub getEventPath -{ - my $event = shift; +sub getEventPath { + my $event = shift; - my $event_path = ""; - if ( $Config{ZM_USE_DEEP_STORAGE} ) - { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.strftime( "%y/%m/%d/%H/%M/%S", - localtime($event->{Time}) - ) - ; - } - else - { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.$event->{Id} - ; - } + my $event_path = ""; + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + $event_path = $Config{ZM_DIR_EVENTS} + .'/'.$event->{MonitorId} + .'/'.strftime( "%y/%m/%d/%H/%M/%S", + localtime($event->{Time}) + ) + ; + } else { + $event_path = $Config{ZM_DIR_EVENTS} + .'/'.$event->{MonitorId} + .'/'.$event->{Id} + ; + } - if ( index($Config{ZM_DIR_EVENTS},'/') != 0 ){ - $event_path = $Config{ZM_PATH_WEB} - .'/'.$event_path - ; - } - return( $event_path ); + if ( index($Config{ZM_DIR_EVENTS},'/') != 0 ) { + $event_path = $Config{ZM_PATH_WEB} + .'/'.$event_path + ; + } + return( $event_path ); } -sub createEventPath -{ - # - # WARNING assumes running from events directory - # - my $event = shift; - my $eventRootPath = ($Config{ZM_DIR_EVENTS}=~m|/|) - ? $Config{ZM_DIR_EVENTS} - : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}); - my $eventPath = $eventRootPath.'/'.$event->{MonitorId}; +sub createEventPath { +# +# WARNING assumes running from events directory +# + my $event = shift; + my $eventRootPath = ($Config{ZM_DIR_EVENTS}=~m|/|) + ? $Config{ZM_DIR_EVENTS} + : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}); + my $eventPath = $eventRootPath.'/'.$event->{MonitorId}; - if ( $Config{ZM_USE_DEEP_STORAGE} ) - { - my @startTime = localtime( $event->{StartTime} ); + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + my @startTime = localtime( $event->{StartTime} ); - my @datetimeParts = (); - $datetimeParts[0] = sprintf( "%02d", $startTime[5]-100 ); - $datetimeParts[1] = sprintf( "%02d", $startTime[4]+1 ); - $datetimeParts[2] = sprintf( "%02d", $startTime[3] ); - $datetimeParts[3] = sprintf( "%02d", $startTime[2] ); - $datetimeParts[4] = sprintf( "%02d", $startTime[1] ); - $datetimeParts[5] = sprintf( "%02d", $startTime[0] ); + my @datetimeParts = (); + $datetimeParts[0] = sprintf( "%02d", $startTime[5]-100 ); + $datetimeParts[1] = sprintf( "%02d", $startTime[4]+1 ); + $datetimeParts[2] = sprintf( "%02d", $startTime[3] ); + $datetimeParts[3] = sprintf( "%02d", $startTime[2] ); + $datetimeParts[4] = sprintf( "%02d", $startTime[1] ); + $datetimeParts[5] = sprintf( "%02d", $startTime[0] ); - my $datePath = join('/',@datetimeParts[0..2]); - my $timePath = join('/',@datetimeParts[3..5]); + my $datePath = join('/',@datetimeParts[0..2]); + my $timePath = join('/',@datetimeParts[3..5]); - makePath( $datePath, $eventPath ); - $eventPath .= '/'.$datePath; + makePath( $datePath, $eventPath ); + $eventPath .= '/'.$datePath; - # Create event id symlink - my $idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} ); - symlink( $timePath, $idFile ) - or Fatal( "Can't symlink $idFile -> $eventPath: $!" ); +# Create event id symlink + my $idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} ); + symlink( $timePath, $idFile ) + or Fatal( "Can't symlink $idFile -> $eventPath: $!" ); - makePath( $timePath, $eventPath ); - $eventPath .= '/'.$timePath; - setFileOwner( $idFile ); # Must come after directory has been created + makePath( $timePath, $eventPath ); + $eventPath .= '/'.$timePath; + setFileOwner( $idFile ); # Must come after directory has been created - # Create empty id tag file - $idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} ); - open( my $ID_FP, ">", $idFile ) - or Fatal( "Can't open $idFile: $!" ); - close( $ID_FP ); - setFileOwner( $idFile ); - } - else - { - makePath( $event->{Id}, $eventPath ); - $eventPath .= '/'.$event->{Id}; +# Create empty id tag file + $idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} ); + open( my $ID_FP, ">", $idFile ) + or Fatal( "Can't open $idFile: $!" ); + close( $ID_FP ); + setFileOwner( $idFile ); + } else { + makePath( $event->{Id}, $eventPath ); + $eventPath .= '/'.$event->{Id}; - my $idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} ); - open( my $ID_FP, ">", $idFile ) - or Fatal( "Can't open $idFile: $!" ); - close( $ID_FP ); - setFileOwner( $idFile ); - } - return( $eventPath ); + my $idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} ); + open( my $ID_FP, ">", $idFile ) + or Fatal( "Can't open $idFile: $!" ); + close( $ID_FP ); + setFileOwner( $idFile ); + } + return( $eventPath ); } use Data::Dumper; @@ -289,491 +267,412 @@ use Data::Dumper; our $_setFileOwner = undef; our ( $_ownerUid, $_ownerGid ); -sub _checkProcessOwner -{ - if ( !defined($_setFileOwner) ) - { - my ( $processOwner ) = getpwuid( $> ); - if ( $processOwner ne $Config{ZM_WEB_USER} ) - { - # Not running as web user, so should be root in which case chown - # the temporary directory - ( my $ownerName, my $ownerPass, $_ownerUid, $_ownerGid ) - = getpwnam( $Config{ZM_WEB_USER} ) - or Fatal( "Can't get user details for web user '" - .$Config{ZM_WEB_USER}."': $!" - ); - $_setFileOwner = 1; - } - else - { - $_setFileOwner = 0; - } +sub _checkProcessOwner { + if ( !defined($_setFileOwner) ) { + my ( $processOwner ) = getpwuid( $> ); + if ( $processOwner ne $Config{ZM_WEB_USER} ) { +# Not running as web user, so should be root in which case chown +# the temporary directory + ( my $ownerName, my $ownerPass, $_ownerUid, $_ownerGid ) + = getpwnam( $Config{ZM_WEB_USER} ) + or Fatal( "Can't get user details for web user '" + .$Config{ZM_WEB_USER}."': $!" + ); + $_setFileOwner = 1; + } else { + $_setFileOwner = 0; } - return( $_setFileOwner ); + } + return( $_setFileOwner ); } -sub setFileOwner -{ - my $file = shift; +sub setFileOwner { + my $file = shift; - if ( _checkProcessOwner() ) - { - chown( $_ownerUid, $_ownerGid, $file ) - or Fatal( "Can't change ownership of file '$file' to '" - .$Config{ZM_WEB_USER}.":".$Config{ZM_WEB_GROUP}."': $!" - ); - } + if ( _checkProcessOwner() ) { + chown( $_ownerUid, $_ownerGid, $file ) + or Fatal( "Can't change ownership of file '$file' to '" + .$Config{ZM_WEB_USER}.":".$Config{ZM_WEB_GROUP}."': $!" + ); + } } our $_hasImageInfo = undef; -sub _checkForImageInfo -{ - if ( !defined($_hasImageInfo) ) - { - my $result = eval - { - require Image::Info; - Image::Info->import(); - }; - $_hasImageInfo = $@?0:1; - } - return( $_hasImageInfo ); +sub _checkForImageInfo { + if ( !defined($_hasImageInfo) ) { + my $result = eval { + require Image::Info; + Image::Info->import(); + }; + $_hasImageInfo = $@?0:1; + } + return( $_hasImageInfo ); } -sub createEvent -{ - my $event = shift; +sub createEvent { + my $event = shift; - Debug( "Creating event" ); - #print( Dumper( $event )."\n" ); + Debug( "Creating event" ); +#print( Dumper( $event )."\n" ); - _checkForImageInfo(); + _checkForImageInfo(); - my $dbh = zmDbConnect(); + my $dbh = zmDbConnect(); - if ( $event->{monitor} ) - { - $event->{MonitorId} = $event->{monitor}->{Id}; - } - elsif ( $event->{MonitorId} ) - { - my $sql = "select * from Monitors where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{MonitorId} ) - or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); - $event->{monitor} = $sth->fetchrow_hashref() - or Fatal( "Unable to create event, can't load monitor with id '" - .$event->{MonitorId}."'" - ); - $sth->finish(); - } - else - { - Fatal( "Unable to create event, no monitor or monitor id supplied" ); - } - $event->{Name} = "New Event" unless( $event->{Name} ); - $event->{Frames} = int(@{$event->{frames}}); - $event->{TotScore} = $event->{MaxScore} = 0; + if ( $event->{monitor} ) { + $event->{MonitorId} = $event->{monitor}->{Id}; + } elsif ( $event->{MonitorId} ) { + my $sql = "select * from Monitors where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{MonitorId} ) + or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); + $event->{monitor} = $sth->fetchrow_hashref() + or Fatal( "Unable to create event, can't load monitor with id '" + .$event->{MonitorId}."'" + ); + $sth->finish(); + } else { + Fatal( "Unable to create event, no monitor or monitor id supplied" ); + } + $event->{Name} = "New Event" unless( $event->{Name} ); + $event->{Frames} = int(@{$event->{frames}}); + $event->{TotScore} = $event->{MaxScore} = 0; - my $lastTimestamp = 0.0; - foreach my $frame ( @{$event->{frames}} ) - { - if ( !$event->{Width} ) - { - if ( $_hasImageInfo ) - { - my $imageInfo = Image::Info::image_info( $frame->{imagePath} ); - if ( $imageInfo->{error} ) - { - Error( "Unable to extract image info from '" - .$frame->{imagePath}."': ".$imageInfo->{error} - ); - } - else - { - ( $event->{Width}, $event->{Height} ) = Image::Info::dim( $imageInfo ); - } - } + my $lastTimestamp = 0.0; + foreach my $frame ( @{$event->{frames}} ) { + if ( !$event->{Width} ) { + if ( $_hasImageInfo ) { + my $imageInfo = Image::Info::image_info( $frame->{imagePath} ); + if ( $imageInfo->{error} ) { + Error( "Unable to extract image info from '" + .$frame->{imagePath}."': ".$imageInfo->{error} + ); + } else { + ( $event->{Width}, $event->{Height} ) = Image::Info::dim( $imageInfo ); } - $frame->{Type} = $frame->{Score}>0?'Alarm':'Normal' unless( $frame->{Type} ); - $frame->{Delta} = $lastTimestamp?($frame->{TimeStamp}-$lastTimestamp):0.0; - $event->{StartTime} = $frame->{TimeStamp} unless ( $event->{StartTime} ); - $event->{TotScore} += $frame->{Score}; - $event->{MaxScore} = $frame->{Score} if ( $frame->{Score} > $event->{MaxScore} ); - $event->{AlarmFrames}++ if ( $frame->{Type} eq 'Alarm' ); - $event->{EndTime} = $frame->{TimeStamp}; - $lastTimestamp = $frame->{TimeStamp}; + } } - $event->{Width} = $event->{monitor}->{Width} unless( $event->{Width} ); - $event->{Height} = $event->{monitor}->{Height} unless( $event->{Height} ); - $event->{AvgScore} = $event->{TotScore}/int($event->{AlarmFrames}); - $event->{Length} = $event->{EndTime} - $event->{StartTime}; + $frame->{Type} = $frame->{Score}>0?'Alarm':'Normal' unless( $frame->{Type} ); + $frame->{Delta} = $lastTimestamp?($frame->{TimeStamp}-$lastTimestamp):0.0; + $event->{StartTime} = $frame->{TimeStamp} unless ( $event->{StartTime} ); + $event->{TotScore} += $frame->{Score}; + $event->{MaxScore} = $frame->{Score} if ( $frame->{Score} > $event->{MaxScore} ); + $event->{AlarmFrames}++ if ( $frame->{Type} eq 'Alarm' ); + $event->{EndTime} = $frame->{TimeStamp}; + $lastTimestamp = $frame->{TimeStamp}; + } + $event->{Width} = $event->{monitor}->{Width} unless( $event->{Width} ); + $event->{Height} = $event->{monitor}->{Height} unless( $event->{Height} ); + $event->{AvgScore} = $event->{TotScore}/int($event->{AlarmFrames}); + $event->{Length} = $event->{EndTime} - $event->{StartTime}; - my %formats = ( - StartTime => 'from_unixtime(?)', - EndTime => 'from_unixtime(?)', - ); + my %formats = ( + StartTime => 'from_unixtime(?)', + EndTime => 'from_unixtime(?)', + ); + + my ( @fields, @formats, @values ); + while ( my ( $field, $value ) = each( %$event ) ) { + next unless $field =~ /^[A-Z]/; + push( @fields, $field ); + push( @formats, ($formats{$field} or '?') ); + push( @values, $event->{$field} ); + } + + my $sql = "INSERT INTO Events (".join(',',@fields) + .") VALUES (".join(',',@formats).")" + ; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( @values ) + or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); + $event->{Id} = $dbh->{mysql_insertid}; + Info( "Created event ".$event->{Id} ); + + if ( $event->{EndTime} ) { + $event->{Name} = $event->{monitor}->{EventPrefix}.$event->{Id} + if ( $event->{Name} eq 'New Event' ); + my $sql = "update Events set Name = ? where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Name}, $event->{Id} ) + or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); + } + + my $eventPath = createEventPath( $event ); + + my %frameFormats = ( + TimeStamp => 'from_unixtime(?)', + ); + my $frameId = 1; + foreach my $frame ( @{$event->{frames}} ) { + $frame->{EventId} = $event->{Id}; + $frame->{FrameId} = $frameId++; my ( @fields, @formats, @values ); - while ( my ( $field, $value ) = each( %$event ) ) - { - next unless $field =~ /^[A-Z]/; - push( @fields, $field ); - push( @formats, ($formats{$field} or '?') ); - push( @values, $event->{$field} ); + while ( my ( $field, $value ) = each( %$frame ) ) { + next unless $field =~ /^[A-Z]/; + push( @fields, $field ); + push( @formats, ($frameFormats{$field} or '?') ); + push( @values, $frame->{$field} ); } - my $sql = "INSERT INTO Events (".join(',',@fields) - .") VALUES (".join(',',@formats).")" - ; + my $sql = "insert into Frames (".join(',',@fields) + .") values (".join(',',@formats).")" + ; my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); + or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); my $res = $sth->execute( @values ) - or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); - $event->{Id} = $dbh->{mysql_insertid}; - Info( "Created event ".$event->{Id} ); - - if ( $event->{EndTime} ) - { - $event->{Name} = $event->{monitor}->{EventPrefix}.$event->{Id} - if ( $event->{Name} eq 'New Event' ); - my $sql = "update Events set Name = ? where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Name}, $event->{Id} ) - or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); - } - - my $eventPath = createEventPath( $event ); - - my %frameFormats = ( - TimeStamp => 'from_unixtime(?)', - ); - my $frameId = 1; - foreach my $frame ( @{$event->{frames}} ) - { - $frame->{EventId} = $event->{Id}; - $frame->{FrameId} = $frameId++; - - my ( @fields, @formats, @values ); - while ( my ( $field, $value ) = each( %$frame ) ) - { - next unless $field =~ /^[A-Z]/; - push( @fields, $field ); - push( @formats, ($frameFormats{$field} or '?') ); - push( @values, $frame->{$field} ); - } - - my $sql = "insert into Frames (".join(',',@fields) - .") values (".join(',',@formats).")" - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( @values ) - or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); - #$frame->{FrameId} = $dbh->{mysql_insertid}; - if ( $frame->{imagePath} ) - { - $frame->{capturePath} = sprintf( - "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS} - ."d-capture.jpg" - , $eventPath - , $frame->{FrameId} + or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); +#$frame->{FrameId} = $dbh->{mysql_insertid}; + if ( $frame->{imagePath} ) { + $frame->{capturePath} = sprintf( + "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS} + ."d-capture.jpg" + , $eventPath + , $frame->{FrameId} + ); + rename( $frame->{imagePath}, $frame->{capturePath} ) + or Fatal( "Can't copy ".$frame->{imagePath} + ." to ".$frame->{capturePath}.": $!" ); - rename( $frame->{imagePath}, $frame->{capturePath} ) - or Fatal( "Can't copy ".$frame->{imagePath} - ." to ".$frame->{capturePath}.": $!" - ); - setFileOwner( $frame->{capturePath} ); - if ( 0 && $Config{ZM_CREATE_ANALYSIS_IMAGES} ) - { - $frame->{analysePath} = sprintf( - "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS} - ."d-analyse.jpg" - , $eventPath - , $frame->{FrameId} - ); - link( $frame->{capturePath}, $frame->{analysePath} ) - or Fatal( "Can't link ".$frame->{capturePath} - ." to ".$frame->{analysePath}.": $!" - ); - setFileOwner( $frame->{analysePath} ); - } - } + setFileOwner( $frame->{capturePath} ); + if ( 0 && $Config{ZM_CREATE_ANALYSIS_IMAGES} ) { + $frame->{analysePath} = sprintf( + "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS} + ."d-analyse.jpg" + , $eventPath + , $frame->{FrameId} + ); + link( $frame->{capturePath}, $frame->{analysePath} ) + or Fatal( "Can't link ".$frame->{capturePath} + ." to ".$frame->{analysePath}.": $!" + ); + setFileOwner( $frame->{analysePath} ); + } } + } } -sub addEventImage -{ - my $event = shift; - my $frame = shift; +sub addEventImage { + my $event = shift; + my $frame = shift; - # TBD +# TBD } -sub updateEvent -{ - my $event = shift; +sub updateEvent { + my $event = shift; - if ( !$event->{EventId} ) - { - Error( "Unable to update event, no event id supplied" ); - return( 0 ); - } + if ( !$event->{EventId} ) { + Error( "Unable to update event, no event id supplied" ); + return( 0 ); + } - my $dbh = zmDbConnect(); + my $dbh = zmDbConnect(); - $event->{Name} = $event->{monitor}->{EventPrefix}.$event->{Id} - if ( $event->{Name} eq 'New Event' ); + $event->{Name} = $event->{monitor}->{EventPrefix}.$event->{Id} + if ( $event->{Name} eq 'New Event' ); - my %formats = ( - StartTime => 'from_unixtime(?)', - EndTime => 'from_unixtime(?)', - ); + my %formats = ( + StartTime => 'from_unixtime(?)', + EndTime => 'from_unixtime(?)', + ); - my ( @values, @sets ); - while ( my ( $field, $value ) = each( %$event ) ) - { - next if ( $field eq 'Id' ); - push( @values, $event->{$field} ); - push( @sets, $field." = ".($formats{$field} or '?') ); - } - my $sql = "update Events set ".join(',',@sets)." where Id = ?"; - push( @values, $event->{Id} ); + my ( @values, @sets ); + while ( my ( $field, $value ) = each( %$event ) ) { + next if ( $field eq 'Id' ); + push( @values, $event->{$field} ); + push( @sets, $field." = ".($formats{$field} or '?') ); + } + my $sql = "update Events set ".join(',',@sets)." where Id = ?"; + push( @values, $event->{Id} ); - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( @values ) - or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( @values ) + or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); } -sub deleteEventFiles -{ - # - # WARNING assumes running from events directory - # - my $event_id = shift; - my $monitor_id = shift; - $monitor_id = '*' if ( !defined($monitor_id) ); +sub deleteEventFiles { +# +# WARNING assumes running from events directory +# + my $event_id = shift; + my $monitor_id = shift; + $monitor_id = '*' if ( !defined($monitor_id) ); - if ( $Config{ZM_USE_DEEP_STORAGE} ) - { - my $link_path = $monitor_id."/*/*/*/.".$event_id; - #Debug( "LP1:$link_path" ); - my @links = glob($link_path); - #Debug( "L:".$links[0].": $!" ); - if ( @links ) - { - ( $link_path ) = ( $links[0] =~ /^(.*)$/ ); # De-taint - #Debug( "LP2:$link_path" ); + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + my $link_path = $monitor_id."/*/*/*/.".$event_id; +#Debug( "LP1:$link_path" ); + my @links = glob($link_path); +#Debug( "L:".$links[0].": $!" ); + if ( @links ) { + ( $link_path ) = ( $links[0] =~ /^(.*)$/ ); # De-taint +#Debug( "LP2:$link_path" ); - ( my $day_path = $link_path ) =~ s/\.\d+//; - #Debug( "DP:$day_path" ); - my $event_path = $day_path.readlink( $link_path ); - ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint - #Debug( "EP:$event_path" ); - my $command = "/bin/rm -rf ".$event_path; - #Debug( "C:$command" ); - executeShellCommand( $command ); + ( my $day_path = $link_path ) =~ s/\.\d+//; +#Debug( "DP:$day_path" ); + my $event_path = $day_path.readlink( $link_path ); + ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint +#Debug( "EP:$event_path" ); + my $command = "/bin/rm -rf ".$event_path; +#Debug( "C:$command" ); + executeShellCommand( $command ); - unlink( $link_path ) or Error( "Unable to unlink '$link_path': $!" ); - my @path_parts = split( /\//, $event_path ); - for ( my $i = int(@path_parts)-2; $i >= 1; $i-- ) - { - my $delete_path = join( '/', @path_parts[0..$i] ); - #Debug( "DP$i:$delete_path" ); - my @has_files = glob( $delete_path."/*" ); - #Debug( "HF1:".$has_files[0] ) if ( @has_files ); - last if ( @has_files ); - @has_files = glob( $delete_path."/.[0-9]*" ); - #Debug( "HF2:".$has_files[0] ) if ( @has_files ); - last if ( @has_files ); - my $command = "/bin/rm -rf ".$delete_path; - executeShellCommand( $command ); - } - } - } - else - { - my $command = "/bin/rm -rf $monitor_id/$event_id"; + unlink( $link_path ) or Error( "Unable to unlink '$link_path': $!" ); + my @path_parts = split( /\//, $event_path ); + for ( my $i = int(@path_parts)-2; $i >= 1; $i-- ) { + my $delete_path = join( '/', @path_parts[0..$i] ); +#Debug( "DP$i:$delete_path" ); + my @has_files = glob( $delete_path."/*" ); +#Debug( "HF1:".$has_files[0] ) if ( @has_files ); + last if ( @has_files ); + @has_files = glob( $delete_path."/.[0-9]*" ); +#Debug( "HF2:".$has_files[0] ) if ( @has_files ); + last if ( @has_files ); + my $command = "/bin/rm -rf ".$delete_path; executeShellCommand( $command ); + } } + } else { + my $command = "/bin/rm -rf $monitor_id/$event_id"; + executeShellCommand( $command ); + } } -sub makePath -{ - my $path = shift; - my $root = shift; - $root = (( $path =~ m|^/| )?'':'.' ) unless( $root ); +sub makePath { + my $path = shift; + my $root = shift; + $root = (( $path =~ m|^/| )?'':'.' ) unless( $root ); - Debug( "Creating path '$path' in $root'\n" ); - my @parts = split( '/', $path ); - my $fullPath = $root; - foreach my $dir ( @parts ) - { - $fullPath .= '/'.$dir; - if ( !-d $fullPath ) - { - if ( -e $fullPath ) - { - Fatal( "Can't create '$fullPath', already exists as non directory" ); - } - else - { - Debug( "Creating '$fullPath'\n" ); - mkdir( $fullPath, 0755 ) or Fatal( "Can't mkdir '$fullPath': $!" ); - setFileOwner( $fullPath ); - } - } + Debug( "Creating path '$path' in $root'\n" ); + my @parts = split( '/', $path ); + my $fullPath = $root; + foreach my $dir ( @parts ) { + $fullPath .= '/'.$dir; + if ( !-d $fullPath ) { + if ( -e $fullPath ) { + Fatal( "Can't create '$fullPath', already exists as non directory" ); + } else { + Debug( "Creating '$fullPath'\n" ); + mkdir( $fullPath, 0755 ) or Fatal( "Can't mkdir '$fullPath': $!" ); + setFileOwner( $fullPath ); + } } - return( $fullPath ); + } + return( $fullPath ); } our $testedJSON = 0; our $hasJSONAny = 0; -sub _testJSON -{ - return if ( $testedJSON ); - my $result = eval - { - require JSON::Any; - JSON::Any->import(); - }; - $testedJSON = 1; - $hasJSONAny = 1 if ( $result ); +sub _testJSON { + return if ( $testedJSON ); + my $result = eval { + require JSON::Any; + JSON::Any->import(); + }; + $testedJSON = 1; + $hasJSONAny = 1 if ( $result ); } -sub _getJSONType -{ - my $value = shift; - return( 'null' ) unless( defined($value) ); - return( 'integer' ) if ( $value =~ /^\d+$/ ); - return( 'double' ) if ( $value =~ /^\d+$/ ); - return( 'hash' ) if ( ref($value) eq 'HASH' ); - return( 'array' ) if ( ref($value) eq 'ARRAY' ); - return( 'string' ); +sub _getJSONType { + my $value = shift; + return( 'null' ) unless( defined($value) ); + return( 'integer' ) if ( $value =~ /^\d+$/ ); + return( 'double' ) if ( $value =~ /^\d+$/ ); + return( 'hash' ) if ( ref($value) eq 'HASH' ); + return( 'array' ) if ( ref($value) eq 'ARRAY' ); + return( 'string' ); } sub jsonEncode; -sub jsonEncode -{ - my $value = shift; +sub jsonEncode { + my $value = shift; - _testJSON(); - if ( $hasJSONAny ) - { - my $string = eval { JSON::Any->objToJson( $value ) }; - Fatal( "Unable to encode object to JSON: $@" ) unless( $string ); - return( $string ); - } + _testJSON(); + if ( $hasJSONAny ) { + my $string = eval { JSON::Any->objToJson( $value ) }; + Fatal( "Unable to encode object to JSON: $@" ) unless( $string ); + return( $string ); + } - my $type = _getJSONType($value); - if ( $type eq 'integer' || $type eq 'double' ) - { - return( $value ); - } - elsif ( $type eq 'boolean' ) - { - return( $value?'true':'false' ); - } - elsif ( $type eq 'string' ) - { - $value =~ s|(["\\/])|\\$1|g; - $value =~ s|\r?\n|\n|g; - return( '"'.$value.'"' ); - } - elsif ( $type eq 'null' ) - { - return( 'null' ); - } - elsif ( $type eq 'array' ) - { - return( '['.join( ',', map { jsonEncode( $_ ) } @$value ).']' ); - } - elsif ( $type eq 'hash' ) - { - my $result = '{'; - while ( my ( $subKey=>$subValue ) = each( %$value ) ) - { - $result .= ',' if ( $result ne '{' ); - $result .= '"'.$subKey.'":'.jsonEncode( $subValue ); - } - return( $result.'}' ); - } - else - { - Fatal( "Unexpected type '$type'" ); + my $type = _getJSONType($value); + if ( $type eq 'integer' || $type eq 'double' ) { + return( $value ); + } elsif ( $type eq 'boolean' ) { + return( $value?'true':'false' ); + } elsif ( $type eq 'string' ) { + $value =~ s|(["\\/])|\\$1|g; + $value =~ s|\r?\n|\n|g; + return( '"'.$value.'"' ); + } elsif ( $type eq 'null' ) { + return( 'null' ); + } elsif ( $type eq 'array' ) { + return( '['.join( ',', map { jsonEncode( $_ ) } @$value ).']' ); + } elsif ( $type eq 'hash' ) { + my $result = '{'; + while ( my ( $subKey=>$subValue ) = each( %$value ) ) { + $result .= ',' if ( $result ne '{' ); + $result .= '"'.$subKey.'":'.jsonEncode( $subValue ); } + return( $result.'}' ); + } else { + Fatal( "Unexpected type '$type'" ); + } } -sub jsonDecode -{ - my $value = shift; +sub jsonDecode { + my $value = shift; - _testJSON(); - if ( $hasJSONAny ) - { - my $object = eval { JSON::Any->jsonToObj( $value ) }; - Fatal( "Unable to decode JSON string '$value': $@" ) unless( $object ); - return( $object ); - } + _testJSON(); + if ( $hasJSONAny ) { + my $object = eval { JSON::Any->jsonToObj( $value ) }; + Fatal( "Unable to decode JSON string '$value': $@" ) unless( $object ); + return( $object ); + } - my $comment = 0; - my $unescape = 0; - my $out = ''; - my @chars = split( //, $value ); - for ( my $i = 0; $i < @chars; $i++ ) - { - if ( !$comment ) - { - if ( $chars[$i] eq ':' ) - { - $out .= '=>'; - } - else - { - $out .= $chars[$i]; - } - } - elsif ( !$unescape ) - { - if ( $chars[$i] eq '\\' ) - { - $unescape = 1; - } - else - { - $out .= $chars[$i]; - } - } - else - { - if ( $chars[$i] ne '/' ) - { - $out .= '\\'; - } - $out .= $chars[$i]; - $unescape = 0; - } - if ( $chars[$i] eq '"' ) - { - $comment = !$comment; - } + my $comment = 0; + my $unescape = 0; + my $out = ''; + my @chars = split( //, $value ); + for ( my $i = 0; $i < @chars; $i++ ) { + if ( !$comment ) { + if ( $chars[$i] eq ':' ) { + $out .= '=>'; + } else { + $out .= $chars[$i]; + } + } elsif ( !$unescape ) { + if ( $chars[$i] eq '\\' ) { + $unescape = 1; + } else { + $out .= $chars[$i]; + } + } else { + if ( $chars[$i] ne '/' ) { + $out .= '\\'; + } + $out .= $chars[$i]; + $unescape = 0; } - $out =~ s/=>true/=>1/g; - $out =~ s/=>false/=>0/g; - $out =~ s/=>null/=>undef/g; - $out =~ s/`/'/g; - $out =~ s/qx/qq/g; - ( $out ) = $out =~ m/^({.+})$/; # Detaint and check it's a valid object syntax + if ( $chars[$i] eq '"' ) { + $comment = !$comment; + } + } + $out =~ s/=>true/=>1/g; + $out =~ s/=>false/=>0/g; + $out =~ s/=>null/=>undef/g; + $out =~ s/`/'/g; + $out =~ s/qx/qq/g; + ( $out ) = $out =~ m/^({.+})$/; # Detaint and check it's a valid object syntax my $result = eval $out; - Fatal( $@ ) if ( $@ ); - return( $result ); + Fatal( $@ ) if ( $@ ); + return( $result ); } 1; @@ -786,8 +685,8 @@ ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS - use ZoneMinder::Database; - blah blah blah +use ZoneMinder::Database; +blah blah blah =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index a6a82db87..8563a95ef 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -42,43 +42,43 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( 'constants' => [ qw( - DEBUG - INFO - WARNING - ERROR - FATAL - PANIC - NOLOG - ) ], + DEBUG + INFO + WARNING + ERROR + FATAL + PANIC + NOLOG + ) ], 'functions' => [ qw( - logInit - logReinit - logTerm - logSetSignal - logClearSignal - logDebugging - logLevel - logTermLevel - logDatabaseLevel - logFileLevel - logSyslogLevel - Mark - Dump - Debug - Info - Warning - Error - Fatal - Panic - ) ] -); -push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; + logInit + logReinit + logTerm + logSetSignal + logClearSignal + logDebugging + logLevel + logTermLevel + logDatabaseLevel + logFileLevel + logSyslogLevel + Mark + Dump + Debug + Info + Warning + Error + Fatal + Panic + ) ] + ); + push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); + our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); -our @EXPORT = qw(); + our @EXPORT = qw(); -our $VERSION = $ZoneMinder::Base::VERSION; + our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # @@ -86,25 +86,25 @@ our $VERSION = $ZoneMinder::Base::VERSION; # # ========================================================================== -use ZoneMinder::Config qw(:all); + use ZoneMinder::Config qw(:all); -use DBI; -use Carp; -use POSIX; -use IO::Handle; -use Data::Dumper; -use Time::HiRes qw/gettimeofday/; -use Sys::Syslog; + use DBI; + use Carp; + use POSIX; + use IO::Handle; + use Data::Dumper; + use Time::HiRes qw/gettimeofday/; + use Sys::Syslog; -use constant { - DEBUG => 1, - INFO => 0, - WARNING => -1, - ERROR => -2, - FATAL => -3, - PANIC => -4, - NOLOG => -5 -}; + use constant { + DEBUG => 1, + INFO => 0, + WARNING => -1, + ERROR => -2, + FATAL => -3, + PANIC => -4, + NOLOG => -5 + }; our %codes = ( &DEBUG => "DBG", @@ -114,7 +114,7 @@ our %codes = ( &FATAL => "FAT", &PANIC => "PNC", &NOLOG => "OFF" -); + ); our %priorities = ( &DEBUG => "debug", @@ -123,664 +123,564 @@ our %priorities = ( &ERROR => "err", &FATAL => "err", &PANIC => "err" -); + ); our $logger; our $LOGFILE; -sub new -{ - my $class = shift; - my $this = {}; +sub new { + my $class = shift; + my $this = {}; - $this->{initialised} = undef; + $this->{initialised} = undef; - #$this->{id} = "zmundef"; - ( $this->{id} ) = $0 =~ m|^(?:.*/)?([^/]+?)(?:\.[^/.]+)?$|; - $this->{idRoot} = $this->{id}; - $this->{idArgs} = ""; +#$this->{id} = "zmundef"; + ( $this->{id} ) = $0 =~ m|^(?:.*/)?([^/]+?)(?:\.[^/.]+)?$|; + $this->{idRoot} = $this->{id}; + $this->{idArgs} = ""; - $this->{level} = INFO; - $this->{termLevel} = NOLOG; - $this->{databaseLevel} = NOLOG; - $this->{fileLevel} = NOLOG; - $this->{syslogLevel} = NOLOG; - $this->{effectiveLevel} = INFO; + $this->{level} = INFO; + $this->{termLevel} = NOLOG; + $this->{databaseLevel} = NOLOG; + $this->{fileLevel} = NOLOG; + $this->{syslogLevel} = NOLOG; + $this->{effectiveLevel} = INFO; - $this->{autoFlush} = 1; - $this->{hasTerm} = -t STDERR; + $this->{autoFlush} = 1; + $this->{hasTerm} = -t STDERR; - ( $this->{fileName} = $0 ) =~ s|^.*/||; - $this->{logPath} = $Config{ZM_PATH_LOGS}; - $this->{logFile} = $this->{logPath}."/".$this->{id}.".log"; + ( $this->{fileName} = $0 ) =~ s|^.*/||; + $this->{logPath} = $Config{ZM_PATH_LOGS}; + $this->{logFile} = $this->{logPath}."/".$this->{id}.".log"; - $this->{trace} = 0; + $this->{trace} = 0; - bless( $this, $class ); - return $this; + bless( $this, $class ); + return $this; } -sub BEGIN -{ - # Fake the config variables that are used in case they are not defined yet - # Only really necessary to support upgrade from previous version - if ( !eval('defined($Config{ZM_LOG_DEBUG})') ) - { - no strict 'subs'; - no strict 'refs'; - my %dbgConfig = ( - ZM_LOG_LEVEL_DATABASE => 0, - ZM_LOG_LEVEL_FILE => 0, - ZM_LOG_LEVEL_SYSLOG => 0, - ZM_LOG_DEBUG => 0, - ZM_LOG_DEBUG_TARGET => "", - ZM_LOG_DEBUG_LEVEL => 1, - ZM_LOG_DEBUG_FILE => "" +sub BEGIN { +# Fake the config variables that are used in case they are not defined yet +# Only really necessary to support upgrade from previous version + if ( !eval('defined($Config{ZM_LOG_DEBUG})') ) { + no strict 'subs'; + no strict 'refs'; + my %dbgConfig = ( + ZM_LOG_LEVEL_DATABASE => 0, + ZM_LOG_LEVEL_FILE => 0, + ZM_LOG_LEVEL_SYSLOG => 0, + ZM_LOG_DEBUG => 0, + ZM_LOG_DEBUG_TARGET => "", + ZM_LOG_DEBUG_LEVEL => 1, + ZM_LOG_DEBUG_FILE => "" ); - while ( my ( $name, $value ) = each( %dbgConfig ) ) - { - *{$name} = sub { $value }; + while ( my ( $name, $value ) = each( %dbgConfig ) ) { + *{$name} = sub { $value }; + } + use strict 'subs'; + use strict 'refs'; + } +} + +sub DESTROY { + my $this = shift; + $this->terminate(); +} + +sub initialise( @ ) { + my $this = shift; + my %options = @_; + + $this->{id} = $options{id} if ( defined($options{id}) ); + + $this->{logPath} = $options{logPath} if ( defined($options{logPath}) ); + + my $tempLogFile; + $tempLogFile = $this->{logPath}."/".$this->{id}.".log"; + $tempLogFile = $options{logFile} if ( defined($options{logFile}) ); + if ( my $logFile = $this->getTargettedEnv('LOG_FILE') ) { + $tempLogFile = $logFile; + } + + my $tempLevel = INFO; + my $tempTermLevel = $this->{termLevel}; + my $tempDatabaseLevel = $this->{databaseLevel}; + my $tempFileLevel = $this->{fileLevel}; + my $tempSyslogLevel = $this->{syslogLevel}; + + $tempTermLevel = $options{termLevel} if ( defined($options{termLevel}) ); + if ( defined($options{databaseLevel}) ) { + $tempDatabaseLevel = $options{databaseLevel}; + } else { + $tempDatabaseLevel = $Config{ZM_LOG_LEVEL_DATABASE}; + } + if ( defined($options{fileLevel}) ) { + $tempFileLevel = $options{fileLevel}; + } else { + $tempFileLevel = $Config{ZM_LOG_LEVEL_FILE}; + } + if ( defined($options{syslogLevel}) ) { + $tempSyslogLevel = $options{syslogLevel}; + } else { + $tempSyslogLevel = $Config{ZM_LOG_LEVEL_SYSLOG}; + } + + if ( defined($ENV{'LOG_PRINT'}) ) { + $tempTermLevel = $ENV{'LOG_PRINT'}? DEBUG : NOLOG; + } + + my $level; + $tempLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL')) ); + + $tempTermLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_TERM')) ); + $tempDatabaseLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_DATABASE')) ); + $tempFileLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_FILE')) ); + $tempSyslogLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_SYSLOG')) ); + + if ( $Config{ZM_LOG_DEBUG} ) { + foreach my $target ( split( /\|/, $Config{ZM_LOG_DEBUG_TARGET} ) ) { + if ( $target eq $this->{id} + || $target eq "_".$this->{id} + || $target eq $this->{idRoot} + || $target eq "_".$this->{idRoot} + || $target eq "" + ) { + if ( $Config{ZM_LOG_DEBUG_LEVEL} > NOLOG ) { + $tempLevel = $this->limit( $Config{ZM_LOG_DEBUG_LEVEL} ); + if ( $Config{ZM_LOG_DEBUG_FILE} ne "" ) { + $tempLogFile = $Config{ZM_LOG_DEBUG_FILE}; + $tempFileLevel = $tempLevel; + } } - use strict 'subs'; - use strict 'refs'; + } } + } + + $this->logFile( $tempLogFile ); + + $this->termLevel( $tempTermLevel ); + $this->databaseLevel( $tempDatabaseLevel ); + $this->fileLevel( $tempFileLevel ); + $this->syslogLevel( $tempSyslogLevel ); + + $this->level( $tempLevel ); + + $this->{trace} = $options{trace} if ( defined($options{trace}) ); + + $this->{autoFlush} = $ENV{'LOG_FLUSH'}?1:0 if ( defined($ENV{'LOG_FLUSH'}) ); + + $this->{initialised} = !undef; + + Debug( "LogOpts: level=".$codes{$this->{level}} + ."/".$codes{$this->{effectiveLevel}} + .", screen=".$codes{$this->{termLevel}} + .", database=".$codes{$this->{databaseLevel}} + .", logfile=".$codes{$this->{fileLevel}} + ."->".$this->{logFile} + .", syslog=".$codes{$this->{syslogLevel}} + ); } -sub DESTROY -{ - my $this = shift; - $this->terminate(); +sub terminate { + my $this = shift; + return unless ( $this->{initialised} ); + $this->syslogLevel( NOLOG ); + $this->fileLevel( NOLOG ); + $this->databaseLevel( NOLOG ); + $this->termLevel( NOLOG ); } -sub initialise( @ ) -{ - my $this = shift; - my %options = @_; +sub reinitialise { + my $this = shift; - $this->{id} = $options{id} if ( defined($options{id}) ); + return unless ( $this->{initialised} ); - $this->{logPath} = $options{logPath} if ( defined($options{logPath}) ); +# Bit of a nasty hack to reopen connections to log files and the DB + my $syslogLevel = $this->syslogLevel(); + $this->syslogLevel( NOLOG ); + my $logfileLevel = $this->fileLevel(); + $this->fileLevel( NOLOG ); + my $databaseLevel = $this->databaseLevel(); + $this->databaseLevel( NOLOG ); + my $screenLevel = $this->termLevel(); + $this->termLevel( NOLOG ); - my $tempLogFile; - $tempLogFile = $this->{logPath}."/".$this->{id}.".log"; - $tempLogFile = $options{logFile} if ( defined($options{logFile}) ); - if ( my $logFile = $this->getTargettedEnv('LOG_FILE') ) - { - $tempLogFile = $logFile; - } - - my $tempLevel = INFO; - my $tempTermLevel = $this->{termLevel}; - my $tempDatabaseLevel = $this->{databaseLevel}; - my $tempFileLevel = $this->{fileLevel}; - my $tempSyslogLevel = $this->{syslogLevel}; - - $tempTermLevel = $options{termLevel} if ( defined($options{termLevel}) ); - if ( defined($options{databaseLevel}) ) - { - $tempDatabaseLevel = $options{databaseLevel}; - } - else - { - $tempDatabaseLevel = $Config{ZM_LOG_LEVEL_DATABASE}; - } - if ( defined($options{fileLevel}) ) - { - $tempFileLevel = $options{fileLevel}; - } - else - { - $tempFileLevel = $Config{ZM_LOG_LEVEL_FILE}; - } - if ( defined($options{syslogLevel}) ) - { - $tempSyslogLevel = $options{syslogLevel}; - } - else - { - $tempSyslogLevel = $Config{ZM_LOG_LEVEL_SYSLOG}; - } - - if ( defined($ENV{'LOG_PRINT'}) ) - { - $tempTermLevel = $ENV{'LOG_PRINT'}? DEBUG : NOLOG; - } - - my $level; - $tempLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL')) ); - - $tempTermLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_TERM')) ); - $tempDatabaseLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_DATABASE')) ); - $tempFileLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_FILE')) ); - $tempSyslogLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_SYSLOG')) ); - - if ( $Config{ZM_LOG_DEBUG} ) - { - foreach my $target ( split( /\|/, $Config{ZM_LOG_DEBUG_TARGET} ) ) - { - if ( $target eq $this->{id} - || $target eq "_".$this->{id} - || $target eq $this->{idRoot} - || $target eq "_".$this->{idRoot} - || $target eq "" - ) - { - if ( $Config{ZM_LOG_DEBUG_LEVEL} > NOLOG ) - { - $tempLevel = $this->limit( $Config{ZM_LOG_DEBUG_LEVEL} ); - if ( $Config{ZM_LOG_DEBUG_FILE} ne "" ) - { - $tempLogFile = $Config{ZM_LOG_DEBUG_FILE}; - $tempFileLevel = $tempLevel; - } - } - } - } - } - - $this->logFile( $tempLogFile ); - - $this->termLevel( $tempTermLevel ); - $this->databaseLevel( $tempDatabaseLevel ); - $this->fileLevel( $tempFileLevel ); - $this->syslogLevel( $tempSyslogLevel ); - - $this->level( $tempLevel ); - - $this->{trace} = $options{trace} if ( defined($options{trace}) ); - - $this->{autoFlush} = $ENV{'LOG_FLUSH'}?1:0 if ( defined($ENV{'LOG_FLUSH'}) ); - - $this->{initialised} = !undef; - - Debug( "LogOpts: level=".$codes{$this->{level}} - ."/".$codes{$this->{effectiveLevel}} - .", screen=".$codes{$this->{termLevel}} - .", database=".$codes{$this->{databaseLevel}} - .", logfile=".$codes{$this->{fileLevel}} - ."->".$this->{logFile} - .", syslog=".$codes{$this->{syslogLevel}} - ); + $this->syslogLevel( $syslogLevel ) if ( $syslogLevel > NOLOG ); + $this->fileLevel( $logfileLevel ) if ( $logfileLevel > NOLOG ); + $this->databaseLevel( $databaseLevel ) if ( $databaseLevel > NOLOG ); + $this->databaseLevel( $databaseLevel ) if ( $databaseLevel > NOLOG ); } -sub terminate -{ - my $this = shift; - return unless ( $this->{initialised} ); - $this->syslogLevel( NOLOG ); - $this->fileLevel( NOLOG ); - $this->databaseLevel( NOLOG ); - $this->termLevel( NOLOG ); +sub limit { + my $this = shift; + my $level = shift; + return( DEBUG ) if ( $level > DEBUG ); + return( NOLOG ) if ( $level < NOLOG ); + return( $level ); } -sub reinitialise -{ - my $this = shift; - - return unless ( $this->{initialised} ); - - # Bit of a nasty hack to reopen connections to log files and the DB - my $syslogLevel = $this->syslogLevel(); - $this->syslogLevel( NOLOG ); - my $logfileLevel = $this->fileLevel(); - $this->fileLevel( NOLOG ); - my $databaseLevel = $this->databaseLevel(); - $this->databaseLevel( NOLOG ); - my $screenLevel = $this->termLevel(); - $this->termLevel( NOLOG ); - - $this->syslogLevel( $syslogLevel ) if ( $syslogLevel > NOLOG ); - $this->fileLevel( $logfileLevel ) if ( $logfileLevel > NOLOG ); - $this->databaseLevel( $databaseLevel ) if ( $databaseLevel > NOLOG ); - $this->databaseLevel( $databaseLevel ) if ( $databaseLevel > NOLOG ); -} - -sub limit -{ - my $this = shift; - my $level = shift; - return( DEBUG ) if ( $level > DEBUG ); - return( NOLOG ) if ( $level < NOLOG ); - return( $level ); -} - -sub getTargettedEnv -{ - my $this = shift; - my $name = shift; - my $envName = $name."_".$this->{id}; - my $value; +sub getTargettedEnv { + my $this = shift; + my $name = shift; + my $envName = $name."_".$this->{id}; + my $value; + $value = $ENV{$envName} if ( defined($ENV{$envName}) ); + if ( !defined($value) && $this->{id} ne $this->{idRoot} ) { + $envName = $name."_".$this->{idRoot}; $value = $ENV{$envName} if ( defined($ENV{$envName}) ); - if ( !defined($value) && $this->{id} ne $this->{idRoot} ) - { - $envName = $name."_".$this->{idRoot}; - $value = $ENV{$envName} if ( defined($ENV{$envName}) ); - } - if ( !defined($value) ) - { - $value = $ENV{$name} if ( defined($ENV{$name}) ); - } - if ( defined($value) ) - { - ( $value ) = $value =~ m/(.*)/; - } - return( $value ); + } + if ( !defined($value) ) { + $value = $ENV{$name} if ( defined($ENV{$name}) ); + } + if ( defined($value) ) { + ( $value ) = $value =~ m/(.*)/; + } + return( $value ); } -sub fetch -{ - if ( !$logger ) - { - $logger = ZoneMinder::Logger->new(); - $logger->initialise( 'syslogLevel'=>INFO, 'databaseLevel'=>INFO ); - } - return( $logger ); +sub fetch { + if ( !$logger ) { + $logger = ZoneMinder::Logger->new(); + $logger->initialise( 'syslogLevel'=>INFO, 'databaseLevel'=>INFO ); + } + return( $logger ); } -sub id -{ - my $this = shift; - my $id = shift; - if ( defined($id) && $this->{id} ne $id ) - { - # Remove whitespace - $id =~ s/\S//g; - # Replace non-alphanum with underscore - $id =~ s/[^a-zA-Z_]/_/g; +sub id { + my $this = shift; + my $id = shift; + if ( defined($id) && $this->{id} ne $id ) { +# Remove whitespace + $id =~ s/\S//g; +# Replace non-alphanum with underscore + $id =~ s/[^a-zA-Z_]/_/g; - if ( $this->{id} ne $id ) - { - $this->{id} = $this->{idRoot} = $id; - if ( $id =~ /^([^_]+)_(.+)$/ ) - { - $this->{idRoot} = $1; - $this->{idArgs} = $2; + if ( $this->{id} ne $id ) { + $this->{id} = $this->{idRoot} = $id; + if ( $id =~ /^([^_]+)_(.+)$/ ) { + $this->{idRoot} = $1; + $this->{idArgs} = $2; + } + } + } + return( $this->{id} ); +} + +sub level { + my $this = shift; + my $level = shift; + if ( defined($level) ) { + $this->{level} = $this->limit( $level ); + $this->{effectiveLevel} = NOLOG; + $this->{effectiveLevel} = $this->{termLevel} if ( $this->{termLevel} > $this->{effectiveLevel} ); + $this->{effectiveLevel} = $this->{databaseLevel} if ( $this->{databaseLevel} > $this->{effectiveLevel} ); + $this->{effectiveLevel} = $this->{fileLevel} if ( $this->{fileLevel} > $this->{effectiveLevel} ); + $this->{effectiveLevel} = $this->{syslogLevel} if ( $this->{syslogLevel} > $this->{level} ); + $this->{effectiveLevel} = $this->{level} if ( $this->{effectiveLevel} > $this->{level} ); + } + return( $this->{level} ); +} + +sub debugOn { + my $this = shift; + return( $this->{effectiveLevel} >= DEBUG ); +} + +sub trace { + my $this = shift; + $this->{trace} = $_[0] if ( @_ ); + return( $this->{trace} ); +} + +sub termLevel { + my $this = shift; + my $termLevel = shift; + if ( defined($termLevel) ) { + $termLevel = NOLOG if ( !$this->{hasTerm} ); + $termLevel = $this->limit( $termLevel ); + if ( $this->{termLevel} != $termLevel ) { + $this->{termLevel} = $termLevel; + } + } + return( $this->{termLevel} ); +} + +sub databaseLevel { + my $this = shift; + my $databaseLevel = shift; + if ( defined($databaseLevel) ) { + $databaseLevel = $this->limit( $databaseLevel ); + if ( $this->{databaseLevel} != $databaseLevel ) { + if ( $databaseLevel > NOLOG && $this->{databaseLevel} <= NOLOG ) { + if ( !$this->{dbh} ) { + my $socket; + my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); + + if ( defined($portOrSocket) ) { + if ( $portOrSocket =~ /^\// ) { + $socket = ";mysql_socket=".$portOrSocket; + } else { + $socket = ";host=".$host.";port=".$portOrSocket; } + } else { + $socket = ";host=".$Config{ZM_DB_HOST}; + } + $this->{dbh} = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} + .$socket + , $Config{ZM_DB_USER} + , $Config{ZM_DB_PASS} + ); + if ( !$this->{dbh} ) { + $databaseLevel = NOLOG; + Error( "Unable to write log entries to DB, can't connect to database '" + .$Config{ZM_DB_NAME} + ."' on host '" + .$Config{ZM_DB_HOST} + ."'" + ); + } else { + $this->{dbh}->{AutoCommit} = 1; + Fatal( "Can't set AutoCommit on in database connection" ) + unless( $this->{dbh}->{AutoCommit} ); + $this->{dbh}->{mysql_auto_reconnect} = 1; + Fatal( "Can't set mysql_auto_reconnect on in database connection" ) + unless( $this->{dbh}->{mysql_auto_reconnect} ); + $this->{dbh}->trace( 0 ); + } } - } - return( $this->{id} ); -} - -sub level -{ - my $this = shift; - my $level = shift; - if ( defined($level) ) - { - $this->{level} = $this->limit( $level ); - $this->{effectiveLevel} = NOLOG; - $this->{effectiveLevel} = $this->{termLevel} if ( $this->{termLevel} > $this->{effectiveLevel} ); - $this->{effectiveLevel} = $this->{databaseLevel} if ( $this->{databaseLevel} > $this->{effectiveLevel} ); - $this->{effectiveLevel} = $this->{fileLevel} if ( $this->{fileLevel} > $this->{effectiveLevel} ); - $this->{effectiveLevel} = $this->{syslogLevel} if ( $this->{syslogLevel} > $this->{level} ); - $this->{effectiveLevel} = $this->{level} if ( $this->{effectiveLevel} > $this->{level} ); - } - return( $this->{level} ); -} - -sub debugOn -{ - my $this = shift; - return( $this->{effectiveLevel} >= DEBUG ); -} - -sub trace -{ - my $this = shift; - $this->{trace} = $_[0] if ( @_ ); - return( $this->{trace} ); -} - -sub termLevel -{ - my $this = shift; - my $termLevel = shift; - if ( defined($termLevel) ) - { - $termLevel = NOLOG if ( !$this->{hasTerm} ); - $termLevel = $this->limit( $termLevel ); - if ( $this->{termLevel} != $termLevel ) - { - $this->{termLevel} = $termLevel; + } elsif ( $databaseLevel <= NOLOG && $this->{databaseLevel} > NOLOG ) { + if ( $this->{dbh} ) { + $this->{dbh}->disconnect(); + undef($this->{dbh}); } + } + $this->{databaseLevel} = $databaseLevel; } - return( $this->{termLevel} ); + } + return( $this->{databaseLevel} ); } -sub databaseLevel -{ - my $this = shift; - my $databaseLevel = shift; - if ( defined($databaseLevel) ) - { - $databaseLevel = $this->limit( $databaseLevel ); - if ( $this->{databaseLevel} != $databaseLevel ) - { - if ( $databaseLevel > NOLOG && $this->{databaseLevel} <= NOLOG ) - { - if ( !$this->{dbh} ) - { - my ( $host, $port ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); - - if ( defined($port) ) - { - $this->{dbh} = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .";host=".$host - .";port=".$port - , $Config{ZM_DB_USER} - , $Config{ZM_DB_PASS} - ); - } - else - { - $this->{dbh} = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .";host=".$Config{ZM_DB_HOST} - , $Config{ZM_DB_USER} - , $Config{ZM_DB_PASS} - ); - } - if ( !$this->{dbh} ) - { - $databaseLevel = NOLOG; - Error( "Unable to write log entries to DB, can't connect to database '" - .$Config{ZM_DB_NAME} - ."' on host '" - .$Config{ZM_DB_HOST} - ."'" - ); - } - else - { - $this->{dbh}->{AutoCommit} = 1; - Fatal( "Can't set AutoCommit on in database connection" ) - unless( $this->{dbh}->{AutoCommit} ); - $this->{dbh}->{mysql_auto_reconnect} = 1; - Fatal( "Can't set mysql_auto_reconnect on in database connection" ) - unless( $this->{dbh}->{mysql_auto_reconnect} ); - $this->{dbh}->trace( 0 ); - } - } - } - elsif ( $databaseLevel <= NOLOG && $this->{databaseLevel} > NOLOG ) - { - if ( $this->{dbh} ) - { - $this->{dbh}->disconnect(); - undef($this->{dbh}); - } - } - $this->{databaseLevel} = $databaseLevel; - } +sub fileLevel { + my $this = shift; + my $fileLevel = shift; + if ( defined($fileLevel) ) { + $fileLevel = $this->limit($fileLevel); + if ( $this->{fileLevel} != $fileLevel ) { + $this->closeFile() if ( $this->{fileLevel} > NOLOG ); + $this->{fileLevel} = $fileLevel; + $this->openFile() if ( $this->{fileLevel} > NOLOG ); } - return( $this->{databaseLevel} ); + } + return( $this->{fileLevel} ); } -sub fileLevel -{ - my $this = shift; - my $fileLevel = shift; - if ( defined($fileLevel) ) - { - $fileLevel = $this->limit($fileLevel); - if ( $this->{fileLevel} != $fileLevel ) - { - $this->closeFile() if ( $this->{fileLevel} > NOLOG ); - $this->{fileLevel} = $fileLevel; - $this->openFile() if ( $this->{fileLevel} > NOLOG ); - } +sub syslogLevel { + my $this = shift; + my $syslogLevel = shift; + if ( defined($syslogLevel) ) { + $syslogLevel = $this->limit($syslogLevel); + if ( $this->{syslogLevel} != $syslogLevel ) { + $this->closeSyslog() if ( $syslogLevel <= NOLOG && $this->{syslogLevel} > NOLOG ); + $this->openSyslog() if ( $syslogLevel > NOLOG && $this->{syslogLevel} <= NOLOG ); + $this->{syslogLevel} = $syslogLevel; } - return( $this->{fileLevel} ); + } + return( $this->{syslogLevel} ); } -sub syslogLevel -{ - my $this = shift; - my $syslogLevel = shift; - if ( defined($syslogLevel) ) - { - $syslogLevel = $this->limit($syslogLevel); - if ( $this->{syslogLevel} != $syslogLevel ) - { - $this->closeSyslog() if ( $syslogLevel <= NOLOG && $this->{syslogLevel} > NOLOG ); - $this->openSyslog() if ( $syslogLevel > NOLOG && $this->{syslogLevel} <= NOLOG ); - $this->{syslogLevel} = $syslogLevel; - } +sub openSyslog { + my $this = shift; + openlog( $this->{id}, "pid", "local1" ); +} + +sub closeSyslog { + my $this = shift; +#closelog(); +} + +sub logFile { + my $this = shift; + my $logFile = shift; + if ( $logFile =~ /^(.+)\+$/ ) { + $this->{logFile} = $1.'.'.$$; + } else { + $this->{logFile} = $logFile; + } +} + +sub openFile { + my $this = shift; + if ( open( $LOGFILE, ">>", $this->{logFile} ) ) { + $LOGFILE->autoflush() if ( $this->{autoFlush} ); + + my $webUid = (getpwnam( $Config{ZM_WEB_USER} ))[2]; + my $webGid = (getgrnam( $Config{ZM_WEB_GROUP} ))[2]; + if ( $> == 0 ) { + chown( $webUid, $webGid, $this->{logFile} ) + or Fatal( "Can't change permissions on log file '" + .$this->{logFile}."': $!" + ) } - return( $this->{syslogLevel} ); + } else { + $this->fileLevel( NOLOG ); + Error( "Can't open log file '".$this->{logFile}."': $!" ); + } } -sub openSyslog -{ - my $this = shift; - openlog( $this->{id}, "pid", "local1" ); +sub closeFile { + my $this = shift; + close( $LOGFILE ) if ( fileno($LOGFILE) ); } -sub closeSyslog -{ - my $this = shift; - #closelog(); -} +sub logPrint { + my $this = shift; + my $level = shift; + my $string = shift; -sub logFile -{ - my $this = shift; - my $logFile = shift; - if ( $logFile =~ /^(.+)\+$/ ) - { - $this->{logFile} = $1.'.'.$$; - } - else - { - $this->{logFile} = $logFile; - } -} + if ( $level <= $this->{effectiveLevel} ) { + $string =~ s/[\r\n]+$//g; -sub openFile -{ - my $this = shift; - if ( open( $LOGFILE, ">>", $this->{logFile} ) ) - { - $LOGFILE->autoflush() if ( $this->{autoFlush} ); + my $code = $codes{$level}; - my $webUid = (getpwnam( $Config{ZM_WEB_USER} ))[2]; - my $webGid = (getgrnam( $Config{ZM_WEB_GROUP} ))[2]; - if ( $> == 0 ) - { - chown( $webUid, $webGid, $this->{logFile} ) - or Fatal( "Can't change permissions on log file '" - .$this->{logFile}."': $!" - ) - } - } - else - { - $this->fileLevel( NOLOG ); - Error( "Can't open log file '".$this->{logFile}."': $!" ); - } -} - -sub closeFile -{ - my $this = shift; - close( $LOGFILE ) if ( fileno($LOGFILE) ); -} - -sub logPrint -{ - my $this = shift; - my $level = shift; - my $string = shift; - - if ( $level <= $this->{effectiveLevel} ) - { - $string =~ s/[\r\n]+$//g; - - my $code = $codes{$level}; - - my ($seconds, $microseconds) = gettimeofday(); - my $message = sprintf( - "%s.%06d %s[%d].%s [%s]" - , strftime( "%x %H:%M:%S" - ,localtime( $seconds ) - ) - , $microseconds - , $this->{id} - , $$ - , $code - , $string + my ($seconds, $microseconds) = gettimeofday(); + my $message = sprintf( + "%s.%06d %s[%d].%s [%s]" + , strftime( "%x %H:%M:%S" + ,localtime( $seconds ) + ) + , $microseconds + , $this->{id} + , $$ + , $code + , $string ); - if ( $this->{trace} ) - { - $message = Carp::shortmess( $message ); - } - else - { - $message = $message."\n"; - } - syslog( $priorities{$level}, $code." [%s]", $string ) - if ( $level <= $this->{syslogLevel} ); - print( $LOGFILE $message ) if ( $level <= $this->{fileLevel} ); - if ( $level <= $this->{databaseLevel} ) - { - my $sql = "insert into Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, NULL )"; - $this->{sth} = $this->{dbh}->prepare_cached( $sql ); - if ( !$this->{sth} ) - { - $this->{databaseLevel} = NOLOG; - Fatal( "Can't prepare log entry '$sql': ".$this->{dbh}->errstr() ); - } - my $res = $this->{sth}->execute( $seconds+($microseconds/1000000.0) - , $this->{id} - , $$ - , $level - , $code - , $string - , $this->{fileName} - ); - if ( !$res ) - { - $this->{databaseLevel} = NOLOG; - Fatal( "Can't execute log entry '$sql': ".$this->{sth}->errstr() ); - } - } - print( STDERR $message ) if ( $level <= $this->{termLevel} ); + if ( $this->{trace} ) { + $message = Carp::shortmess( $message ); + } else { + $message = $message."\n"; } + if ( $level <= $this->{syslogLevel} ) { + syslog( $priorities{$level}, $code." [%s]", $string ); + } + print( $LOGFILE $message ) if ( $level <= $this->{fileLevel} ); + if ( $level <= $this->{databaseLevel} ) { + my $sql = "insert into Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, NULL )"; + $this->{sth} = $this->{dbh}->prepare_cached( $sql ); + if ( !$this->{sth} ) { + $this->{databaseLevel} = NOLOG; + Fatal( "Can't prepare log entry '$sql': ".$this->{dbh}->errstr() ); + } + my $res = $this->{sth}->execute( $seconds+($microseconds/1000000.0) + , $this->{id} + , $$ + , $level + , $code + , $string + , $this->{fileName} + ); + if ( !$res ) { + $this->{databaseLevel} = NOLOG; + Fatal( "Can't execute log entry '$sql': ".$this->{sth}->errstr() ); + } + } + print( STDERR $message ) if ( $level <= $this->{termLevel} ); + } } -sub logInit( ;@ ) -{ - my %options = @_ ? @_ : (); - $logger = ZoneMinder::Logger->new() if ( !$logger ); - $logger->initialise( %options ); +sub logInit( ;@ ) { + my %options = @_ ? @_ : (); + $logger = ZoneMinder::Logger->new() if ( !$logger ); + $logger->initialise( %options ); } -sub logReinit -{ - fetch()->reinitialise(); +sub logReinit { + fetch()->reinitialise(); } -sub logTerm -{ - return unless ( $logger ); - $logger->terminate(); - $logger = undef; +sub logTerm { + return unless ( $logger ); + $logger->terminate(); + $logger = undef; } -sub logHupHandler -{ - my $savedErrno = $!; - return unless( $logger ); - fetch()->reinitialise(); - logSetSignal(); - $! = $savedErrno; +sub logHupHandler { + my $savedErrno = $!; + return unless( $logger ); + fetch()->reinitialise(); + logSetSignal(); + $! = $savedErrno; } -sub logSetSignal -{ - $SIG{HUP} = \&logHupHandler; +sub logSetSignal { + $SIG{HUP} = \&logHupHandler; } -sub logClearSignal -{ - $SIG{HUP} = 'DEFAULT'; +sub logClearSignal { + $SIG{HUP} = 'DEFAULT'; } -sub logLevel -{ - return( fetch()->level( @_ ) ); +sub logLevel { + return( fetch()->level( @_ ) ); } -sub logDebugging -{ - return( fetch()->debugOn() ); +sub logDebugging { + return( fetch()->debugOn() ); } -sub logTermLevel -{ - return( fetch()->termLevel( @_ ) ); +sub logTermLevel { + return( fetch()->termLevel( @_ ) ); } -sub logDatabaseLevel -{ - return( fetch()->databaseLevel( @_ ) ); +sub logDatabaseLevel { + return( fetch()->databaseLevel( @_ ) ); } -sub logFileLevel -{ - return( fetch()->fileLevel( @_ ) ); +sub logFileLevel { + return( fetch()->fileLevel( @_ ) ); } -sub logSyslogLevel -{ - return( fetch()->syslogLevel( @_ ) ); +sub logSyslogLevel { + return( fetch()->syslogLevel( @_ ) ); } -sub Mark -{ - my $level = shift; - $level = DEBUG unless( defined($level) ); - my $tag = "Mark"; - fetch()->logPrint( $level, $tag ); +sub Mark { + my $level = shift; + $level = DEBUG unless( defined($level) ); + my $tag = "Mark"; + fetch()->logPrint( $level, $tag ); } -sub Dump -{ - my $var = shift; - my $label = shift; - $label = "VAR" unless( defined($label) ); - fetch()->logPrint( DEBUG, Data::Dumper->Dump( [ $var ], [ $label ] ) ); +sub Dump { + my $var = shift; + my $label = shift; + $label = "VAR" unless( defined($label) ); + fetch()->logPrint( DEBUG, Data::Dumper->Dump( [ $var ], [ $label ] ) ); } -sub Debug( @ ) -{ - fetch()->logPrint( DEBUG, @_ ); +sub Debug( @ ) { + fetch()->logPrint( DEBUG, @_ ); } -sub Info( @ ) -{ - fetch()->logPrint( INFO, @_ ); +sub Info( @ ) { + fetch()->logPrint( INFO, @_ ); } -sub Warning( @ ) -{ - fetch()->logPrint( WARNING, @_ ); +sub Warning( @ ) { + fetch()->logPrint( WARNING, @_ ); } -sub Error( @ ) -{ - fetch()->logPrint( ERROR, @_ ); +sub Error( @ ) { + fetch()->logPrint( ERROR, @_ ); } -sub Fatal( @ ) -{ - fetch()->logPrint( FATAL, @_ ); - exit( -1 ); +sub Fatal( @ ) { + fetch()->logPrint( FATAL, @_ ); + exit( -1 ); } -sub Panic( @ ) -{ - fetch()->logPrint( PANIC, @_ ); - confess( $_[0] ); +sub Panic( @ ) { + fetch()->logPrint( PANIC, @_ ); + confess( $_[0] ); } 1; @@ -792,17 +692,17 @@ ZoneMinder::Logger - ZoneMinder Logger module =head1 SYNOPSIS - use ZoneMinder::Logger; - use ZoneMinder::Logger qw(:all); +use ZoneMinder::Logger; +use ZoneMinder::Logger qw(:all); - logInit( "myproc", DEBUG ); +logInit( "myproc", DEBUG ); - Debug( "This is what is happening" ); - Info( "Something interesting is happening" ); - Warning( "Something might be going wrong." ); - Error( "Something has gone wrong!!" ); - Fatal( "Something has gone badly wrong, gotta stop!!" ); - Panic( "Something fundamental has gone wrong, die with stack trace ); +Debug( "This is what is happening" ); +Info( "Something interesting is happening" ); +Warning( "Something might be going wrong." ); +Error( "Something has gone wrong!!" ); +Fatal( "Something has gone badly wrong, gotta stop!!" ); +Panic( "Something fundamental has gone wrong, die with stack trace ); =head1 DESCRIPTION @@ -833,15 +733,15 @@ compulsory arguments are $id which must be a string that will identify debug coming from this script in mixed logs. Other options may be provided as below, - Option Default Description - --------- --------- ----------- - level INFO The initial debug level which defines which statements are output and which are ignored - trace 0 Whether to use the Carp::shortmess format in debug statements to identify where the debug was emitted from - termLevel NOLOG At what level debug is written to terminal standard error, 0 is no, 1 is yes, 2 is write only if terminal - databaseLevel INFO At what level debug is written to the Log table in the database; - fileLevel NOLOG At what level debug is written to a log file of the format of .log in the standard log directory. - syslogLevel INFO At what level debug is written to syslog. - +Option Default Description +--------- --------- ----------- +level INFO The initial debug level which defines which statements are output and which are ignored +trace 0 Whether to use the Carp::shortmess format in debug statements to identify where the debug was emitted from +termLevel NOLOG At what level debug is written to terminal standard error, 0 is no, 1 is yes, 2 is write only if terminal +databaseLevel INFO At what level debug is written to the Log table in the database; +fileLevel NOLOG At what level debug is written to a log file of the format of .log in the standard log directory. +syslogLevel INFO At what level debug is written to syslog. + To disable any of these action entirely set to NOLOG =item logTerm (); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in index 8e7b9228e..91f2d0b54 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -41,48 +41,48 @@ our @ISA = qw(Exporter ZoneMinder::Base); # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( - 'constants' => [ qw( - STATE_IDLE - STATE_PREALARM - STATE_ALARM - STATE_ALERT - STATE_TAPE - ACTION_GET - ACTION_SET - ACTION_RELOAD - ACTION_SUSPEND - ACTION_RESUME - TRIGGER_CANCEL - TRIGGER_ON - TRIGGER_OFF + constants => [ qw( + STATE_IDLE + STATE_PREALARM + STATE_ALARM + STATE_ALERT + STATE_TAPE + ACTION_GET + ACTION_SET + ACTION_RELOAD + ACTION_SUSPEND + ACTION_RESUME + TRIGGER_CANCEL + TRIGGER_ON + TRIGGER_OFF + ) ], + functions => [ qw( + zmMemVerify + zmMemInvalidate + zmMemRead + zmMemWrite + zmMemTidy + zmGetMonitorState + zmGetAlarmLocation + zmIsAlarmed + zmInAlarm + zmHasAlarmed + zmGetLastEvent + zmGetLastWriteTime + zmGetLastReadTime + zmMonitorEnable + zmMonitorDisable + zmMonitorSuspend + zmMonitorResume + zmTriggerEventOn + zmTriggerEventOff + zmTriggerEventCancel + zmTriggerShowtext ) ], - 'functions' => [ qw( - zmMemVerify - zmMemInvalidate - zmMemRead - zmMemWrite - zmMemTidy - zmGetMonitorState - zmGetAlarmLocation - zmIsAlarmed - zmInAlarm - zmHasAlarmed - zmGetLastEvent - zmGetLastWriteTime - zmGetLastReadTime - zmMonitorEnable - zmMonitorDisable - zmMonitorSuspend - zmMonitorResume - zmTriggerEventOn - zmTriggerEventOff - zmTriggerEventCancel - zmTriggerShowtext - ) ], -); + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); +our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); our @EXPORT = qw(); @@ -115,15 +115,13 @@ use constant TRIGGER_OFF => 2; use Storable qw( freeze thaw ); -if ( "@ENABLE_MMAP@" eq 'yes' ) # 'yes' if memory is mmapped -{ - require ZoneMinder::Memory::Mapped; - ZoneMinder::Memory::Mapped->import(); -} -else -{ - require ZoneMinder::Memory::Shared; - ZoneMinder::Memory::Shared->import(); +if ( '@ENABLE_MMAP@' eq 'yes' ) { +# 'yes' if memory is mmapped + require ZoneMinder::Memory::Mapped; + ZoneMinder::Memory::Mapped->import(); +} else { + require ZoneMinder::Memory::Shared; + ZoneMinder::Memory::Shared->import(); } # Detaint our environment @@ -142,608 +140,489 @@ our $arch = 32 + 32*( qx(uname -m) =~ /64/ ); our $native = $arch/8; our $mem_seq = 0; -our $mem_data = -{ - "shared_data" => { "type"=>"SharedData", "seq"=>$mem_seq++, "contents"=> { - "size" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_write_index" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_read_index" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "state" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_event" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "action" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "brightness" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "hue" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "colour" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "contrast" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "alarm_x" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "alarm_y" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "valid" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "active" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "signal" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "format" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "imagesize" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "epadding1" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "epadding2" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_write_time" => { "type"=>"time_t64", "seq"=>$mem_seq++ }, - "last_read_time" => { "type"=>"time_t64", "seq"=>$mem_seq++ }, - "control_state" => { "type"=>"uint8[256]", "seq"=>$mem_seq++ }, - } - }, - "trigger_data" => { "type"=>"TriggerData", "seq"=>$mem_seq++, "contents"=> { - "size" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_state" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_score" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "padding" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_cause" => { "type"=>"int8[32]", "seq"=>$mem_seq++ }, - "trigger_text" => { "type"=>"int8[256]", "seq"=>$mem_seq++ }, - "trigger_showtext" => { "type"=>"int8[256]", "seq"=>$mem_seq++ }, - } - }, - "end" => { "seq"=>$mem_seq++, "size"=> 0 } +our $mem_data = { + shared_data => { type=>'SharedData', seq=>$mem_seq++, contents=> { + size => { type=>'uint32', seq=>$mem_seq++ }, + last_write_index => { type=>'uint32', seq=>$mem_seq++ }, + last_read_index => { type=>'uint32', seq=>$mem_seq++ }, + state => { type=>'uint32', seq=>$mem_seq++ }, + last_event => { type=>'uint32', seq=>$mem_seq++ }, + action => { type=>'uint32', seq=>$mem_seq++ }, + brightness => { type=>'int32', seq=>$mem_seq++ }, + hue => { type=>'int32', seq=>$mem_seq++ }, + colour => { type=>'int32', seq=>$mem_seq++ }, + contrast => { type=>'int32', seq=>$mem_seq++ }, + alarm_x => { type=>'int32', seq=>$mem_seq++ }, + alarm_y => { type=>'int32', seq=>$mem_seq++ }, + valid => { type=>'uint8', seq=>$mem_seq++ }, + active => { type=>'uint8', seq=>$mem_seq++ }, + signal => { type=>'uint8', seq=>$mem_seq++ }, + format => { type=>'uint8', seq=>$mem_seq++ }, + imagesize => { type=>'uint32', seq=>$mem_seq++ }, + epadding1 => { type=>'uint32', seq=>$mem_seq++ }, + epadding2 => { type=>'uint32', seq=>$mem_seq++ }, + last_write_time => { type=>'time_t64', seq=>$mem_seq++ }, + last_read_time => { type=>'time_t64', seq=>$mem_seq++ }, + control_state => { type=>'uint8[256]', seq=>$mem_seq++ }, + } + }, + trigger_data => { type=>'TriggerData', seq=>$mem_seq++, 'contents'=> { + size => { type=>'uint32', seq=>$mem_seq++ }, + trigger_state => { type=>'uint32', seq=>$mem_seq++ }, + trigger_score => { type=>'uint32', seq=>$mem_seq++ }, + padding => { type=>'uint32', seq=>$mem_seq++ }, + trigger_cause => { type=>'int8[32]', seq=>$mem_seq++ }, + trigger_text => { type=>'int8[256]', seq=>$mem_seq++ }, + trigger_showtext => { type=>'int8[256]', seq=>$mem_seq++ }, + } + }, + end => { seq=>$mem_seq++, size=>0 } }; our $mem_size = 0; -our $mem_verified = {}; -sub zmMemInit -{ - my $offset = 0; +sub zmMemInit { + my $offset = 0; - foreach my $section_data ( sort { $a->{seq} <=> $b->{seq} } values( %$mem_data ) ) - { - $section_data->{offset} = $offset; - $section_data->{align} = 0; + foreach my $section_data ( sort { $a->{seq} <=> $b->{seq} } values( %$mem_data ) ) { + $section_data->{offset} = $offset; + $section_data->{align} = 0; - if ( $section_data->{align} > 1 ) - { - my $rem = $offset % $section_data->{align}; - if ( $rem > 0 ) - { - $offset += ($section_data->{align} - $rem); - } - } - foreach my $member_data ( sort { $a->{seq} <=> $b->{seq} } values( %{$section_data->{contents}} ) ) - { - if ( $member_data->{type} eq "long" - || $member_data->{type} eq "ulong" - || $member_data->{type} eq "size_t" - ) - { - $member_data->{size} = $member_data->{align} = $native; - } - elsif( $member_data->{type} eq "int64" - || $member_data->{type} eq "uint64" - || $member_data->{type} eq "time_t64" - ) - { - $member_data->{size} = $member_data->{align} = 8; - } - elsif ( $member_data->{type} eq "int32" - || $member_data->{type} eq "uint32" - || $member_data->{type} eq "bool4" - ) - { - $member_data->{size} = $member_data->{align} = 4; - } - elsif ($member_data->{type} eq "int16" - || $member_data->{type} eq "uint16" - ) - { - $member_data->{size} = $member_data->{align} = 2; - } - elsif ( $member_data->{type} eq "int8" - || $member_data->{type} eq "uint8" - || $member_data->{type} eq "bool1" - ) - { - $member_data->{size} = $member_data->{align} = 1; - } - elsif ( $member_data->{type} =~ /^u?int8\[(\d+)\]$/ ) - { - $member_data->{size} = $1; - $member_data->{align} = 1; - } - else - { - Fatal( "Unexpected type '".$member_data->{type} - ."' found in shared data definition." - ); - } - - if ( $member_data->{align} > 1 && ($offset%$member_data->{align}) > 0 ) - { - $offset += ($member_data->{align} - ($offset%$member_data->{align})); - } - $member_data->{offset} = $offset; - $offset += $member_data->{size} - } - $section_data->{size} = $offset - $section_data->{offset}; + if ( $section_data->{align} > 1 ) { + my $rem = $offset % $section_data->{align}; + if ( $rem > 0 ) { + $offset += ($section_data->{align} - $rem); + } } + foreach my $member_data ( sort { $a->{seq} <=> $b->{seq} } values( %{$section_data->{contents}} ) ) { + if ( $member_data->{type} eq 'long' + || $member_data->{type} eq 'ulong' + || $member_data->{type} eq 'size_t' + ) { + $member_data->{size} = $member_data->{align} = $native; + } elsif ( $member_data->{type} eq 'int64' + || $member_data->{type} eq 'uint64' + || $member_data->{type} eq 'time_t64' + ) { + $member_data->{size} = $member_data->{align} = 8; + } elsif ( $member_data->{type} eq 'int32' + || $member_data->{type} eq 'uint32' + || $member_data->{type} eq 'bool4' + ) { + $member_data->{size} = $member_data->{align} = 4; + } elsif ($member_data->{type} eq 'int16' + || $member_data->{type} eq 'uint16' + ) { + $member_data->{size} = $member_data->{align} = 2; + } elsif ( $member_data->{type} eq 'int8' + || $member_data->{type} eq 'uint8' + || $member_data->{type} eq 'bool1' + ) { + $member_data->{size} = $member_data->{align} = 1; + } elsif ( $member_data->{type} =~ /^u?int8\[(\d+)\]$/ ) { + $member_data->{size} = $1; + $member_data->{align} = 1; + } else { + Fatal( "Unexpected type '".$member_data->{type} + ."' found in shared data definition." + ); + } - $mem_size = $offset; + if ( $member_data->{align} > 1 && ($offset%$member_data->{align}) > 0 ) { + $offset += ($member_data->{align} - ($offset%$member_data->{align})); + } + $member_data->{offset} = $offset; + $offset += $member_data->{size} + } + $section_data->{size} = $offset - $section_data->{offset}; + } + + $mem_size = $offset; } &zmMemInit(); -sub zmMemVerify -{ - my $monitor = shift; - if ( !zmMemAttach( $monitor, $mem_size ) ) - { - return( undef ); - } +sub zmMemVerify { + my $monitor = shift; + if ( !zmMemAttach( $monitor, $mem_size ) ) { + return( undef ); + } - my $mem_key = zmMemKey( $monitor ); - if ( !defined($mem_verified->{$mem_key}) ) - { - my $sd_size = zmMemRead( $monitor, "shared_data:size", 1 ); - if ( $sd_size != $mem_data->{shared_data}->{size} ) - { - if ( $sd_size ) - { - Error( "Shared data size conflict in shared_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{shared_data}->{size} - .", got " - .$sd_size - ); - } - else - { - Debug( "Shared data size conflict in shared_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{shared_data}->{size} - .", got ".$sd_size - ); - } - return( undef ); - } - my $td_size = zmMemRead( $monitor, "trigger_data:size", 1 ); - if ( $td_size != $mem_data->{trigger_data}->{size} ) - { - if ( $td_size ) - { - Error( "Shared data size conflict in trigger_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{triggger_data}->{size} - .", got " - .$td_size - ); - } - else - { - Debug( "Shared data size conflict in trigger_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{triggger_data}->{size} - .", got " - .$td_size - ); - } - return( undef ); - } - $mem_verified->{$mem_key} = !undef; - } - return( !undef ); -} - -sub zmMemRead -{ - my $monitor = shift; - my $fields = shift; - my $nocheck = shift; - - if ( !($nocheck || zmMemVerify( $monitor )) ) - { - return( undef ); - } - - if ( !ref($fields) ) - { - $fields = [ $fields ]; - } - my @values; - foreach my $field ( @$fields ) - { - my ( $section, $element ) = split( /[\/:.]/, $field ); - Fatal( "Invalid shared data selector '$field'" ) if ( !$section || !$element ); - - my $offset = $mem_data->{$section}->{contents}->{$element}->{offset}; - my $type = $mem_data->{$section}->{contents}->{$element}->{type}; - my $size = $mem_data->{$section}->{contents}->{$element}->{size}; - - my $data = zmMemGet( $monitor, $offset, $size ); - if ( !defined($data) ) - { - Error( "Unable to read '$field' from memory for monitor ".$monitor->{Id} ); - zmMemInvalidate( $monitor ); - return( undef ); - } - my $value; - if ( $type eq "long" ) - { - ( $value ) = unpack( "l!", $data ); - } - elsif ( $type eq "ulong" || $type eq "size_t" ) - { - ( $value ) = unpack( "L!", $data ); - } - elsif ( $type eq "int64" || $type eq "time_t64" ) - { - # The "q" type is only available on 64bit platforms, so use native. - ( $value ) = unpack( "l!", $data ); - } - elsif ( $type eq "uint64" ) - { - # The "q" type is only available on 64bit platforms, so use native. - ( $value ) = unpack( "L!", $data ); - } - elsif ( $type eq "int32" ) - { - ( $value ) = unpack( "l", $data ); - } - elsif ( $type eq "uint32" || $type eq "bool4" ) - { - ( $value ) = unpack( "L", $data ); - } - elsif ( $type eq "int16" ) - { - ( $value ) = unpack( "s", $data ); - } - elsif ( $type eq "uint16" ) - { - ( $value ) = unpack( "S", $data ); - } - elsif ( $type eq "int8" ) - { - ( $value ) = unpack( "c", $data ); - } - elsif ( $type eq "uint8" || $type eq "bool1" ) - { - ( $value ) = unpack( "C", $data ); - } - elsif ( $type =~ /^int8\[\d+\]$/ ) - { - ( $value ) = unpack( "Z".$size, $data ); - } - elsif ( $type =~ /^uint8\[\d+\]$/ ) - { - ( $value ) = unpack( "C".$size, $data ); - } - else - { - Fatal( "Unexpected type '".$type."' found for '".$field."'" ); - } - push( @values, $value ); - } - if ( wantarray() ) - { - return( @values ) - } - return( $values[0] ); -} - -sub zmMemInvalidate -{ - my $monitor = shift; - my $mem_key = zmMemKey($monitor); - if ( $mem_key ) - { - delete $mem_verified->{$mem_key}; - zmMemDetach( $monitor ); - } -} - -sub zmMemTidy -{ - zmMemClean(); -} - -sub zmMemWrite -{ - my $monitor = shift; - my $field_values = shift; - my $nocheck = shift; - - if ( !($nocheck || zmMemVerify( $monitor )) ) - { - return( undef ); - } - - while ( my ( $field, $value ) = each( %$field_values ) ) - { - my ( $section, $element ) = split( /[\/:.]/, $field ); - Fatal( "Invalid shared data selector '$field'" ) - if ( !$section || !$element ); - - my $offset = $mem_data->{$section}->{contents}->{$element}->{offset}; - my $type = $mem_data->{$section}->{contents}->{$element}->{type}; - my $size = $mem_data->{$section}->{contents}->{$element}->{size}; - - my $data; - if ( $type eq "long" ) - { - $data = pack( "l!", $value ); - } - elsif ( $type eq "ulong" || $type eq "size_t" ) - { - $data = pack( "L!", $value ); - } - elsif ( $type eq "int64" || $type eq "time_t64" ) - { - # The "q" type is only available on 64bit platforms, so use native. - $data = pack( "l!", $value ); - } - elsif ( $type eq "uint64" ) - { - # The "q" type is only available on 64bit platforms, so use native. - $data = pack( "L!", $value ); - } - elsif ( $type eq "int32" ) - { - $data = pack( "l", $value ); - } - elsif ( $type eq "uint32" || $type eq "bool4" ) - { - $data = pack( "L", $value ); - } - elsif ( $type eq "int16" ) - { - $data = pack( "s", $value ); - } - elsif ( $type eq "uint16" ) - { - $data = pack( "S", $value ); - } - elsif ( $type eq "int8" ) - { - $data = pack( "c", $value ); - } - elsif ( $type eq "uint8" || $type eq "bool1" ) - { - $data = pack( "C", $value ); - } - elsif ( $type =~ /^int8\[\d+\]$/ ) - { - $data = pack( "Z".$size, $value ); - } - elsif ( $type =~ /^uint8\[\d+\]$/ ) - { - $data = pack( "C".$size, $value ); - } - else - { - Fatal( "Unexpected type '".$type."' found for '".$field."'" ); - } - - if ( !zmMemPut( $monitor, $offset, $size, $data ) ) - { - Error( "Unable to write '$value' to '$field' in memory for monitor " - .$monitor->{Id} - ); - zmMemInvalidate( $monitor ); - return( undef ); - } - } - return( !undef ); -} - -sub zmGetMonitorState -{ - my $monitor = shift; - - return( zmMemRead( $monitor, "shared_data:state" ) ); -} - -sub zmGetAlarmLocation -{ - my $monitor = shift; - - return( zmMemRead( $monitor, [ "shared_data:alarm_x", "shared_data:alarm_y" ] ) ); -} - -sub zmSetControlState -{ - my $monitor = shift; - my $control_state = shift; - - zmMemWrite( $monitor, { "shared_data:control_state" => $control_state } ); -} - -sub zmGetControlState -{ - my $monitor = shift; - - return( zmMemRead( $monitor, "shared_data:control_state" ) ); -} - -sub zmSaveControlState -{ - my $monitor = shift; - my $control_state = shift; - - zmSetControlState( $monitor, freeze( $control_state ) ); -} - -sub zmRestoreControlState -{ - my $monitor = shift; - - return( thaw( zmGetControlState( $monitor ) ) ); -} - -sub zmIsAlarmed -{ - my $monitor = shift; - - my $state = zmGetMonitorState( $monitor ); - - return( $state == STATE_ALARM ); -} - -sub zmInAlarm -{ - my $monitor = shift; - - my $state = zmGetMonitorState( $monitor ); - - return( $state == STATE_ALARM || $state == STATE_ALERT ); -} - -sub zmHasAlarmed -{ - my $monitor = shift; - my $last_event_id = shift; - - my ( $state, $last_event ) = zmMemRead( $monitor, [ "shared_data:state" - ,"shared_data:last_event" - ] - ); - - if ( $state == STATE_ALARM || $state == STATE_ALERT ) - { - return( $last_event ); - } - elsif( $last_event != $last_event_id ) - { - return( $last_event ); + my $sd_size = zmMemRead( $monitor, 'shared_data:size', 1 ); + if ( $sd_size != $mem_data->{shared_data}->{size} ) { + if ( $sd_size ) { + Error( "Shared data size conflict in shared_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{shared_data}->{size} + .", got " + .$sd_size + ); + } else { + Debug( "Shared data size conflict in shared_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{shared_data}->{size} + .", got ".$sd_size + ); } return( undef ); + } + my $td_size = zmMemRead( $monitor, 'trigger_data:size', 1 ); + if ( $td_size != $mem_data->{trigger_data}->{size} ) { + if ( $td_size ) { + Error( "Shared data size conflict in trigger_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{triggger_data}->{size} + .", got " + .$td_size + ); + } else { + Debug( "Shared data size conflict in trigger_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{triggger_data}->{size} + .", got " + .$td_size + ); + } + return( undef ); + } + if ( !zmMemRead($monitor, 'shared_data:valid',1) ) { + Error( "Shared data not valid for monitor $$monitor{Id}" ); + return( undef ); + } + + return( !undef ); } -sub zmGetLastEvent -{ - my $monitor = shift; +sub zmMemRead { + my $monitor = shift; + my $fields = shift; + my $nocheck = shift; - return( zmMemRead( $monitor, "shared_data:last_event" ) ); + if ( !($nocheck || zmMemVerify( $monitor )) ) { + return( undef ); + } + + if ( !ref($fields) ) { + $fields = [ $fields ]; + } + my @values; + foreach my $field ( @$fields ) { + my ( $section, $element ) = split( /[\/:.]/, $field ); + Fatal( "Invalid shared data selector '$field'" ) if ( !$section || !$element ); + + my $offset = $mem_data->{$section}->{contents}->{$element}->{offset}; + my $type = $mem_data->{$section}->{contents}->{$element}->{type}; + my $size = $mem_data->{$section}->{contents}->{$element}->{size}; + + my $data = zmMemGet( $monitor, $offset, $size ); + if ( !defined($data) ) { + Error( "Unable to read '$field' from memory for monitor ".$monitor->{Id} ); + zmMemInvalidate( $monitor ); + return( undef ); + } + my $value; + if ( $type eq 'long' ) { + ( $value ) = unpack( 'l!', $data ); + } elsif ( $type eq 'ulong' || $type eq 'size_t' ) { + ( $value ) = unpack( 'L!', $data ); + } elsif ( $type eq 'int64' || $type eq 'time_t64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + ( $value ) = unpack( 'l!', $data ); + } elsif ( $type eq 'uint64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + ( $value ) = unpack( 'L!', $data ); + } elsif ( $type eq 'int32' ) { + ( $value ) = unpack( 'l', $data ); + } elsif ( $type eq 'uint32' || $type eq 'bool4' ) { + ( $value ) = unpack( 'L', $data ); + } elsif ( $type eq 'int16' ) { + ( $value ) = unpack( 's', $data ); + } elsif ( $type eq 'uint16' ) { + ( $value ) = unpack( 'S', $data ); + } elsif ( $type eq 'int8' ) { + ( $value ) = unpack( 'c', $data ); + } elsif ( $type eq 'uint8' || $type eq 'bool1' ) { + ( $value ) = unpack( 'C', $data ); + } elsif ( $type =~ /^int8\[\d+\]$/ ) { + ( $value ) = unpack( 'Z'.$size, $data ); + } elsif ( $type =~ /^uint8\[\d+\]$/ ) { + ( $value ) = unpack( 'C'.$size, $data ); + } else { + Fatal( "Unexpected type '".$type."' found for '".$field."'" ); + } + push( @values, $value ); + } + if ( wantarray() ) { + return( @values ) + } + return( $values[0] ); } -sub zmGetLastWriteTime -{ - my $monitor = shift; - - return( zmMemRead( $monitor, "shared_data:last_write_time" ) ); +sub zmMemInvalidate { + my $monitor = shift; + my $mem_key = zmMemKey($monitor); + if ( $mem_key ) { + zmMemDetach( $monitor ); + } } -sub zmGetLastReadTime -{ - my $monitor = shift; - - return( zmMemRead( $monitor, "shared_data:last_read_time" ) ); +sub zmMemTidy { + zmMemClean(); } -sub zmGetMonitorActions -{ - my $monitor = shift; +sub zmMemWrite { + my $monitor = shift; + my $field_values = shift; + my $nocheck = shift; - return( zmMemRead( $monitor, "shared_data:action" ) ); + if ( !($nocheck || zmMemVerify( $monitor )) ) { + return( undef ); + } + + while ( my ( $field, $value ) = each( %$field_values ) ) { + my ( $section, $element ) = split( /[\/:.]/, $field ); + if ( !$section || !$element ) { + Fatal( "Invalid shared data selector '$field'" ); + } + + my $offset = $mem_data->{$section}->{contents}->{$element}->{offset}; + my $type = $mem_data->{$section}->{contents}->{$element}->{type}; + my $size = $mem_data->{$section}->{contents}->{$element}->{size}; + + my $data; + if ( $type eq 'long' ) { + $data = pack( 'l!', $value ); + } elsif ( $type eq 'ulong' || $type eq 'size_t' ) { + $data = pack( 'L!', $value ); + } elsif ( $type eq 'int64' || $type eq 'time_t64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + $data = pack( 'l!', $value ); + } elsif ( $type eq 'uint64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + $data = pack( 'L!', $value ); + } elsif ( $type eq 'int32' ) { + $data = pack( 'l', $value ); + } elsif ( $type eq 'uint32' || $type eq 'bool4' ) { + $data = pack( 'L', $value ); + } elsif ( $type eq 'int16' ) { + $data = pack( 's', $value ); + } elsif ( $type eq 'uint16' ) { + $data = pack( 'S', $value ); + } elsif ( $type eq 'int8' ) { + $data = pack( 'c', $value ); + } elsif ( $type eq 'uint8' || $type eq 'bool1' ) { + $data = pack( 'C', $value ); + } elsif ( $type =~ /^int8\[\d+\]$/ ) { + $data = pack( 'Z'.$size, $value ); + } elsif ( $type =~ /^uint8\[\d+\]$/ ) { + $data = pack( 'C'.$size, $value ); + } else { + Fatal( "Unexpected type \"$type\" found for \"$field\"" ); + } + + if ( !zmMemPut( $monitor, $offset, $size, $data ) ) { + Error( "Unable to write '$value' to '$field' in memory for monitor " + .$monitor->{Id} + ); + zmMemInvalidate( $monitor ); + return( undef ); + } + } + return( !undef ); } -sub zmMonitorEnable -{ - my $monitor = shift; +sub zmGetMonitorState { + my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); - $action |= ACTION_SUSPEND; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + return( zmMemRead( $monitor, 'shared_data:state' ) ); } -sub zmMonitorDisable -{ - my $monitor = shift; +sub zmGetAlarmLocation { + my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); - $action |= ACTION_RESUME; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + return( zmMemRead( $monitor, [ 'shared_data:alarm_x', 'shared_data:alarm_y' ] ) ); } -sub zmMonitorSuspend -{ - my $monitor = shift; +sub zmSetControlState { + my $monitor = shift; + my $control_state = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); - $action |= ACTION_SUSPEND; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:control_state' => $control_state } ); } -sub zmMonitorResume -{ - my $monitor = shift; +sub zmGetControlState { + my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); - $action |= ACTION_RESUME; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + return( zmMemRead( $monitor, 'shared_data:control_state' ) ); } -sub zmGetTriggerState -{ - my $monitor = shift; +sub zmSaveControlState { + my $monitor = shift; + my $control_state = shift; - return( zmMemRead( $monitor, "trigger_data:trigger_state" ) ); + zmSetControlState( $monitor, freeze( $control_state ) ); } -sub zmTriggerEventOn -{ - my $monitor = shift; - my $score = shift; - my $cause = shift; - my $text = shift; - my $showtext = shift; +sub zmRestoreControlState { + my $monitor = shift; - my $values = { - "trigger_data:trigger_score" => $score, - "trigger_data:trigger_cause" => $cause, - }; - $values->{"trigger_data:trigger_text"} = $text if ( defined($text) ); - $values->{"trigger_data:trigger_showtext"} = $showtext if ( defined($showtext) ); - $values->{"trigger_data:trigger_state"} = TRIGGER_ON; # Write state last so event not read incomplete - - zmMemWrite( $monitor, $values ); + return( thaw( zmGetControlState( $monitor ) ) ); } -sub zmTriggerEventOff -{ - my $monitor = shift; +sub zmIsAlarmed { + my $monitor = shift; - my $values = { - "trigger_data:trigger_state" => TRIGGER_OFF, - "trigger_data:trigger_score" => 0, - "trigger_data:trigger_cause" => "", - "trigger_data:trigger_text" => "", - "trigger_data:trigger_showtext" => "", - }; + my $state = zmGetMonitorState( $monitor ); - zmMemWrite( $monitor, $values ); + return( $state == STATE_ALARM ); } -sub zmTriggerEventCancel -{ - my $monitor = shift; +sub zmInAlarm { + my $monitor = shift; - my $values = { - "trigger_data:trigger_state" => TRIGGER_CANCEL, - "trigger_data:trigger_score" => 0, - "trigger_data:trigger_cause" => "", - "trigger_data:trigger_text" => "", - "trigger_data:trigger_showtext" => "", - }; + my $state = zmGetMonitorState( $monitor ); - zmMemWrite( $monitor, $values ); + return( $state == STATE_ALARM || $state == STATE_ALERT ); } -sub zmTriggerShowtext -{ - my $monitor = shift; - my $showtext = shift; +sub zmHasAlarmed { + my $monitor = shift; + my $last_event_id = shift; - my $values = { - "trigger_data:trigger_showtext" => $showtext, - }; + my ( $state, $last_event ) = zmMemRead( $monitor, [ 'shared_data:state' + ,'shared_data:last_event' + ] + ); - zmMemWrite( $monitor, $values ); + if ( $state == STATE_ALARM || $state == STATE_ALERT ) { + return( $last_event ); + } elsif( $last_event != $last_event_id ) { + return( $last_event ); + } + return( undef ); +} + +sub zmGetLastEvent { + my $monitor = shift; + + return( zmMemRead( $monitor, 'shared_data:last_event' ) ); +} + +sub zmGetLastWriteTime { + my $monitor = shift; + + return( zmMemRead( $monitor, 'shared_data:last_write_time' ) ); +} + +sub zmGetLastReadTime { + my $monitor = shift; + + return( zmMemRead( $monitor, 'shared_data:last_read_time' ) ); +} + +sub zmGetMonitorActions { + my $monitor = shift; + + return( zmMemRead( $monitor, 'shared_data:action' ) ); +} + +sub zmMonitorEnable { + my $monitor = shift; + + my $action = zmMemRead( $monitor, 'shared_data:action' ); + $action |= ACTION_SUSPEND; + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); +} + +sub zmMonitorDisable { + my $monitor = shift; + + my $action = zmMemRead( $monitor, 'shared_data:action' ); + $action |= ACTION_RESUME; + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); +} + +sub zmMonitorSuspend { + my $monitor = shift; + + my $action = zmMemRead( $monitor, 'shared_data:action' ); + $action |= ACTION_SUSPEND; + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); +} + +sub zmMonitorResume { + my $monitor = shift; + + my $action = zmMemRead( $monitor, 'shared_data:action' ); + $action |= ACTION_RESUME; + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); +} + +sub zmGetTriggerState { + my $monitor = shift; + + return( zmMemRead( $monitor, 'trigger_data:trigger_state' ) ); +} + +sub zmTriggerEventOn { + my $monitor = shift; + my $score = shift; + my $cause = shift; + my $text = shift; + my $showtext = shift; + + my $values = { + 'trigger_data:trigger_score' => $score, + 'trigger_data:trigger_cause' => $cause, + }; + $values->{'trigger_data:trigger_text'} = $text if ( defined($text) ); + $values->{'trigger_data:trigger_showtext'} = $showtext if ( defined($showtext) ); + $values->{'trigger_data:trigger_state'} = TRIGGER_ON; # Write state last so event not read incomplete + + zmMemWrite( $monitor, $values ); +} + +sub zmTriggerEventOff { + my $monitor = shift; + + my $values = { + 'trigger_data:trigger_state' => TRIGGER_OFF, + 'trigger_data:trigger_score' => 0, + 'trigger_data:trigger_cause' => '', + 'trigger_data:trigger_text' => '', + 'trigger_data:trigger_showtext' => '', + }; + + zmMemWrite( $monitor, $values ); +} + +sub zmTriggerEventCancel { + my $monitor = shift; + + my $values = { + 'trigger_data:trigger_state' => TRIGGER_CANCEL, + 'trigger_data:trigger_score' => 0, + 'trigger_data:trigger_cause' => '', + 'trigger_data:trigger_text' => '', + 'trigger_data:trigger_showtext' => '', + }; + + zmMemWrite( $monitor, $values ); +} + +sub zmTriggerShowtext { + my $monitor = shift; + my $showtext = shift; + + my $values = { + 'trigger_data:trigger_showtext' => $showtext, + }; + + zmMemWrite( $monitor, $values ); } 1; @@ -755,23 +634,21 @@ ZoneMinder::MappedMem - ZoneMinder Mapped Memory access module =head1 SYNOPSIS - use ZoneMinder::MappedMem; - use ZoneMinder::MappedMem qw(:all); +use ZoneMinder::MappedMem; +use ZoneMinder::MappedMem qw(:all); - if ( zmMemVerify( $monitor ) ) - { - $state = zmGetMonitorState( $monitor ); - if ( $state == STATE_ALARM ) - { - ... - } +if ( zmMemVerify( $monitor ) ) { + $state = zmGetMonitorState( $monitor ); + if ( $state == STATE_ALARM ) { + ... } +} - ( $lri, $lwi ) = zmMemRead( $monitor, [ "shared_data:last_read_index", - "shared_data:last_write_index" - ] - ); - zmMemWrite( $monitor, { "trigger_data:trigger_showtext" => "Some Text" } ); +( $lri, $lwi ) = zmMemRead( $monitor, [ "shared_data:last_read_index", + "shared_data:last_write_index" +] +); +zmMemWrite( $monitor, { 'trigger_data:trigger_showtext' => "Some Text" } ); =head1 DESCRIPTION @@ -921,32 +798,32 @@ harmless, extreme care must be taken when writing to mapped memory, especially in the shared_data section as this is normally written to only by monitor capture and analysis processes. - shared_data The general mapped memory section - size The size, in bytes, of this section - valid Flag indicating whether this section has been initialised - active Flag indicating whether this monitor is active (enabled/disabled) - signal Flag indicating whether this monitor is reciving a valid signal - state The current monitor state, see the STATE constants below - last_write_index The last index, in the image buffer, that an image has been saved to - last_read_index The last index, in the image buffer, that an image has been analysed from - last_write_time The time (in utc seconds) when the last image was captured - last_read_time The time (in utc seconds) when the last image was analysed - last_event The id of the last event generated by the monitor analysis process, 0 if none - action The monitor actions bitmask, see the ACTION constants below - brightness Read/write location for the current monitor brightness - hue Read/write location for the current monitor hue - colour Read/write location for the current monitor colour - contrast Read/write location for the current monitor contrast - alarm_x Image x co-ordinate (from left) of the centre of the last motion event, -1 if none - alarm_y Image y co-ordinate (from top) of the centre of the last motion event, -1 if none +shared_data The general mapped memory section +size The size, in bytes, of this section +valid Flag indicating whether this section has been initialised +active Flag indicating whether this monitor is active (enabled/disabled) +signal Flag indicating whether this monitor is reciving a valid signal +state The current monitor state, see the STATE constants below +last_write_index The last index, in the image buffer, that an image has been saved to +last_read_index The last index, in the image buffer, that an image has been analysed from +last_write_time The time (in utc seconds) when the last image was captured +last_read_time The time (in utc seconds) when the last image was analysed +last_event The id of the last event generated by the monitor analysis process, 0 if none +action The monitor actions bitmask, see the ACTION constants below +brightness Read/write location for the current monitor brightness +hue Read/write location for the current monitor hue +colour Read/write location for the current monitor colour +contrast Read/write location for the current monitor contrast +alarm_x Image x co-ordinate (from left) of the centre of the last motion event, -1 if none +alarm_y Image y co-ordinate (from top) of the centre of the last motion event, -1 if none - trigger_data The triggered event mapped memory section - size The size, in bytes of this section - trigger_state The current trigger state, see the TRIGGER constants below - trigger_score The current triggered event score - trigger_cause The current triggered event cause string - trigger_text The current triggered event descriptive text string - trigger_showtext The triggered text that will be displayed on captured image timestamps +trigger_data The triggered event mapped memory section +size The size, in bytes of this section +trigger_state The current trigger state, see the TRIGGER constants below +trigger_score The current triggered event score +trigger_cause The current triggered event cause string +trigger_text The current triggered event descriptive text string +trigger_showtext The triggered text that will be displayed on captured image timestamps =head1 CONSTANTS diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm index 017b5aa8c..bd826c9c7 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -40,15 +40,15 @@ our @ISA = qw(Exporter ZoneMinder::Base); # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( - 'functions' => [ qw( - zmMemKey - zmMemAttach - zmMemDetach - zmMemGet - zmMemPut - zmMemClean - ) ], -); + functions => [ qw( + zmMemKey + zmMemAttach + zmMemDetach + zmMemGet + zmMemPut + zmMemClean + ) ], + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); @@ -68,133 +68,119 @@ use ZoneMinder::Logger qw(:all); use Sys::Mmap; -sub zmMemKey -{ - my $monitor = shift; - return( defined($monitor->{MMapAddr})?$monitor->{MMapAddr}:undef ); +sub zmMemKey { + my $monitor = shift; + return( defined($monitor->{MMapAddr})?$monitor->{MMapAddr}:undef ); } -sub zmMemAttach -{ - my ( $monitor, $size ) = @_; - if ( ! $size ) { - Error( "No size passed to zmMemAttach for monitor $$monitor{Id}\n" ); - return( undef ); +sub zmMemAttach { + my ( $monitor, $size ) = @_; + if ( ! $size ) { + Error( "No size passed to zmMemAttach for monitor $$monitor{Id}\n" ); + return( undef ); + } + if ( !defined($monitor->{MMapAddr}) ) { + + my $mmap_file = $Config{ZM_PATH_MAP}."/zm.mmap.".$monitor->{Id}; + if ( ! -e $mmap_file ) { + Error( sprintf( "Memory map file '%s' does not exist. zmc might not be running." + , $mmap_file + ) + ); + return ( undef ); } - if ( !defined($monitor->{MMapAddr}) ) - { - my $mmap_file = $Config{ZM_PATH_MAP}."/zm.mmap.".$monitor->{Id}; - if ( ! -e $mmap_file ) { - Error( sprintf( "Memory map file '%s' does not exist. zmc might not be running." - , $mmap_file - ) - ); - return ( undef ); - } + my $mmap_file_size = -s $mmap_file; - my $mmap_file_size = -s $mmap_file; - - if ( $mmap_file_size < $size ) { - Error( sprintf( "Memory map file '%s' should have been %d but was instead %d" - , $mmap_file - , $size - , $mmap_file_size - ) - ); - return ( undef ); - } - if ( !open( MMAP, "+<", $mmap_file ) ) - { - Error( sprintf( "Can't open memory map file '%s': $!\n", $mmap_file ) ); - return( undef ); - } - my $mmap = undef; - my $mmap_addr = mmap( $mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, \*MMAP ); - if ( !$mmap_addr || !$mmap ) - { - Error( sprintf( "Can't mmap to file '%s': $!\n", $mmap_file ) ); - close( MMAP ); - return( undef ); - } - $monitor->{MMapHandle} = \*MMAP; - $monitor->{MMapAddr} = $mmap_addr; - $monitor->{MMap} = \$mmap; + if ( $mmap_file_size < $size ) { + Error( sprintf( "Memory map file '%s' should have been %d but was instead %d" + , $mmap_file + , $size + , $mmap_file_size + ) + ); + return ( undef ); } - return( !undef ); + my $MMAP; + if ( !open( $MMAP, '+<', $mmap_file ) ) { + Error( sprintf( "Can't open memory map file '%s': $!\n", $mmap_file ) ); + return( undef ); + } + my $mmap = undef; + my $mmap_addr = mmap( $mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, $MMAP ); + if ( !$mmap_addr || !$mmap ) { + Error( sprintf( "Can't mmap to file '%s': $!\n", $mmap_file ) ); + close( $MMAP ); + return( undef ); + } + $monitor->{MMapHandle} = $MMAP; + $monitor->{MMapAddr} = $mmap_addr; + $monitor->{MMap} = \$mmap; + } + return( !undef ); } -sub zmMemDetach -{ - my $monitor = shift; +sub zmMemDetach { + my $monitor = shift; - if ( $monitor->{MMap} ) - { - if ( ! munmap( ${$monitor->{MMap}} ) ) { - Warn( "Unable to munmap for monitor $$monitor{Id}\n"); - } - delete $monitor->{MMap}; - } - if ( $monitor->{MMapAddr} ) - { - delete $monitor->{MMapAddr}; - } - if ( $monitor->{MMapHandle} ) - { - close( $monitor->{MMapHandle} ); - delete $monitor->{MMapHandle}; + if ( $monitor->{MMap} ) { + if ( ! munmap( ${$monitor->{MMap}} ) ) { + Warn( "Unable to munmap for monitor $$monitor{Id}\n"); } + delete $monitor->{MMap}; + } + if ( $monitor->{MMapAddr} ) { + delete $monitor->{MMapAddr}; + } + if ( $monitor->{MMapHandle} ) { + close( $monitor->{MMapHandle} ); + delete $monitor->{MMapHandle}; + } } -sub zmMemGet -{ - my $monitor = shift; - my $offset = shift; - my $size = shift; +sub zmMemGet { + my $monitor = shift; + my $offset = shift; + my $size = shift; - my $mmap = $monitor->{MMap}; - if ( !$mmap || !$$mmap ) - { - Error( sprintf( "Can't read from mapped memory for monitor '%d', gone away?" - , $monitor->{Id} - ) + my $mmap = $monitor->{MMap}; + if ( !$mmap || !$$mmap ) { + Error( sprintf( "Can't read from mapped memory for monitor '%d', gone away?" + , $monitor->{Id} + ) ); - return( undef ); - } - my $data = substr( $$mmap, $offset, $size ); - return( $data ); + return( undef ); + } + my $data = substr( $$mmap, $offset, $size ); + return( $data ); } -sub zmMemPut -{ - my $monitor = shift; - my $offset = shift; - my $size = shift; - my $data = shift; +sub zmMemPut { + my $monitor = shift; + my $offset = shift; + my $size = shift; + my $data = shift; - my $mmap = $monitor->{MMap}; - if ( !$mmap || !$$mmap ) - { - Error( sprintf( "Can't write mapped memory for monitor '%d', gone away?" - , $monitor->{Id} - ) + my $mmap = $monitor->{MMap}; + if ( !$mmap || !$$mmap ) { + Error( sprintf( "Can't write mapped memory for monitor '%d', gone away?" + , $monitor->{Id} + ) ); - return( undef ); - } - substr( $$mmap, $offset, $size ) = $data; - return( !undef ); + return( undef ); + } + substr( $$mmap, $offset, $size ) = $data; + return( !undef ); } -sub zmMemClean -{ - Debug( "Removing memory map files\n" ); - my $mapPath = $Config{ZM_PATH_MAP}."/zm.mmap.*"; - foreach my $mapFile( glob( $mapPath ) ) - { - ( $mapFile ) = $mapFile =~ /^(.+)$/; - Debug( "Removing memory map file '$mapFile'\n" ); - unlink( $mapFile ); - } +sub zmMemClean { + Debug( "Removing memory map files\n" ); + my $mapPath = $Config{ZM_PATH_MAP}.'/zm.mmap.*'; + foreach my $mapFile( glob( $mapPath ) ) { + ( $mapFile ) = $mapFile =~ /^(.+)$/; + Debug( "Removing memory map file '$mapFile'\n" ); + unlink( $mapFile ); + } } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Shared.pm b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Shared.pm index 301327095..fe2cfe115 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Shared.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Shared.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm new file mode 100644 index 000000000..d5b7d7f53 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -0,0 +1,85 @@ +# ========================================================================== +# +# ZoneMinder Monitor Module, $Date$, $Revision$ +# Copyright (C) 2001-2008 Philip Coombes +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Monitor; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; +require ZoneMinder::Server; + +#our @ISA = qw(Exporter ZoneMinder::Base); +use parent qw(ZoneMinder::Object); + +# ========================================================================== +# +# General Utility Functions +# +# ========================================================================== + +use ZoneMinder::Config qw(:all); +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Database qw(:all); + +use POSIX; +use vars qw/ $table $primary_key /; +$table = 'Monitors'; +$primary_key = 'Id'; + +sub Server { + return new ZoneMinder::Server( $_[0]{ServerId} ); +} # end sub Server + +1; +__END__ + +=head1 NAME + +ZoneMinder::Monitor - Perl Class for Monitors + +=head1 SYNOPSIS + +use ZoneMinder::Monitor; + +=head1 DESCRIPTION + + + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2017 ZoneMinder LLC + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm new file mode 100644 index 000000000..b87e111a6 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -0,0 +1,150 @@ +# ========================================================================== +# +# ZoneMinder Object Module, $Date$, $Revision$ +# Copyright (C) 2001-2017 ZoneMinder LLC +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Object; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; + +our @ISA = qw(ZoneMinder::Base); + +# ========================================================================== +# +# General Utility Functions +# +# ========================================================================== + +use ZoneMinder::Config qw(:all); +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Database qw(:all); + +use vars qw/ $AUTOLOAD /; + +sub new { + my ( $parent, $id, $data ) = @_; + + my $self = {}; + bless $self, $parent; + no strict 'refs'; + my $primary_key = ${$parent.'::primary_key'}; + if ( ! $primary_key ) { + Error( 'NO primary_key for type ' . $parent ); + return; + } # end if + if ( ( $$self{$primary_key} = $id ) or $data ) { +#$log->debug("loading $parent $id") if $debug or DEBUG_ALL; + $self->load( $data ); + } + return $self; +} # end sub new + +sub load { + my ( $self, $data ) = @_; + my $type = ref $self; + if ( ! $data ) { + no strict 'refs'; + my $table = ${$type.'::table'}; + if ( ! $table ) { + Error( 'NO table for type ' . $type ); + return; + } # end if + my $primary_key = ${$type.'::primary_key'}; + if ( ! $primary_key ) { + Error( 'NO primary_key for type ' . $type ); + return; + } # end if + + if ( ! $$self{$primary_key} ) { + my ( $caller, undef, $line ) = caller; + Error( (ref $self) . "::load called without $primary_key from $caller:$line"); + } else { +#$log->debug("Object::load Loading from db $type"); + Debug("Loading $type from $table WHERE $primary_key = $$self{$primary_key}"); + $data = $ZoneMinder::Database::dbh->selectrow_hashref( "SELECT * FROM $table WHERE $primary_key=?", {}, $$self{$primary_key} ); + if ( ! $data ) { + if ( $ZoneMinder::Database::dbh->errstr ) { + Error( "Failure to load Object record for $$self{$primary_key}: Reason: " . $ZoneMinder::Database::dbh->errstr ); + } else { + Debug("No Results Loading $type from $table WHERE $primary_key = $$self{$primary_key}"); + } # end if + } # end if + } # end if + } # end if ! $data + if ( $data and %$data ) { + @$self{keys %$data} = values %$data; + } # end if +} # end sub load + +sub AUTOLOAD { + my ( $self, $newvalue ) = @_; + my $type = ref($_[0]); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( @_ > 1 ) { + return $_[0]{$name} = $_[1]; + } + return $_[0]{$name}; +} + + +1; +__END__ + +# Below is stub documentation for your module. You'd better edit it! + +=head1 NAME + +ZoneMinder::Object + +=head1 SYNOPSIS + + use parent ZoneMinder::Object; + + This package should likely not be used directly, as it is meant mainly to be a parent for all other ZoneMinder classes. + +=head1 DESCRIPTION + + A base Object to act as parent for other ZoneMinder Objects. + +=head2 EXPORT + +None by default. + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2017 ZoneMinder LLC + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Server.pm b/scripts/ZoneMinder/lib/ZoneMinder/Server.pm index df8f2fb10..9c68f5a51 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Server.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Server.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # @@ -42,8 +42,8 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( 'functions' => [ qw( - ) ] -); + ) ] + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); @@ -65,46 +65,46 @@ use ZoneMinder::Database qw(:all); use POSIX; sub new { - my ( $parent, $id, $data ) = @_; + my ( $parent, $id, $data ) = @_; - my $self = {}; - bless $self, $parent; - if ( ( $$self{Id} = $id ) or $data ) { + my $self = {}; + bless $self, $parent; + if ( ( $$self{Id} = $id ) or $data ) { #$log->debug("loading $parent $id") if $debug or DEBUG_ALL; - $self->load( $data ); - } - return $self; + $self->load( $data ); + } + return $self; } # end sub new sub load { - my ( $self, $data ) = @_; - my $type = ref $self; - if ( ! $data ) { + my ( $self, $data ) = @_; + my $type = ref $self; + if ( ! $data ) { #$log->debug("Object::load Loading from db $type"); - $data = $ZoneMinder::Database::dbh->selectrow_hashref( 'SELECT * FROM Servers WHERE Id=?', {}, $$self{Id} ); - if ( ! $data ) { - if ( $ZoneMinder::Database::dbh->errstr ) { - Error( "Failure to load Server record for $$self{id}: Reason: " . $ZoneMinder::Database::dbh->errstr ); - } # end if - } # end if - } # end if ! $data - if ( $data and %$data ) { - @$self{keys %$data} = values %$data; - } # end if + $data = $ZoneMinder::Database::dbh->selectrow_hashref( 'SELECT * FROM Servers WHERE Id=?', {}, $$self{Id} ); + if ( ! $data ) { + if ( $ZoneMinder::Database::dbh->errstr ) { + Error( "Failure to load Server record for $$self{id}: Reason: " . $ZoneMinder::Database::dbh->errstr ); + } # end if + } # end if + } # end if ! $data + if ( $data and %$data ) { + @$self{keys %$data} = values %$data; + } # end if } # end sub load sub Name { - if ( @_ > 1 ) { - $_[0]{Name} = $_[1]; - } - return $_[0]{Name}; + if ( @_ > 1 ) { + $_[0]{Name} = $_[1]; + } + return $_[0]{Name}; } # end sub Name sub Hostname { - if ( @_ > 1 ) { - $_[0]{Hostname} = $_[1]; - } - return $_[0]{Hostname}; + if ( @_ > 1 ) { + $_[0]{Hostname} = $_[1]; + } + return $_[0]{Hostname}; } # end sub Hostname 1; @@ -117,8 +117,8 @@ ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS - use ZoneMinder::Server; - blah blah blah +use ZoneMinder::Server; +blah blah blah =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel.pm b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel.pm index 8b746ac3d..c8552f4a0 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/File.pm b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/File.pm index 0a4bdea8a..18444ab93 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/File.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/File.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Handle.pm b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Handle.pm index d26924476..8341cc8d2 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Handle.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Handle.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Inet.pm b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Inet.pm index 0f977f3da..dcd9d63dc 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Inet.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Inet.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Serial.pm b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Serial.pm index ddfc53436..5708510d1 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Serial.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Serial.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Spawning.pm b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Spawning.pm index aeaca182e..97d82b8e7 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Spawning.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Spawning.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Unix.pm b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Unix.pm index ea74c957e..017c2071a 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Unix.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Channel/Unix.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Connection.pm b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Connection.pm index 12cf3136d..a7829a532 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Connection.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Connection.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Connection/Example.pm b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Connection/Example.pm index 6f3a86575..8bef82460 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Connection/Example.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Trigger/Connection/Example.pm @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== # diff --git a/scripts/zm.in b/scripts/zm.in old mode 100755 new mode 100644 diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 7f2df08b9..6cf2af2e3 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== @@ -41,9 +41,9 @@ yet. =head1 OPTIONS - -r, --report - Just report don't actually do anything - -i, --interactive - Ask before applying any changes -c, --continuous - Run continuously + -i, --interactive - Ask before applying any changes + -r, --report - Just report don't actually do anything -v, --version - Print the installed version of ZoneMinder =cut @@ -57,8 +57,8 @@ use bytes; # ========================================================================== use constant MAX_AGED_DIRS => 10; # Number of event dirs to check age on -use constant RECOVER_TAG => "(r)"; # Tag to append to event name when recovered -use constant RECOVER_TEXT => "Recovered."; # Text to append to event notes when recovered +use constant RECOVER_TAG => '(r)'; # Tag to append to event name when recovered +use constant RECOVER_TEXT => 'Recovered.'; # Text to append to event notes when recovered # ========================================================================== # @@ -96,20 +96,23 @@ logInit(); logSetSignal(); GetOptions( - 'report' =>\$report, - 'interactive' =>\$interactive, - 'continuous' =>\$continuous, - 'version' =>\$version -) or pod2usage(-exitstatus => -1); + continuous =>\$continuous, + interactive =>\$interactive, + report =>\$report, + version =>\$version + ) or pod2usage(-exitstatus => -1); if ( $version ) { - print( ZoneMinder::Base::ZM_VERSION . "\n"); - exit(0); + print( ZoneMinder::Base::ZM_VERSION . "\n"); + exit(0); } -if ( ($report + $interactive + $continuous) > 1 ) -{ - print( STDERR "Error, only one option may be specified\n" ); - pod2usage(-exitstatus => -1); +if ( ($report + $interactive + $continuous) > 1 ) { + print( STDERR "Error, only one option may be specified\n" ); + pod2usage(-exitstatus => -1); +} + +if ( ! exists $Config{ZM_AUDIT_MIN_AGE} ) { + Fatal('ZM_AUDIT_MIN_AGE is not set in config.'); } my $dbh = zmDbConnect(); @@ -123,517 +126,439 @@ my $image_path = IMAGE_PATH; my $loop = 1; my $cleaned = 0; MAIN: while( $loop ) { - while ( ! ( $dbh and $dbh->ping() ) ) { - $dbh = zmDbConnect(); + while ( ! ( $dbh and $dbh->ping() ) ) { + $dbh = zmDbConnect(); - if ( $continuous ) { - Error("Unable to connect to database"); - # if we are running continuously, then just skip to the next - # interval, otherwise we are a one off run, so wait a second and - # retry until someone kills us. - sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); - } else { - Fatal("Unable to connect to database"); - } # end if - } # end while can't connect to the db + if ( $continuous ) { + Error('Unable to connect to database'); +# if we are running continuously, then just skip to the next +# interval, otherwise we are a one off run, so wait a second and +# retry until someone kills us. + sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); + } else { + Fatal('Unable to connect to database'); + } # end if + } # end while can't connect to the db - if ( $continuous ) { - # if we are running continuously, then just skip to the next - # interval, otherwise we are a one off run, so wait a second and - # retry until someone kills us. - sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); - } else { - sleep 1; - } # end if + if ( $continuous ) { +# if we are running continuously, then just skip to the next +# interval, otherwise we are a one off run, so wait a second and +# retry until someone kills us. + sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); + } else { + sleep 1; + } # end if - if ( ! exists $Config{ZM_AUDIT_MIN_AGE} ) { - Fatal("ZM_AUDIT_MIN_AGE is not set in config."); + my $db_monitors; + my $monitorSelectSql = 'select Id from Monitors order by Id'; + my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) + or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); + my $eventSelectSql = 'SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) as Age + FROM Events WHERE MonitorId = ? ORDER BY Id'; + my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) + or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); + + $cleaned = 0; + my $res = $monitorSelectSth->execute() + or Fatal( "Can't execute: ".$monitorSelectSth->errstr() ); + while( my $monitor = $monitorSelectSth->fetchrow_hashref() ) { + Debug( "Found database monitor '$monitor->{Id}'" ); + my $db_events = $db_monitors->{$monitor->{Id}} = {}; + my $res = $eventSelectSth->execute( $monitor->{Id} ) + or Fatal( "Can't execute: ".$eventSelectSth->errstr() ); + while ( my $event = $eventSelectSth->fetchrow_hashref() ) { + $db_events->{$event->{Id}} = $event->{Age}; } + Debug( 'Got '.int(keys(%$db_events))." events\n" ); + } - my $db_monitors; - my $monitorSelectSql = "select Id from Monitors order by Id"; - my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) - or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); - my $eventSelectSql = "SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) as Age - FROM Events WHERE MonitorId = ? ORDER BY Id"; - my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) - or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); + my $fs_monitors; + foreach my $monitor ( glob('[0-9]*') ) { +# Thie glob above gives all files starting with a digit. So a monitor with a name starting with a digit will be in this list. + next if $monitor =~ /\D/; + Debug( "Found filesystem monitor '$monitor'" ); + my $fs_events = $fs_monitors->{$monitor} = {}; + ( my $monitor_dir ) = ( $monitor =~ /^(.*)$/ ); # De-taint - $cleaned = 0; - my $res = $monitorSelectSth->execute() - or Fatal( "Can't execute: ".$monitorSelectSth->errstr() ); - while( my $monitor = $monitorSelectSth->fetchrow_hashref() ) - { - Debug( "Found database monitor '$monitor->{Id}'" ); - my $db_events = $db_monitors->{$monitor->{Id}} = {}; - my $res = $eventSelectSth->execute( $monitor->{Id} ) - or Fatal( "Can't execute: ".$eventSelectSth->errstr() ); - while ( my $event = $eventSelectSth->fetchrow_hashref() ) - { - $db_events->{$event->{Id}} = $event->{Age}; + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + foreach my $day_dir ( glob("$monitor_dir/*/*/*") ) { + Debug( "Checking day dir $day_dir" ); + ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint + if ( ! chdir( $day_dir ) ) { + Error( "Can't chdir to '$day_dir': $!" ); + next; } - Debug( "Got ".int(keys(%$db_events))." events\n" ); - } - - my $fs_monitors; - foreach my $monitor ( glob("[0-9]*") ) - { - # Thie glob above gives all files starting with a digit. So a monitor with a name starting with a digit will be in this list. - next if $monitor =~ /\D/; - Debug( "Found filesystem monitor '$monitor'" ); - my $fs_events = $fs_monitors->{$monitor} = {}; - ( my $monitor_dir ) = ( $monitor =~ /^(.*)$/ ); # De-taint - - if ( $Config{ZM_USE_DEEP_STORAGE} ) - { - foreach my $day_dir ( glob("$monitor_dir/*/*/*") ) - { - Debug( "Checking $day_dir" ); - ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint - chdir( $day_dir ); - opendir( DIR, "." ) - or Fatal( "Can't open directory '$day_dir': $!" ); - my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); - closedir( DIR ); - my $count = 0; - foreach my $event_link ( @event_links ) - { - Debug( "Checking link $event_link" ); - ( my $event = $event_link ) =~ s/^.*\.//; - my $event_path = readlink( $event_link ); - if ( $count++ > MAX_AGED_DIRS ) - { - $fs_events->{$event} = -1; - } - else - { - if ( !-e $event_path ) - { - aud_print( "Event link $day_dir/$event_link does not point to valid target" ); - if ( confirm() ) - { - ( $event_link ) = ( $event_link =~ /^(.*)$/ ); # De-taint - unlink( $event_link ); - $cleaned = 1; - } - } - else - { - $fs_events->{$event} = (time() - ($^T - ((-M $event_path) * 24*60*60))); - } - } - } - chdir( EVENT_PATH ); - } + if ( ! opendir( DIR, '.' ) ) { + Error( "Can't open directory '$day_dir': $!" ); + next; } - else - { - chdir( $monitor_dir ); - opendir( DIR, "." ) or Fatal( "Can't open directory '$monitor_dir': $!" ); - my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); - closedir( DIR ); - my $count = 0; - foreach my $event ( @temp_events ) - { - if ( $count++ > MAX_AGED_DIRS ) - { - $fs_events->{$event} = -1; - } - else - { - $fs_events->{$event} = (time() - ($^T - ((-M $event) * 24*60*60))); - } - } - chdir( EVENT_PATH ); - } - Debug( "Got ".int(keys(%$fs_events))." events\n" ); - } - redo MAIN if ( $cleaned ); - $cleaned = 0; - while ( my ( $fs_monitor, $fs_events ) = each(%$fs_monitors) ) - { - if ( my $db_events = $db_monitors->{$fs_monitor} ) - { - if ( $fs_events ) - { - while ( my ( $fs_event, $age ) = each(%$fs_events ) ) - { - if ( !defined($db_events->{$fs_event}) && ($age < 0 || ($age > $Config{ZM_AUDIT_MIN_AGE})) ) - { - aud_print( "Filesystem event '$fs_monitor/$fs_event' does not exist in database" ); - if ( confirm() ) - { - deleteEventFiles( $fs_event, $fs_monitor ); - $cleaned = 1; - } - } - } - } - } - else - { - aud_print( "Filesystem monitor '$fs_monitor' does not exist in database" ); - if ( confirm() ) - { - my $command = "rm -rf $fs_monitor"; - executeShellCommand( $command ); + my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); + closedir( DIR ); + my $count = 0; + foreach my $event_link ( @event_links ) { + Debug( "Checking link $event_link" ); + ( my $event = $event_link ) =~ s/^.*\.//; + my $event_path = readlink( $event_link ); + if ( $count++ > MAX_AGED_DIRS ) { + $fs_events->{$event} = -1; + } else { + if ( !-e $event_path ) { + aud_print( "Event link $day_dir/$event_link does not point to valid target" ); + if ( confirm() ) { + ( $event_link ) = ( $event_link =~ /^(.*)$/ ); # De-taint + unlink( $event_link ); $cleaned = 1; + } + } else { + $fs_events->{$event} = (time() - ($^T - ((-M $event_path) * 24*60*60))); } + } + } # end foreach event_link + chdir( EVENT_PATH ); + } # end foreach day_dir + } else { + chdir( $monitor_dir ); + opendir( DIR, "." ) or Fatal( "Can't open directory '$monitor_dir': $!" ); + my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); + closedir( DIR ); + my $count = 0; + foreach my $event ( @temp_events ) { + if ( $count++ > MAX_AGED_DIRS ) { + $fs_events->{$event} = -1; + } else { + $fs_events->{$event} = (time() - ($^T - ((-M $event) * 24*60*60))); } + } + chdir( EVENT_PATH ); } + Debug( 'Got '.int(keys(%$fs_events))." events\n" ); + } # end foreach monitor Id + redo MAIN if ( $cleaned ); - my $monitor_links; - foreach my $link ( glob("*") ) - { - next if ( !-l $link ); - next if ( -e $link ); - - aud_print( "Filesystem monitor link '$link' does not point to valid monitor directory" ); - if ( confirm() ) - { - ( $link ) = ( $link =~ /^(.*)$/ ); # De-taint - my $command = "rm $link"; - executeShellCommand( $command ); - $cleaned = 1; - } - } - redo MAIN if ( $cleaned ); - - $cleaned = 0; - my $deleteMonitorSql = "delete low_priority from Monitors where Id = ?"; - my $deleteMonitorSth = $dbh->prepare_cached( $deleteMonitorSql ) - or Fatal( "Can't prepare '$deleteMonitorSql': ".$dbh->errstr() ); - my $deleteEventSql = "delete low_priority from Events where Id = ?"; - my $deleteEventSth = $dbh->prepare_cached( $deleteEventSql ) - or Fatal( "Can't prepare '$deleteEventSql': ".$dbh->errstr() ); - my $deleteFramesSql = "delete low_priority from Frames where EventId = ?"; - my $deleteFramesSth = $dbh->prepare_cached( $deleteFramesSql ) - or Fatal( "Can't prepare '$deleteFramesSql': ".$dbh->errstr() ); - my $deleteStatsSql = "delete low_priority from Stats where EventId = ?"; - my $deleteStatsSth = $dbh->prepare_cached( $deleteStatsSql ) - or Fatal( "Can't prepare '$deleteStatsSql': ".$dbh->errstr() ); - while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) ) - { - if ( my $fs_events = $fs_monitors->{$db_monitor} ) - { - if ( $db_events ) - { - while ( my ( $db_event, $age ) = each(%$db_events ) ) - { - if ( !defined($fs_events->{$db_event}) ) { - if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { - aud_print( "Database event '$db_monitor/$db_event' does not exist in filesystem" ); - if ( confirm() ) { - my $res = $deleteEventSth->execute( $db_event ) - or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); - $res = $deleteFramesSth->execute( $db_event ) - or Fatal( "Can't execute: ".$deleteFramesSth->errstr() ); - $res = $deleteStatsSth->execute( $db_event ) - or Fatal( "Can't execute: ".$deleteStatsSth->errstr() ); - $cleaned = 1; - } - } else { - aud_print( "Database event '$db_monitor/$db_event' does not exist in filesystem but too young to delete." ); - } - } - } + $cleaned = 0; + while ( my ( $fs_monitor, $fs_events ) = each(%$fs_monitors) ) { + if ( my $db_events = $db_monitors->{$fs_monitor} ) { + if ( $fs_events ) { + while ( my ( $fs_event, $age ) = each(%$fs_events ) ) { + if ( !defined($db_events->{$fs_event}) && ($age < 0 || ($age > $Config{ZM_AUDIT_MIN_AGE})) ) { + aud_print( "Filesystem event '$fs_monitor/$fs_event' does not exist in database" ); + if ( confirm() ) { + deleteEventFiles( $fs_event, $fs_monitor ); + $cleaned = 1; } + } } - else - { - aud_print( "Database monitor '$db_monitor' does not exist in filesystem" ); - #if ( confirm() ) - #{ - # We don't actually do this in case it's new - #my $res = $deleteMonitorSth->execute( $db_monitor ) - # or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() ); - #$cleaned = 1; - #} - } + } + } else { + aud_print( "Filesystem monitor '$fs_monitor' does not exist in database" ); + if ( confirm() ) { + my $command = "rm -rf $fs_monitor"; + executeShellCommand( $command ); + $cleaned = 1; + } } - redo MAIN if ( $cleaned ); + } - # Remove orphaned events (with no monitor) - $cleaned = 0; - my $selectOrphanedEventsSql = "SELECT Events.Id, Events.Name - FROM Events LEFT JOIN Monitors ON (Events.MonitorId = Monitors.Id) - WHERE isnull(Monitors.Id)"; - my $selectOrphanedEventsSth = $dbh->prepare_cached( $selectOrphanedEventsSql ) - or Fatal( "Can't prepare '$selectOrphanedEventsSql': ".$dbh->errstr() ); - $res = $selectOrphanedEventsSth->execute() - or Fatal( "Can't execute: ".$selectOrphanedEventsSth->errstr() ); - while( my $event = $selectOrphanedEventsSth->fetchrow_hashref() ) - { - aud_print( "Found orphaned event with no monitor '$event->{Id}'" ); - if ( confirm() ) - { - $res = $deleteEventSth->execute( $event->{Id} ) - or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); - $cleaned = 1; - } + my $monitor_links; + foreach my $link ( glob('*') ) { + next if ( !-l $link ); + next if ( -e $link ); + + aud_print( "Filesystem monitor link '$link' does not point to valid monitor directory" ); + if ( confirm() ) { + ( $link ) = ( $link =~ /^(.*)$/ ); # De-taint + my $command = qq`rm "$link"`; + executeShellCommand( $command ); + $cleaned = 1; } - redo MAIN if ( $cleaned ); + } + redo MAIN if ( $cleaned ); - # Remove empty events (with no frames) - $cleaned = 0; - my $selectEmptyEventsSql = "SELECT E.Id AS Id, E.StartTime, F.EventId FROM Events as E LEFT JOIN Frames as F ON (E.Id = F.EventId) - WHERE isnull(F.EventId) AND now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second > E.StartTime"; - my $selectEmptyEventsSth = $dbh->prepare_cached( $selectEmptyEventsSql ) - or Fatal( "Can't prepare '$selectEmptyEventsSql': ".$dbh->errstr() ); - $res = $selectEmptyEventsSth->execute() - or Fatal( "Can't execute: ".$selectEmptyEventsSth->errstr() ); - while( my $event = $selectEmptyEventsSth->fetchrow_hashref() ) - { - aud_print( "Found empty event with no frame records '$event->{Id}'" ); - if ( confirm() ) - { - $res = $deleteEventSth->execute( $event->{Id} ) - or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); - $cleaned = 1; - } - } - redo MAIN if ( $cleaned ); - - # Remove orphaned frame records - $cleaned = 0; - my $selectOrphanedFramesSql = "SELECT DISTINCT EventId FROM Frames - WHERE EventId NOT IN (SELECT Id FROM Events)"; - my $selectOrphanedFramesSth = $dbh->prepare_cached( $selectOrphanedFramesSql ) - or Fatal( "Can't prepare '$selectOrphanedFramesSql': ".$dbh->errstr() ); - $res = $selectOrphanedFramesSth->execute() - or Fatal( "Can't execute: ".$selectOrphanedFramesSth->errstr() ); - while( my $frame = $selectOrphanedFramesSth->fetchrow_hashref() ) - { - aud_print( "Found orphaned frame records for event '$frame->{EventId}'" ); - if ( confirm() ) - { - $res = $deleteFramesSth->execute( $frame->{EventId} ) - or Fatal( "Can't execute: ".$deleteFramesSth->errstr() ); - $cleaned = 1; - } - } - redo MAIN if ( $cleaned ); - - # Remove orphaned stats records - $cleaned = 0; - my $selectOrphanedStatsSql = "SELECT DISTINCT EventId FROM Stats - WHERE EventId NOT IN (SELECT Id FROM Events)"; - my $selectOrphanedStatsSth = $dbh->prepare_cached( $selectOrphanedStatsSql ) - or Fatal( "Can't prepare '$selectOrphanedStatsSql': ".$dbh->errstr() ); - $res = $selectOrphanedStatsSth->execute() - or Fatal( "Can't execute: ".$selectOrphanedStatsSth->errstr() ); - while( my $stat = $selectOrphanedStatsSth->fetchrow_hashref() ) - { - aud_print( "Found orphaned statistic records for event '$stat->{EventId}'" ); - if ( confirm() ) - { - $res = $deleteStatsSth->execute( $stat->{EventId} ) - or Fatal( "Can't execute: ".$deleteStatsSth->errstr() ); - $cleaned = 1; - } - } - redo MAIN if ( $cleaned ); - - # New audit to close any events that were left open for longer than MIN_AGE seconds - my $selectUnclosedEventsSql = - "SELECT E.Id, - max(F.TimeStamp) as EndTime, - unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length, - max(F.FrameId) as Frames, - count(if(F.Score>0,1,NULL)) as AlarmFrames, - sum(F.Score) as TotScore, - max(F.Score) as MaxScore, - M.EventPrefix as Prefix - FROM Events as E - LEFT JOIN Monitors as M on E.MonitorId = M.Id - INNER JOIN Frames as F on E.Id = F.EventId - WHERE isnull(E.Frames) or isnull(E.EndTime) - GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second)" - ; - my $selectUnclosedEventsSth = $dbh->prepare_cached( $selectUnclosedEventsSql ) - or Fatal( "Can't prepare '$selectUnclosedEventsSql': ".$dbh->errstr() ); - my $updateUnclosedEventsSql = - "UPDATE low_priority Events - SET Name = ?, - EndTime = ?, - Length = ?, - Frames = ?, - AlarmFrames = ?, - TotScore = ?, - AvgScore = ?, - MaxScore = ?, - Notes = concat_ws( ' ', Notes, ? ) - WHERE Id = ?" - ; - my $updateUnclosedEventsSth = $dbh->prepare_cached( $updateUnclosedEventsSql ) - or Fatal( "Can't prepare '$updateUnclosedEventsSql': ".$dbh->errstr() ); - $res = $selectUnclosedEventsSth->execute() - or Fatal( "Can't execute: ".$selectUnclosedEventsSth->errstr() ); - while( my $event = $selectUnclosedEventsSth->fetchrow_hashref() ) - { - aud_print( "Found open event '$event->{Id}'" ); - if ( confirm( 'close', 'closing' ) ) - { - $res = $updateUnclosedEventsSth->execute - ( - sprintf("%s%d%s", - $event->{Prefix}, - $event->{Id}, - RECOVER_TAG - ), - $event->{EndTime}, - $event->{Length}, - $event->{Frames}, - $event->{AlarmFrames}, - $event->{TotScore}, - $event->{AlarmFrames} - ? int($event->{TotScore} / $event->{AlarmFrames}) - : 0 - , - $event->{MaxScore}, - RECOVER_TEXT, - $event->{Id} - ) or Fatal( "Can't execute: ".$updateUnclosedEventsSth->errstr() ); - } - } - - # Now delete any old image files - if ( my @old_files = grep { -M > $max_image_age } <$image_path/*.{jpg,gif,wbmp}> ) - { - aud_print( "Deleting ".int(@old_files)." old images\n" ); - my $untainted_old_files = join( ";", @old_files ); - ( $untainted_old_files ) = ( $untainted_old_files =~ /^(.*)$/ ); - unlink( split( /;/, $untainted_old_files ) ); - } - - # Now delete any old swap files - ( my $swap_image_root ) = ( $Config{ZM_PATH_SWAP} =~ /^(.*)$/ ); # De-taint - File::Find::find( { wanted=>\&deleteSwapImage, untaint=>1 }, $swap_image_root ); - - # Prune the Logs table if required - if ( $Config{ZM_LOG_DATABASE_LIMIT} ) - { - if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ ) - { - # Number of rows - my $selectLogRowCountSql = "SELECT count(*) as Rows from Logs"; - my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql ) - or Fatal( "Can't prepare '$selectLogRowCountSql': ".$dbh->errstr() ); - $res = $selectLogRowCountSth->execute() - or Fatal( "Can't execute: ".$selectLogRowCountSth->errstr() ); - my $row = $selectLogRowCountSth->fetchrow_hashref(); - my $logRows = $row->{Rows}; - if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) - { - my $deleteLogByRowsSql = "DELETE low_priority FROM Logs ORDER BY TimeKey ASC LIMIT ?"; - my $deleteLogByRowsSth = $dbh->prepare_cached( $deleteLogByRowsSql ) - or Fatal( "Can't prepare '$deleteLogByRowsSql': ".$dbh->errstr() ); - $res = $deleteLogByRowsSth->execute( $logRows - $Config{ZM_LOG_DATABASE_LIMIT} ) - or Fatal( "Can't execute: ".$deleteLogByRowsSth->errstr() ); - if ( $deleteLogByRowsSth->rows() ) - { - aud_print( "Deleted ".$deleteLogByRowsSth->rows() - ." log table entries by count\n" ) - ; - } - } - } - else - { - # Time of record - my $deleteLogByTimeSql = - "DELETE low_priority FROM Logs - WHERE TimeKey < unix_timestamp(now() - interval ".$Config{ZM_LOG_DATABASE_LIMIT}.")"; - my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql ) - or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() ); - $res = $deleteLogByTimeSth->execute() - or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() ); - if ( $deleteLogByTimeSth->rows() ){ - aud_print( "Deleted ".$deleteLogByTimeSth->rows() - ." log table entries by time\n" ) - ; + $cleaned = 0; + my $deleteMonitorSql = 'delete low_priority from Monitors where Id = ?'; + my $deleteMonitorSth = $dbh->prepare_cached( $deleteMonitorSql ) + or Fatal( "Can't prepare '$deleteMonitorSql': ".$dbh->errstr() ); + my $deleteEventSql = 'delete low_priority from Events where Id = ?'; + my $deleteEventSth = $dbh->prepare_cached( $deleteEventSql ) + or Fatal( "Can't prepare '$deleteEventSql': ".$dbh->errstr() ); + my $deleteFramesSql = 'delete low_priority from Frames where EventId = ?'; + my $deleteFramesSth = $dbh->prepare_cached( $deleteFramesSql ) + or Fatal( "Can't prepare '$deleteFramesSql': ".$dbh->errstr() ); + my $deleteStatsSql = 'delete low_priority from Stats where EventId = ?'; + my $deleteStatsSth = $dbh->prepare_cached( $deleteStatsSql ) + or Fatal( "Can't prepare '$deleteStatsSql': ".$dbh->errstr() ); + while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) ) { + if ( my $fs_events = $fs_monitors->{$db_monitor} ) { + if ( $db_events ) { + while ( my ( $db_event, $age ) = each(%$db_events ) ) { + if ( !defined($fs_events->{$db_event}) ) { + if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { + aud_print( "Database event '$db_monitor/$db_event' does not exist in filesystem" ); + if ( confirm() ) { + my $res = $deleteEventSth->execute( $db_event ) + or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); + $res = $deleteFramesSth->execute( $db_event ) + or Fatal( "Can't execute: ".$deleteFramesSth->errstr() ); + $res = $deleteStatsSth->execute( $db_event ) + or Fatal( "Can't execute: ".$deleteStatsSth->errstr() ); + $cleaned = 1; + } + } else { + aud_print( "Database event '$db_monitor/$db_event' does not exist in filesystem but too young to delete." ); } + } } + } # end if db_events + } else { + aud_print( "Database monitor '$db_monitor' does not exist in filesystem" ); +#if ( confirm() ) +#{ +# We don't actually do this in case it's new +#my $res = $deleteMonitorSth->execute( $db_monitor ) +# or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() ); +#$cleaned = 1; +#} } - $loop = $continuous; + } # end foreach db_monitor + redo MAIN if ( $cleaned ); - sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ) if $continuous; +# Remove orphaned events (with no monitor) + $cleaned = 0; + my $selectOrphanedEventsSql = 'SELECT Events.Id, Events.Name + FROM Events LEFT JOIN Monitors ON (Events.MonitorId = Monitors.Id) + WHERE isnull(Monitors.Id)'; + my $selectOrphanedEventsSth = $dbh->prepare_cached( $selectOrphanedEventsSql ) + or Fatal( "Can't prepare '$selectOrphanedEventsSql': ".$dbh->errstr() ); + $res = $selectOrphanedEventsSth->execute() + or Fatal( "Can't execute: ".$selectOrphanedEventsSth->errstr() ); + while( my $event = $selectOrphanedEventsSth->fetchrow_hashref() ) { + aud_print( "Found orphaned event with no monitor '$event->{Id}'" ); + if ( confirm() ) { + $res = $deleteEventSth->execute( $event->{Id} ) + or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); + $cleaned = 1; + } + } + redo MAIN if ( $cleaned ); + +# Remove empty events (with no frames) + $cleaned = 0; + my $selectEmptyEventsSql = "SELECT E.Id AS Id, E.StartTime, F.EventId FROM Events as E LEFT JOIN Frames as F ON (E.Id = F.EventId) + WHERE isnull(F.EventId) AND now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second > E.StartTime"; + my $selectEmptyEventsSth = $dbh->prepare_cached( $selectEmptyEventsSql ) + or Fatal( "Can't prepare '$selectEmptyEventsSql': ".$dbh->errstr() ); + $res = $selectEmptyEventsSth->execute() + or Fatal( "Can't execute: ".$selectEmptyEventsSth->errstr() ); + while( my $event = $selectEmptyEventsSth->fetchrow_hashref() ) { + aud_print( "Found empty event with no frame records '$event->{Id}'" ); + if ( confirm() ) { + $res = $deleteEventSth->execute( $event->{Id} ) + or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); + $cleaned = 1; + } + } + redo MAIN if ( $cleaned ); + +# Remove orphaned frame records + $cleaned = 0; + my $selectOrphanedFramesSql = 'SELECT DISTINCT EventId FROM Frames + WHERE EventId NOT IN (SELECT Id FROM Events)'; + my $selectOrphanedFramesSth = $dbh->prepare_cached( $selectOrphanedFramesSql ) + or Fatal( "Can't prepare '$selectOrphanedFramesSql': ".$dbh->errstr() ); + $res = $selectOrphanedFramesSth->execute() + or Fatal( "Can't execute: ".$selectOrphanedFramesSth->errstr() ); + while( my $frame = $selectOrphanedFramesSth->fetchrow_hashref() ) { + aud_print( "Found orphaned frame records for event '$frame->{EventId}'" ); + if ( confirm() ) { + $res = $deleteFramesSth->execute( $frame->{EventId} ) + or Fatal( "Can't execute: ".$deleteFramesSth->errstr() ); + $cleaned = 1; + } + } + redo MAIN if ( $cleaned ); + +# Remove orphaned stats records + $cleaned = 0; + my $selectOrphanedStatsSql = 'SELECT DISTINCT EventId FROM Stats + WHERE EventId NOT IN (SELECT Id FROM Events)'; + my $selectOrphanedStatsSth = $dbh->prepare_cached( $selectOrphanedStatsSql ) + or Fatal( "Can't prepare '$selectOrphanedStatsSql': ".$dbh->errstr() ); + $res = $selectOrphanedStatsSth->execute() + or Fatal( "Can't execute: ".$selectOrphanedStatsSth->errstr() ); + while( my $stat = $selectOrphanedStatsSth->fetchrow_hashref() ) { + aud_print( "Found orphaned statistic records for event '$stat->{EventId}'" ); + if ( confirm() ) { + $res = $deleteStatsSth->execute( $stat->{EventId} ) + or Fatal( "Can't execute: ".$deleteStatsSth->errstr() ); + $cleaned = 1; + } + } + redo MAIN if ( $cleaned ); + +# New audit to close any events that were left open for longer than MIN_AGE seconds + my $selectUnclosedEventsSql = + "SELECT E.Id, + max(F.TimeStamp) as EndTime, + unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length, + max(F.FrameId) as Frames, + count(if(F.Score>0,1,NULL)) as AlarmFrames, + sum(F.Score) as TotScore, + max(F.Score) as MaxScore, + M.EventPrefix as Prefix + FROM Events as E + LEFT JOIN Monitors as M on E.MonitorId = M.Id + INNER JOIN Frames as F on E.Id = F.EventId + WHERE isnull(E.Frames) or isnull(E.EndTime) + GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second)" + ; + my $selectUnclosedEventsSth = $dbh->prepare_cached( $selectUnclosedEventsSql ) + or Fatal( "Can't prepare '$selectUnclosedEventsSql': ".$dbh->errstr() ); + my $updateUnclosedEventsSql = + "UPDATE low_priority Events + SET Name = ?, + EndTime = ?, + Length = ?, + Frames = ?, + AlarmFrames = ?, + TotScore = ?, + AvgScore = ?, + MaxScore = ?, + Notes = concat_ws( ' ', Notes, ? ) + WHERE Id = ?" + ; + my $updateUnclosedEventsSth = $dbh->prepare_cached( $updateUnclosedEventsSql ) + or Fatal( "Can't prepare '$updateUnclosedEventsSql': ".$dbh->errstr() ); + $res = $selectUnclosedEventsSth->execute() + or Fatal( "Can't execute: ".$selectUnclosedEventsSth->errstr() ); + while( my $event = $selectUnclosedEventsSth->fetchrow_hashref() ) { + aud_print( "Found open event '$event->{Id}'" ); + if ( confirm( 'close', 'closing' ) ) { + $res = $updateUnclosedEventsSth->execute( + sprintf('%s%d%s', $event->{Prefix}, $event->{Id}, RECOVER_TAG), + $event->{EndTime}, + $event->{Length}, + $event->{Frames}, + $event->{AlarmFrames}, + $event->{TotScore}, + $event->{AlarmFrames} ? int($event->{TotScore} / $event->{AlarmFrames}) : 0, + $event->{MaxScore}, + RECOVER_TEXT, + $event->{Id} + ) or Fatal( "Can't execute: ".$updateUnclosedEventsSth->errstr() ); + } + } + +# Now delete any old image files + my @old_files = grep { -M > $max_image_age } <$image_path/*.{jpg,gif,wbmp}>; + if ( @old_files ) { + aud_print( 'Deleting '.( scalar @old_files )." old images\n" ); + my $untainted_old_files = join( ';', @old_files ); + ( $untainted_old_files ) = ( $untainted_old_files =~ /^(.*)$/ ); + unlink( split( /;/, $untainted_old_files ) ); + } + +# Now delete any old swap files + ( my $swap_image_root ) = ( $Config{ZM_PATH_SWAP} =~ /^(.*)$/ ); # De-taint + File::Find::find( { wanted=>\&deleteSwapImage, untaint=>1 }, $swap_image_root ); + + # Prune the Logs table if required + if ( $Config{ZM_LOG_DATABASE_LIMIT} ) { + if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ ) { + # Number of rows + my $selectLogRowCountSql = 'SELECT count(*) AS Rows FROM Logs'; + my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql ) + or Fatal( "Can't prepare '$selectLogRowCountSql': ".$dbh->errstr() ); + $res = $selectLogRowCountSth->execute() + or Fatal( "Can't execute: ".$selectLogRowCountSth->errstr() ); + my $row = $selectLogRowCountSth->fetchrow_hashref(); + my $logRows = $row->{Rows}; + if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) { + my $deleteLogByRowsSql = 'DELETE low_priority FROM Logs ORDER BY TimeKey ASC LIMIT ?'; + my $deleteLogByRowsSth = $dbh->prepare_cached( $deleteLogByRowsSql ) + or Fatal( "Can't prepare '$deleteLogByRowsSql': ".$dbh->errstr() ); + $res = $deleteLogByRowsSth->execute( $logRows - $Config{ZM_LOG_DATABASE_LIMIT} ) + or Fatal( "Can't execute: ".$deleteLogByRowsSth->errstr() ); + if ( $deleteLogByRowsSth->rows() ) { + aud_print( 'Deleted '.$deleteLogByRowsSth->rows() ." log table entries by count\n" ); + } + } + } else { + # Time of record + my $deleteLogByTimeSql = + 'DELETE low_priority FROM Logs + WHERE TimeKey < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.')'; + my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql ) + or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() ); + $res = $deleteLogByTimeSth->execute() + or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() ); + if ( $deleteLogByTimeSth->rows() ){ + aud_print( "Deleted ".$deleteLogByTimeSth->rows() ." log table entries by time\n" ); + } + } + } # end if ( $Config{ZM_LOG_DATABASE_LIMIT} ) + $loop = $continuous; + + sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ) if $continuous; }; exit( 0 ); -sub aud_print -{ - my $string = shift; - if ( !$continuous ) - { - print( $string ); - } - else - { - Info( $string ); - } +sub aud_print { + my $string = shift; + if ( ! $continuous ) { + print( $string ); + } else { + Info( $string ); + } } -sub confirm -{ - my $prompt = shift || "delete"; - my $action = shift || "deleting"; +sub confirm { + my $prompt = shift || 'delete'; + my $action = shift || 'deleting'; - my $yesno = 0; - if ( $report ) - { - print( "\n" ); + my $yesno = 0; + if ( $report ) { + print( "\n" ); + } elsif ( $interactive ) { + print( ", $prompt y/n: " ); + my $char = <>; + chomp( $char ); + if ( $char eq 'q' ) { + exit( 0 ); } - elsif ( $interactive ) - { - print( ", $prompt y/n: " ); - my $char = <>; - chomp( $char ); - if ( $char eq 'q' ) - { - exit( 0 ); - } - if ( !$char ) - { - $char = 'y'; - } - $yesno = ( $char =~ /[yY]/ ); + if ( !$char ) { + $char = 'y'; } - else - { - if ( !$continuous ) - { - print( ", $action\n" ); - } - else - { - Info( $action ); - } - $yesno = 1; + $yesno = ( $char =~ /[yY]/ ); + } else { + if ( !$continuous ) { + print( ", $action\n" ); + } else { + Info( $action ); } - return( $yesno ); + $yesno = 1; + } + return( $yesno ); } -sub deleteSwapImage -{ - my $file = $_; +sub deleteSwapImage { + my $file = $_; - if ( $file !~ /^zmswap-/ ) - { - return; - } + if ( $file !~ /^zmswap-/ ) { + return; + } - # Ignore directories - if ( -d $file ) - { - return; - } +# Ignore directories + if ( -d $file ) { + return; + } - if ( -M $file > $max_swap_age ) - { - Debug( "Deleting $file" ); - #unlink( $file ); - } + if ( -M $file > $max_swap_age ) { + Debug( "Deleting $file" ); +#unlink( $file ); + } } + +1; +__END__ diff --git a/scripts/zmcamtool.pl.in b/scripts/zmcamtool.pl.in index 8d6a6321c..e26aca8a6 100644 --- a/scripts/zmcamtool.pl.in +++ b/scripts/zmcamtool.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== @@ -55,6 +55,7 @@ a sql file, which can then be easily imported to another zoneminder system. --help - Print usage information. --user= - Alternate dB user with privileges to alter dB. --pass= - Password of alternate dB user with privileges to alter dB. + --version - Print version. =cut use strict; @@ -105,43 +106,43 @@ $Config{ZM_DB_USER} = $dbUser; $Config{ZM_DB_PASS} = $dbPass; if ( $version ) { - print( ZoneMinder::Base::ZM_VERSION . "\n"); - exit(0); + print( ZoneMinder::Base::ZM_VERSION . "\n"); + exit(0); } # Check to make sure commandline params make sense if ( ((!$help) && ($import + $export + $topreset) != 1 )) { - print( STDERR qq/Please give only one of the following: "import", "export", or "topreset".\n/ ); - pod2usage(-exitstatus => -1); + print( STDERR qq/Please give only one of the following: "import", "export", or "topreset".\n/ ); + pod2usage(-exitstatus => -1); } if ( ($export)&&($overwrite) ) { - print( "Warning: Overwrite parameter ignored during an export.\n"); + print( "Warning: Overwrite parameter ignored during an export.\n"); } if ( ($noregex)&&(!$topreset) ) { - print( qq/Warning: Noregex parameter only applies when "topreset" parameter is also set. Ignoring.\n/); + print( qq/Warning: Noregex parameter only applies when "topreset" parameter is also set. Ignoring.\n/); } if ( ($topreset)&&($ARGV[0] !~ /\d\d*/) ) { - print( STDERR qq/Parameter "topreset" requires a valid monitor ID.\n/ ); - pod2usage(-exitstatus => -1); + print( STDERR qq/Parameter "topreset" requires a valid monitor ID.\n/ ); + pod2usage(-exitstatus => -1); } # Call the appropriate subroutine based on the params given on the commandline if ($help) { - pod2usage(-exitstatus => -1); + pod2usage(-exitstatus => -1); } if ($export) { - exportsql(); + exportsql(); } if ($import) { - importsql(); + importsql(); } if ($topreset) { - toPreset(); + toPreset(); } ############### @@ -149,298 +150,293 @@ if ($topreset) { ############### # Execute a pre-built sql select query -sub selectQuery -{ - my $dbh = shift; - my $sql = shift; - my $monitorid = shift; +sub selectQuery { + my $dbh = shift; + my $sql = shift; + my $monitorid = shift; - my $sth = $dbh->prepare_cached( $sql ) - or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute($monitorid) - or die( "Can't execute: ".$sth->errstr() ); + my $sth = $dbh->prepare_cached( $sql ) + or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute($monitorid) + or die( "Can't execute: ".$sth->errstr() ); - my @data = $sth->fetchrow_array(); - $sth->finish(); + my @data = $sth->fetchrow_array(); + $sth->finish(); - return @data; + return @data; } # Exectute a pre-built sql query -sub runQuery -{ - my $dbh = shift; - my $sql = shift; - my $sth = $dbh->prepare_cached( $sql ) - or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() - or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); +sub runQuery { + my $dbh = shift; + my $sql = shift; + my $sth = $dbh->prepare_cached( $sql ) + or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() + or die( "Can't execute: ".$sth->errstr() ); + $sth->finish(); - return $res; + return $res; } # Build and execute a sql insert query -sub insertQuery -{ - my $dbh = shift; - my $tablename = shift; - my @data = @_; +sub insertQuery { + my $dbh = shift; + my $tablename = shift; + my @data = @_; - my $sql = "INSERT INTO $tablename VALUES (NULL," - .(join ", ", ("?") x @data).")"; # Add "?" for each array element + my $sql = "INSERT INTO $tablename VALUES (NULL," + .(join ', ', ('?') x @data).')'; # Add "?" for each array element my $sth = $dbh->prepare_cached( $sql ) - or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute(@data) - or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); + or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute(@data) + or die( "Can't execute: ".$sth->errstr() ); + $sth->finish(); - return $res; + return $res; } # Build and execute a sql delete query -sub deleteQuery -{ - my $dbh = shift; - my $sqltable = shift; - my $sqlname = shift; +sub deleteQuery { + my $dbh = shift; + my $sqltable = shift; + my $sqlname = shift; - my $sql = "DELETE FROM $sqltable WHERE Name = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute($sqlname) - or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); + my $sql = "DELETE FROM $sqltable WHERE Name = ?"; + my $sth = $dbh->prepare_cached( $sql ) + or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute($sqlname) + or die( "Can't execute: ".$sth->errstr() ); + $sth->finish(); - return $res; + return $res; } # Build and execute a sql select count query -sub checkExists -{ - my $dbh = shift; - my $sqltable = shift; - my $sqlname = shift; - my $result = 0; +sub checkExists { + my $dbh = shift; + my $sqltable = shift; + my $sqlname = shift; + my $result = 0; - my $sql = "SELECT count(*) FROM $sqltable WHERE Name = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute($sqlname) - or die( "Can't execute: ".$sth->errstr() ); + my $sql = "SELECT count(*) FROM $sqltable WHERE Name = ?"; + my $sth = $dbh->prepare_cached( $sql ) + or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute($sqlname) + or die( "Can't execute: ".$sth->errstr() ); - my $rows = $sth->fetchrow_arrayref(); - $sth->finish(); + my $rows = $sth->fetchrow_arrayref(); + $sth->finish(); - if ($rows->[0] > 0) { - $result = 1; - } + if ($rows->[0] > 0) { + $result = 1; + } - return $result; + return $result; } # Import camera control & presets into the zoneminder dB -sub importsql -{ - my @newcontrols; - my @overwritecontrols; - my @skippedcontrols; - my @newpresets; - my @overwritepresets; - my @skippedpresets; - my %controls; - my %monitorpresets; +sub importsql { + my @newcontrols; + my @overwritecontrols; + my @skippedcontrols; + my @newpresets; + my @overwritepresets; + my @skippedpresets; + my %controls; + my %monitorpresets; - if ($ARGV[0]) { - $sqlfile = $ARGV[0]; + if ($ARGV[0]) { + $sqlfile = $ARGV[0]; + } else { + $sqlfile = $Config{ZM_PATH_DATA}.'/db/zm_create.sql'; + } + + open(my $SQLFILE,'<',$sqlfile) + or die( "Can't Open file: $!\n" ); + +# Find and extract ptz control and monitor preset records + while (<$SQLFILE>) { +# Our regex replaces the primary key with NULL + if (s/^(INSERT INTO .*?Controls.*? VALUES \().*?(,')(.*?)(',.*)/$1NULL$2$3$4/i) { + $controls{$3} = $_; + } elsif (s/^(INSERT INTO .*?MonitorPresets.*? VALUES \().*?(,')(.*?)(',.*)/$1NULL$2$3$4/i) { + $monitorpresets{$3} = $_; + } + } + close $SQLFILE; + + if ( ! (%controls || %monitorpresets) ) { + die( "Error: No relevant data found in $sqlfile.\n" ); + } + +# Now that we've got what we were looking for, +# compare to what is already in the dB + + my $dbh = zmDbConnect(); + foreach (keys %controls) { + if (!checkExists($dbh,'Controls',$_)) { +# No existing Control was found. Add new control to dB. + runQuery($dbh,$controls{$_}); + push @newcontrols, $_; + } elsif ($overwrite) { +# An existing Control was found and the overwrite flag is set. +# Overwrite the control. + deleteQuery($dbh,'Controls',$_); + runQuery($dbh,$controls{$_}); + push @overwritecontrols, $_; } else { - $sqlfile = $Config{ZM_PATH_DATA}.'/db/zm_create.sql'; - } +# An existing Control was found and the overwrite flag was not set. +# Do nothing. + push @skippedcontrols, $_; + } + } - open(my $SQLFILE,"<",$sqlfile) - or die( "Can't Open file: $!\n" ); + foreach (keys %monitorpresets) { + if (!checkExists($dbh,'MonitorPresets',$_)) { +# No existing MonitorPreset was found. Add new MonitorPreset to dB. + runQuery($dbh,$monitorpresets{$_}); + push @newpresets, $_; + } elsif ($overwrite) { +# An existing MonitorPreset was found and the overwrite flag is set. +# Overwrite the MonitorPreset. + deleteQuery($dbh,'MonitorPresets',$_); + runQuery($dbh,$monitorpresets{$_}); + push @overwritepresets, $_; + } else { +# An existing MonitorPreset was found and the overwrite flag was +# not set. Do nothing. + push @skippedpresets, $_; + } + } - # Find and extract ptz control and monitor preset records - while (<$SQLFILE>) { - # Our regex replaces the primary key with NULL - if (s/^(INSERT INTO .*?Controls.*? VALUES \().*?(,')(.*?)(',.*)/$1NULL$2$3$4/i) { - $controls{$3} = $_; - } elsif (s/^(INSERT INTO .*?MonitorPresets.*? VALUES \().*?(,')(.*?)(',.*)/$1NULL$2$3$4/i) { - $monitorpresets{$3} = $_; - } - } - close $SQLFILE; + if (@newcontrols) { + print 'Number of ptz camera controls added: ' + .scalar(@newcontrols)."\n"; + } + if (@overwritecontrols) { + print 'Number of existing ptz camera controls overwritten: ' + .scalar(@overwritecontrols)."\n"; + } + if (@skippedcontrols) { + print 'Number of existing ptz camera controls skipped: ' + .scalar(@skippedcontrols)."\n"; + } - if ( ! (%controls || %monitorpresets) ) { - die( "Error: No relevant data found in $sqlfile.\n" ); - } - - # Now that we've got what we were looking for, - # compare to what is already in the dB - - my $dbh = zmDbConnect(); - foreach (keys %controls) { - if (!checkExists($dbh,"Controls",$_)) { - # No existing Control was found. Add new control to dB. - runQuery($dbh,$controls{$_}); - push @newcontrols, $_; - } elsif ($overwrite) { - # An existing Control was found and the overwrite flag is set. - # Overwrite the control. - deleteQuery($dbh,"Controls",$_); - runQuery($dbh,$controls{$_}); - push @overwritecontrols, $_; - } else { - # An existing Control was found and the overwrite flag was not set. - # Do nothing. - push @skippedcontrols, $_; - } - } - - foreach (keys %monitorpresets) { - if (!checkExists($dbh,"MonitorPresets",$_)) { - # No existing MonitorPreset was found. Add new MonitorPreset to dB. - runQuery($dbh,$monitorpresets{$_}); - push @newpresets, $_; - } elsif ($overwrite) { - # An existing MonitorPreset was found and the overwrite flag is set. - # Overwrite the MonitorPreset. - deleteQuery($dbh,"MonitorPresets",$_); - runQuery($dbh,$monitorpresets{$_}); - push @overwritepresets, $_; - } else { - # An existing MonitorPreset was found and the overwrite flag was - # not set. Do nothing. - push @skippedpresets, $_; - } - } - - if (@newcontrols) { - print "Number of ptz camera controls added: " - .scalar(@newcontrols)."\n"; - } - if (@overwritecontrols) { - print "Number of existing ptz camera controls overwritten: " - .scalar(@overwritecontrols)."\n"; - } - if (@skippedcontrols) { - print "Number of existing ptz camera controls skipped: " - .scalar(@skippedcontrols)."\n"; - } - - if (@newpresets) { - print "Number of monitor presets added: " - .scalar(@newpresets)."\n"; - } - if (@overwritepresets) { - print "Number of existing monitor presets overwritten: " - .scalar(@overwritepresets)."\n"; - } - if (@skippedpresets) { - print "Number of existing presets skipped: " - .scalar(@skippedpresets)."\n"; - } + if (@newpresets) { + print 'Number of monitor presets added: ' + .scalar(@newpresets)."\n"; + } + if (@overwritepresets) { + print 'Number of existing monitor presets overwritten: ' + .scalar(@overwritepresets)."\n"; + } + if (@skippedpresets) { + print 'Number of existing presets skipped: ' + .scalar(@skippedpresets)."\n"; + } } # Export camera controls & presets from the zoneminder dB to STDOUT -sub exportsql -{ +sub exportsql { -my ( $host, $port ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); -my $command = "mysqldump -t --skip-opt --compact -h".$host; -$command .= " -P".$port if defined($port); -if ( $dbUser ) { - $command .= " -u".$dbUser; + my ( $host, $port ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); + my $command = 'mysqldump -t --skip-opt --compact -h'.$host; + $command .= ' -P'.$port if defined($port); + if ( $dbUser ) { + $command .= ' -u'.$dbUser; if ( $dbPass ) { - $command .= " -p".$dbPass; - } + $command .= ' -p'.$dbPass; } + } -if ($ARGV[0]) { + if ($ARGV[0]) { $command .= qq( --where="Name = '$ARGV[0]'"); -} + } -$command .= " zm Controls MonitorPresets"; + $command .= " zm Controls MonitorPresets"; -my $output = qx($command); -my $status = $? >> 8; -if ( $status || logDebugging() ) { + my $output = qx($command); + my $status = $? >> 8; + if ( $status || logDebugging() ) { chomp( $output ); print( "Output: $output\n" ); -} -if ( $status ) { + } + if ( $status ) { die( "Command '$command' exited with status: $status\n" ); -} else { - # NULLify the primary keys before printing the output to STDOUT + } else { +# NULLify the primary keys before printing the output to STDOUT $output =~ s/VALUES \((.*?),'/VALUES \(NULL,'/ig; print $output; - } + } } -sub toPreset -{ - my $dbh = zmDbConnect(); - my $monitorid = $ARGV[0]; +sub toPreset { + my $dbh = zmDbConnect(); + my $monitorid = $ARGV[0]; - # Grap the following fields from the Monitors table - my $sql = "SELECT - Name, - Type, - Device, - Channel, - Format, - Protocol, - Method, - Host, - Port, - Path, - SubPath, - Width, - Height, - Palette, - MaxFPS, - Controllable, - ControlId, - ControlDevice, - ControlAddress, - DefaultRate, - DefaultScale - FROM Monitors WHERE Id = ?"; - my @data = selectQuery($dbh,$sql,$monitorid); +# Grap the following fields from the Monitors table + my $sql = 'SELECT + Name, + Type, + Device, + Channel, + Format, + Protocol, + Method, + Host, + Port, + Path, + SubPath, + Width, + Height, + Palette, + MaxFPS, + Controllable, + ControlId, + ControlDevice, + ControlAddress, + DefaultRate, + DefaultScale + FROM Monitors WHERE Id = ?'; + my @data = selectQuery($dbh,$sql,$monitorid); - if (!@data) { - die( "Error: Monitor Id $monitorid does not appear to exist in the database.\n" ); + if (!@data) { + die( "Error: Monitor Id $monitorid does not appear to exist in the database.\n" ); + } + +# Attempt to search for and replace system specific values such as +# ip addresses, ports, usernames, etc. with generic placeholders + if (!$noregex) { + foreach (@data) { + next if ! $_; + s/\b(?:\d{1,3}\.){3}\d{1,3}\b//; # ip address + s/:(6553[0-5]|655[0-2]\d|65[0-4]\d\d|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)$/:/; # tcpip port + s/\/\/.*:.*@/\/\/:@/; # user & pwd preceding an ip address + s/(&|\?)(user|username)=\w\w*(&|\?)/$1$2=$3/i; # username embedded in url + s/(&|\?)(pwd|password)=\w\w*(&|\?)/$1$2=$3/i; # password embedded in url + s/\w\w*:\w\w*/:/; # user & pwd in their own field + s/\/dev\/video\d\d*/\/dev\/video/; # local video devices } + } - # Attempt to search for and replace system specific values such as - # ip addresses, ports, usernames, etc. with generic placeholders - if (!$noregex) { - foreach (@data) { - s/\b(?:\d{1,3}\.){3}\d{1,3}\b//; # ip address - s/:(6553[0-5]|655[0-2]\d|65[0-4]\d\d|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)$/:/; # tcpip port - s/\/\/.*:.*@/\/\/:@/; # user & pwd preceding an ip address - s/(&|\?)(user|username)=\w\w*(&|\?)/$1$2=$3/i; # username embedded in url - s/(&|\?)(pwd|password)=\w\w*(&|\?)/$1$2=$3/i; # password embedded in url - s/\w\w*:\w\w*/:/; # user & pwd in their own field - s/\/dev\/video\d\d*/\/dev\/video/; # local video devices - } - } - - if (!checkExists($dbh,"MonitorPresets",$data[0])) { - # No existing Preset was found. Add new Preset to dB. - print "Adding new preset: $data[0]\n"; - insertQuery($dbh,"MonitorPresets",@data); - } elsif ($overwrite) { - # An existing Control was found and the overwrite flag is set. - # Overwrite the control. - print "Existing preset $data[0] detected.\nOverwriting...\n"; - deleteQuery($dbh,"MonitorPresets",$data[0]); - insertQuery($dbh,"MonitorPresets",@data); - } else { - # An existing Control was found and the overwrite flag was not set. - # Do nothing. - print "Existing preset $data[0] detected and overwrite flag not set.\nSkipping...\n"; - } + if (!checkExists($dbh,"MonitorPresets",$data[0])) { +# No existing Preset was found. Add new Preset to dB. + print "Adding new preset: $data[0]\n"; + insertQuery($dbh,'MonitorPresets',@data); + } elsif ($overwrite) { +# An existing Control was found and the overwrite flag is set. +# Overwrite the control. + print "Existing preset $data[0] detected.\nOverwriting...\n"; + deleteQuery($dbh,'MonitorPresets',$data[0]); + insertQuery($dbh,'MonitorPresets',@data); + } else { +# An existing Control was found and the overwrite flag was not set. +# Do nothing. + print "Existing preset $data[0] detected and overwrite flag not set.\nSkipping...\n"; + } } +1; +__END__ diff --git a/scripts/zmcontrol.pl.in b/scripts/zmcontrol.pl.in index 9b34533f7..79aeccb10 100644 --- a/scripts/zmcontrol.pl.in +++ b/scripts/zmcontrol.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index bafb0ed9e..33cfde9f7 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== @@ -69,7 +69,7 @@ use IO::Handle; use autouse 'Pod::Usage'=>qw(pod2usage); #use Data::Dumper; -use constant SOCK_FILE => $Config{ZM_PATH_SOCKS}.'/zmdc.sock'; +use constant SOCK_FILE => $Config{ZM_PATH_SOCKS}.'/zmdc'.($Config{ZM_SERVER_ID}?$Config{ZM_SERVER_ID}:'').'.sock'; $| = 1; @@ -78,13 +78,17 @@ $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; if ( $Config{ZM_LD_PRELOAD} ) { Debug("Adding ENV{LD_PRELOAD} = $Config{ZM_LD_PRELOAD}"); $ENV{LD_PRELOAD} = $Config{ZM_LD_PRELOAD}; + foreach my $lib ( split(/\s+/, $ENV{LD_PRELOAD} ) ) { + if ( ! -e $lib ) { + Warning("LD_PRELOAD lib $lib does not exist from LD_PRELOAD $ENV{LD_PRELOAD}."); + } + } } delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my @daemons = ( 'zmc', 'zma', - 'zmf', 'zmfilter.pl', 'zmaudit.pl', 'zmtrigger.pl', diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in old mode 100755 new mode 100644 index a101edf3a..c69ad57b1 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== @@ -27,7 +27,7 @@ zmfilter.pl - ZoneMinder tool to filter events =head1 SYNOPSIS - zmfilter.pl [-f ,--filter=] | -v, --version +zmfilter.pl [-f ,--filter=] | -v, --version =head1 DESCRIPTION @@ -37,8 +37,8 @@ matching events. =head1 OPTIONS - -f{filter name}, --filter={filter name} - The name of a specific filter to run - -v, --version - Print ZoneMinder version +-f{filter name}, --filter={filter name} - The name of a specific filter to run +-v, --version - Print ZoneMinder version =cut use strict; @@ -60,7 +60,6 @@ use constant START_DELAY => 5; # How long to wait before starting @EXTRA_PERL_LIB@ use ZoneMinder; -require ZoneMinder::Filter; use DBI; use POSIX; use Time::HiRes qw/gettimeofday/; @@ -69,61 +68,61 @@ use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use autouse 'Data::Dumper'=>qw(Dumper); +my $filter_parm = ''; +my $version = 0; + +GetOptions( + 'filter=s' =>\$filter_parm, + 'version' =>\$version + ) or pod2usage(-exitstatus => -1); + +if ( $version ) { + print ZoneMinder::Base::ZM_VERSION . "\n"; + exit(0); +} + +require ZoneMinder::Filter; + use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|) - ? $Config{ZM_DIR_EVENTS} - : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) -; + ? $Config{ZM_DIR_EVENTS} + : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) + ; logInit(); logSetSignal(); -if ( $Config{ZM_OPT_UPLOAD} ) -{ - # Comment these out if you don't have them and don't want to upload - # or don't want to use that format - if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" ) - { - require Archive::Zip; - import Archive::Zip qw( :ERROR_CODES :CONSTANTS ); - } - else - { - require Archive::Tar; - } - if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" ) - { - require Net::FTP; - } - else - { - require Net::SFTP::Foreign; - } +if ( $Config{ZM_OPT_UPLOAD} ) { +# Comment these out if you don't have them and don't want to upload +# or don't want to use that format + if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'zip' ) { + require Archive::Zip; + import Archive::Zip qw( :ERROR_CODES :CONSTANTS ); + } else { + require Archive::Tar; + } + if ( $Config{ZM_UPLOAD_PROTOCOL} eq 'ftp' ) { + require Net::FTP; + } else { + require Net::SFTP::Foreign; + } } -if ( $Config{ZM_OPT_EMAIL} ) -{ - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - require MIME::Lite; - require Net::SMTP; - } - else - { - require MIME::Entity; - } +if ( $Config{ZM_OPT_EMAIL} ) { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { + require MIME::Lite; + require Net::SMTP; + } else { + require MIME::Entity; + } } -if ( $Config{ZM_OPT_MESSAGE} ) -{ - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - require MIME::Lite; - require Net::SMTP; - } - else - { - require MIME::Entity; - } +if ( $Config{ZM_OPT_MESSAGE} ) { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { + require MIME::Lite; + require Net::SMTP; + } else { + require MIME::Entity; + } } $| = 1; @@ -134,862 +133,795 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL}; my $event_id = 0; -my $filter_parm = ""; -my $version = 0; - -# - -GetOptions( - 'filter=s' =>\$filter_parm, - 'version' =>\$version -) or pod2usage(-exitstatus => -1); - -if ( $version ) { - print ZoneMinder::Base::ZM_VERSION . "\n"; - exit(0); -} if ( ! EVENT_PATH ) { - Error( "No event path defined. Config was $Config{ZM_DIR_EVENTS}\n" ); - die; + Error( "No event path defined. Config was $Config{ZM_DIR_EVENTS}\n" ); + die; } +# In future, should not be neccessary wrt StorageAreas chdir( EVENT_PATH ); +# SHould not be neccessary... but nice to get a local var. What if it fails? my $dbh = zmDbConnect(); -if ( $filter_parm ) -{ - Info( "Scanning for events using filter '$filter_parm'\n" ); -} -else -{ - Info( "Scanning for events\n" ); +if ( $filter_parm ) { + Info( "Scanning for events using filter '$filter_parm'\n" ); +} else { + Info( "Scanning for events\n" ); } -if ( !$filter_parm ) -{ - sleep( START_DELAY ); +if ( ! $filter_parm ) { + Debug("Sleeping due to start delay: " . START_DELAY . ' seconds...' ); + sleep( START_DELAY ); } -my $filters; +my @filters; my $last_action = 0; -while( 1 ) -{ - my $now = time; - if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) - { - Debug( "Reloading filters\n" ); - $last_action = $now; - $filters = getFilters( $filter_parm ); - } +while( 1 ) { + my $now = time; + if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) { + Debug( "Reloading filters\n" ); + $last_action = $now; + @filters = getFilters( $filter_parm ); + } - foreach my $filter ( @$filters ) - { - checkFilter( $filter ); - } + foreach my $filter ( @filters ) { + checkFilter( $filter ); + } - last if ( $filter_parm ); + last if $filter_parm; - Debug( "Sleeping for $delay seconds\n" ); - sleep( $delay ); + Debug( "Sleeping for $delay seconds\n" ); + sleep( $delay ); } -sub getFilters -{ - my $filter_name = shift; +sub getFilters { + my $filter_name = shift; - my @filters; - my $sql = "SELECT * FROM Filters WHERE"; - if ( $filter_name ) - { - $sql .= " Name = ? and"; + my @filters; + my $sql = 'SELECT * FROM Filters WHERE'; + if ( $filter_name ) { + $sql .= ' Name = ? and'; + } else { + $sql .= ' Background = 1 AND'; + } + $sql .= '( AutoArchive = 1 + or AutoVideo = 1 + or AutoUpload = 1 + or AutoEmail = 1 + or AutoMessage = 1 + or AutoExecute = 1 + or AutoDelete = 1 + ) ORDER BY Name'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res; + if ( $filter_name ) { + $res = $sth->execute( $filter_name ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } else { + $res = $sth->execute() + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } +FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) { + my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); + Debug( "Found filter '$db_filter->{Name}'\n" ); + my $sql = $filter->Sql(); + + if ( ! $sql ) { + Error( "Error parsing Sql. skipping filter '$db_filter->{Name}'\n" ); + next FILTER; } - else - { - $sql .= " Background = 1 and"; - } - $sql .= "( AutoArchive = 1 - or AutoVideo = 1 - or AutoUpload = 1 - or AutoEmail = 1 - or AutoMessage = 1 - or AutoExecute = 1 - or AutoDelete = 1 - ) ORDER BY Name"; - my $sth = $dbh->prepare_cached( $sql ) + push( @filters, $filter ); + } + $sth->finish(); + Debug( 'Got ' . @filters . ' filters' ); + return( @filters ); +} + +sub checkFilter { + my $filter = shift; + + Debug( "Checking filter '$filter->{Name}'". + ($filter->{AutoDelete}?', delete':''). + ($filter->{AutoArchive}?', archive':''). + ($filter->{AutoVideo}?', video':''). + ($filter->{AutoUpload}?', upload':''). + ($filter->{AutoEmail}?', email':''). + ($filter->{AutoMessage}?', message':''). + ($filter->{AutoExecute}?', execute':''). + '\n' + ); + + foreach my $event ( $filter->Execute() ) { + Debug( "Checking event $event->{Id}\n" ); + my $delete_ok = !undef; + $dbh->ping(); + if ( $filter->{AutoArchive} ) { + Info( "Archiving event $event->{Id}\n" ); +# Do it individually to avoid locking up the table for new events + my $sql = 'update Events set Archived = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res; - if ( $filter_name ) - { - $res = $sth->execute( $filter_name ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Error( "Can't execute '$sql': ".$sth->errstr() ); } - else - { - $res = $sth->execute() - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { + if ( !$event->{Videoed} ) { + $delete_ok = undef if ( !generateVideo( $filter, $event ) ); + } } - FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) - { - my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); - Debug( "Found filter '$db_filter->{Name}'\n" ); - my $sql = $filter->Sql(); + if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) { + if ( !$event->{Emailed} ) { + $delete_ok = undef if ( !sendEmail( $filter, $event ) ); + } + } + if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) { + if ( !$event->{Messaged} ) { + $delete_ok = undef if ( !sendMessage( $filter, $event ) ); + } + } + if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) { + if ( !$event->{Uploaded} ) { + $delete_ok = undef if ( !uploadArchFile( $filter, $event ) ); + } + } + if ( $filter->{AutoExecute} ) { + if ( !$event->{Executed} ) { + $delete_ok = undef if ( !executeCommand( $filter, $event ) ); + } + } + if ( $filter->{AutoDelete} ) { + if ( $delete_ok ) { + Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId}\n" ); +# Do it individually to avoid locking up the table for new events + my $sql = 'delete from Events where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - if ( ! $sql ) { - Error( "Error parsing Sql. skipping filter '$db_filter->{Name}'\n" ); - next FILTER; - } - push( @filters, $filter ); + if ( ! $Config{ZM_OPT_FAST_DELETE} ) { + my $sql = 'delete from Frames where EventId = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + $sql = 'delete from Stats where EventId = ?'; + $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + deleteEventFiles( $event->{Id}, $event->{MonitorId} ); + } + } else { + Error( "Unable to delete event $event->{Id} as previous operations failed\n" ); + } + } + } +} + +sub generateVideo { + my $filter = shift; + my $event = shift; + my $phone = shift; + + my $rate = $event->{DefaultRate}/100; + my $scale = $event->{DefaultScale}/100; + my $format; + + my @ffmpeg_formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} ); + my $default_video_format; + my $default_phone_format; + foreach my $ffmpeg_format( @ffmpeg_formats ) { + if ( $ffmpeg_format =~ /^(.+)\*\*$/ ) { + $default_phone_format = $1; + } elsif ( $ffmpeg_format =~ /^(.+)\*$/ ) { + $default_video_format = $1; + } + } + + if ( $phone && $default_phone_format ) { + $format = $default_phone_format; + } elsif ( $default_video_format ) { + $format = $default_video_format; + } else { + $format = $ffmpeg_formats[0]; + } + + my $command = join( '', + $Config{ZM_PATH_BIN}, + '/zmvideo.pl -e ', + $event->{Id}, + ' -r ', + $rate, + ' -s ', + $scale, + ' -f ', + $format, + ); + my $output = qx($command); + chomp( $output ); + my $status = $? >> 8; + if ( $status || logDebugging() ) { + Debug( "Output: $output\n" ); + } + if ( $status ) { + Error( "Video generation '$command' failed with status: $status\n" ); + if ( wantarray() ) { + return( undef, undef ); + } + return( 0 ); + } else { + my $sql = 'update Events set Videoed = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + if ( wantarray() ) { + return( $format, $output ); + } + } + return( 1 ); +} + +# Returns an image absolute path for given event and frame +# Optionally an analyse image path may be returned if an analyse image exists +# If neither capture nor analyse image exists it will try to extract a frame from .mp4 file if exists +# An empty string is returned if no one from methods above works +sub generateImage { + my $event = shift; + my $frame = shift; + my $analyse = do { @_ ? shift : 0 }; # don't return analyse image by default + + my $capture_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-capture.jpg', getEventPath($event), $frame->{FrameId}); + my $analyse_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-analyse.jpg', getEventPath($event), $frame->{FrameId}) if $analyse; + my $video_path = sprintf('%s/%d-video.mp4', getEventPath($event), $event->{Id}); + my $image_path = ''; + +# check if the image file exists. If the file doesn't exist and we use H264 try to extract it from .mp4 video + if ( $analyse && -r $analyse_image_path ) { + $image_path = $analyse_image_path; + } elsif ( -r $capture_image_path ) { + $image_path = $capture_image_path; + } elsif ( -e $video_path ) { + if ( !system('ffmpeg -y -v 0 -i '.$video_path." -vf 'select=gte(n\\,".$frame->{FrameId}."),setpts=PTS-STARTPTS' -vframes 1 -f image2 ".$capture_image_path) ) { + $image_path = $capture_image_path; + } + } + return $image_path; +} + + +sub uploadArchFile { + my $filter = shift; + my $event = shift; + + if ( ! $Config{ZM_UPLOAD_HOST} ) { + Error( 'Cannot upload archive as no upload host defined' ); + return( 0 ); + } + + my $archFile = $event->{MonitorName}.'-'.$event->{Id}; + my $archImagePath = getEventPath( $event ) + .'/' + .( + ( $Config{ZM_UPLOAD_ARCH_ANALYSE} ) + ? '{*analyse,*capture}' + : '*capture' + ) + .'.jpg' + ; + my @archImageFiles = glob($archImagePath); + my $archLocPath; + + my $archError = 0; + if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'zip' ) { + $archFile .= '.zip'; + $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; + my $zip = Archive::Zip->new(); + Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); + + my $status = &AZ_OK; + foreach my $imageFile ( @archImageFiles ) { + Debug( "Adding $imageFile\n" ); + my $member = $zip->addFile( $imageFile ); + if ( !$member ) { + Error( "Unable to add image file $imageFile to zip archive $archLocPath" ); + $archError = 1; + last; + } + $member->desiredCompressionMethod( $Config{ZM_UPLOAD_ARCH_COMPRESS} + ? &COMPRESSION_DEFLATED + : &COMPRESSION_STORED + ); + } + if ( !$archError ) { + $status = $zip->writeToFileNamed( $archLocPath ); + + if ( $archError = ($status != &AZ_OK) ) { + Error( "Zip error: $status\n " ); + } + } else { + Error( "Error adding images to zip archive $archLocPath, not writing" ); + } + } elsif ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'tar' ) { + if ( $Config{ZM_UPLOAD_ARCH_COMPRESS} ) { + $archFile .= '.tar.gz'; + } else { + $archFile .= '.tar'; + } + $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; + Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); + + if ( $archError = !Archive::Tar->create_archive( + $archLocPath, + $Config{ZM_UPLOAD_ARCH_COMPRESS}, + @archImageFiles + ) + ) { + Error( 'Tar error: '.Archive::Tar->error()."\n " ); + } + } + + if ( $archError ) { + return( 0 ); + } else { + if ( $Config{ZM_UPLOAD_PROTOCOL} eq 'ftp' ) { + Info( 'Uploading to '.$Config{ZM_UPLOAD_HOST}." using FTP\n" ); + my $ftp = Net::FTP->new( + $Config{ZM_UPLOAD_HOST}, + Timeout=>$Config{ZM_UPLOAD_TIMEOUT}, + Passive=>$Config{ZM_UPLOAD_FTP_PASSIVE}, + Debug=>$Config{ZM_UPLOAD_DEBUG} + ); + if ( !$ftp ) { + Error( "Can't create FTP connection: $@" ); + return( 0 ); + } + $ftp->login( $Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS} ) + or Error( "FTP - Can't login" ); + $ftp->binary() + or Error( "FTP - Can't go binary" ); + $ftp->cwd( $Config{ZM_UPLOAD_REM_DIR} ) + or Error( "FTP - Can't cwd" ) + if ( $Config{ZM_UPLOAD_REM_DIR} ); + $ftp->put( $archLocPath ) + or Error( "FTP - Can't upload '$archLocPath'" ); + $ftp->quit() + or Error( "FTP - Can't quit" ); + } else { + my $host = $Config{ZM_UPLOAD_HOST}; + $host .= ':'.$Config{ZM_UPLOAD_PORT} + if $Config{ZM_UPLOAD_PORT}; + Info( 'Uploading to '.$host." using SFTP\n" ); + my %sftpOptions = ( host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} ); + $sftpOptions{password} = $Config{ZM_UPLOAD_PASS} + if $Config{ZM_UPLOAD_PASS}; + $sftpOptions{port} = $Config{ZM_UPLOAD_PORT} + if $Config{ZM_UPLOAD_PORT}; + $sftpOptions{timeout} = $Config{ZM_UPLOAD_TIMEOUT} + if $Config{ZM_UPLOAD_TIMEOUT}; + my @more_ssh_args; + push @more_ssh_args, '-o'=>'StrictHostKeyChecking=no' + if ! $Config{ZM_UPLOAD_STRICT}; + push @more_ssh_args, '-v' + if $Config{ZM_UPLOAD_DEBUG}; + $sftpOptions{more} = [@more_ssh_args]; + my $sftp = Net::SFTP::Foreign->new( $Config{ZM_UPLOAD_HOST}, %sftpOptions ); + if ( $sftp->error ) { + Error( "Can't create SFTP connection: ".$sftp->error ); + return( 0 ); + } + $sftp->setcwd( $Config{ZM_UPLOAD_REM_DIR} ) + or Error( "SFTP - Can't setcwd: ".$sftp->error ) + if $Config{ZM_UPLOAD_REM_DIR}; + $sftp->put( $archLocPath, $archFile ) + or Error( "SFTP - Can't upload '$archLocPath': ".$sftp->error ); + } + unlink( $archLocPath ); + my $sql = 'update Events set Uploaded = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } + return( 1 ); +} + +sub substituteTags { + my $text = shift; + my $filter = shift; + my $event = shift; + my $attachments_ref = shift; + +# First we'd better check what we need to get +# We have a filter and an event, do we need any more +# monitor information? + my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; + + my $monitor = {}; + if ( $need_monitor ) { + my $db_now = strftime( "%Y-%m-%d %H:%M:%S", localtime() ); + my $sql = "SELECT + M.Id, + count(E.Id) as EventCount, + count(if(E.Archived,1,NULL)) + as ArchEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 1 HOUR && E.Archived = 0,1,NULL)) + as HourEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 1 DAY && E.Archived = 0,1,NULL)) + as DayEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 7 DAY && E.Archived = 0,1,NULL)) + as WeekEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 1 MONTH && E.Archived = 0,1,NULL)) + as MonthEventCount + FROM Monitors as M LEFT JOIN Events as E on E.MonitorId = M.Id + WHERE MonitorId = ? + GROUP BY E.MonitorId + ORDER BY Id" + ; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{MonitorId} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $monitor = $sth->fetchrow_hashref(); + $sth->finish(); + return() if ( !$monitor ); + } + +# Do we need the image information too? + my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA)%/; + my $first_alarm_frame; + my $max_alarm_frame; + my $max_alarm_score = 0; + if ( $need_images ) { + my $sql = "SELECT * FROM Frames + WHERE EventId = ? AND Type = 'Alarm' + ORDER BY FrameId" + ; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + while( my $frame = $sth->fetchrow_hashref() ) { + if ( !$first_alarm_frame ) { + $first_alarm_frame = $frame; + } + if ( $frame->{Score} > $max_alarm_score ) { + $max_alarm_frame = $frame; + $max_alarm_score = $frame->{Score}; + } } $sth->finish(); - Debug( "Got " . @filters . " filters" ); - return( \@filters ); + } + + my $url = $Config{ZM_URL}; + $text =~ s/%ZP%/$url/g; + $text =~ s/%MN%/$event->{MonitorName}/g; + $text =~ s/%MET%/$monitor->{EventCount}/g; + $text =~ s/%MEH%/$monitor->{HourEventCount}/g; + $text =~ s/%MED%/$monitor->{DayEventCount}/g; + $text =~ s/%MEW%/$monitor->{WeekEventCount}/g; + $text =~ s/%MEM%/$monitor->{MonthEventCount}/g; + $text =~ s/%MEA%/$monitor->{ArchEventCount}/g; + $text =~ s/%MP%/$url?view=watch&mid=$event->{MonitorId}/g; + $text =~ s/%MPS%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=stream/g; + $text =~ s/%MPI%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=still/g; + $text =~ s/%EP%/$url?view=event&mid=$event->{MonitorId}&eid=$event->{Id}/g; + $text =~ s/%EPS%/$url?view=event&mode=stream&mid=$event->{MonitorId}&eid=$event->{Id}/g; + $text =~ s/%EPI%/$url?view=event&mode=still&mid=$event->{MonitorId}&eid=$event->{Id}/g; + $text =~ s/%EI%/$event->{Id}/g; + $text =~ s/%EN%/$event->{Name}/g; + $text =~ s/%EC%/$event->{Cause}/g; + $text =~ s/%ED%/$event->{Notes}/g; + $text =~ s/%ET%/$event->{StartTime}/g; + $text =~ s/%EL%/$event->{Length}/g; + $text =~ s/%EF%/$event->{Frames}/g; + $text =~ s/%EFA%/$event->{AlarmFrames}/g; + $text =~ s/%EST%/$event->{TotScore}/g; + $text =~ s/%ESA%/$event->{AvgScore}/g; + $text =~ s/%ESM%/$event->{MaxScore}/g; + + if ( $first_alarm_frame ) { + $text =~ s/%EPI1%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$first_alarm_frame->{FrameId}/g; + $text =~ s/%EPIM%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$max_alarm_frame->{FrameId}/g; + if ( $attachments_ref && $text =~ s/%EI1%//g ) { + my $path = generateImage( $event, $first_alarm_frame ); + if ( -e $path ) { + push( @$attachments_ref, { type=>'image/jpeg', path=>$path } ); + } + } + + if ( $attachments_ref && $text =~ s/%EIM%//g ) { + # Don't attach the same image twice + if ( !@$attachments_ref + || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) + ) { + my $path = generateImage( $event, $max_alarm_frame ); + if ( -e $path ) { + push( @$attachments_ref, { type=>'image/jpeg', path=>$path } ); + } + } + } + if ( $attachments_ref && $text =~ s/%EI1A%//g ) { + my $path = generateImage( $event, $first_alarm_frame, 'analyse' ); + if ( -e $path ) { + push( @$attachments_ref, { type=>'image/jpeg', path=>$path } ); + } + } + if ( $attachments_ref && $text =~ s/%EIMA%//g ) { + # Don't attach the same image twice + if ( !@$attachments_ref + || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) + ) { + my $path = generateImage( $event, $max_alarm_frame, 'analyse'); + if ( -e $path ) { + push( @$attachments_ref, { type=>'image/jpeg', path=>$path } ); + } + } + } + } # end if $first_alarm_frame + + if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} ) { + if ( $text =~ s/%EV%//g ) { + my ( $format, $path ) = generateVideo( $filter, $event ); + if ( !$format ) { + return( undef ); + } + push( @$attachments_ref, { type=>"video/$format", path=>$path } ); + } + if ( $text =~ s/%EVM%//g ) { + my ( $format, $path ) = generateVideo( $filter, $event, 1 ); + if ( !$format ) { + return( undef ); + } + push( @$attachments_ref, { type=>"video/$format", path=>$path } ); + } + } + $text =~ s/%FN%/$filter->{Name}/g; + ( my $filter_name = $filter->{Name} ) =~ s/ /+/g; + $text =~ s/%FP%/$url?view=filter&mid=$event->{MonitorId}&filter_name=$filter_name/g; + + return( $text ); +} # end subsitituteTags + +sub sendEmail { + my $filter = shift; + my $event = shift; + + if ( ! $Config{ZM_FROM_EMAIL} ) { + Error( "No 'from' email address defined, not sending email" ); + return( 0 ); + } + if ( ! $Config{ZM_EMAIL_ADDRESS} ) { + Error( 'No email address defined, not sending email' ); + return( 0 ); + } + + Info( "Creating notification email\n" ); + + my $subject = substituteTags( $Config{ZM_EMAIL_SUBJECT}, $filter, $event ); + return( 0 ) if ( !$subject ); + my @attachments; + my $body = substituteTags( $Config{ZM_EMAIL_BODY}, $filter, $event, \@attachments ); + return( 0 ) if ( !$body ); + + Info( "Sending notification email '$subject'\n" ); + + eval { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { +### Create the multipart container + my $mail = MIME::Lite->new ( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_EMAIL_ADDRESS}, + Subject => $subject, + Type => 'multipart/mixed' + ); +### Add the text message part + $mail->attach ( + Type => 'TEXT', + Data => $body + ); +### Add the attachments + foreach my $attachment ( @attachments ) { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Disposition => 'attachment' + ); + } +### Send the Message + if ( $Config{ZM_SSMTP_MAIL} ) { + my $ssmtp_location = $Config{ZM_SSMTP_PATH}; + if ( !$ssmtp_location ) { + if ( logDebugging() ) { + Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); + } + $ssmtp_location = qx('which ssmtp'); + } + if ( !$ssmtp_location ) { + Debug( "Can't find ssmtp, trying MIME::Lite->send" ); + MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } else { +### Send using SSMTP + $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS} ); + } + } else { + MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } + } else { + my $mail = MIME::Entity->build( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_EMAIL_ADDRESS}, + Subject => $subject, + Type => (($body=~//)?'text/html':'text/plain'), + Data => $body + ); + + foreach my $attachment ( @attachments ) { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Encoding => 'base64' + ); + } + $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} ); + } + }; + if ( $@ ) { + Error( "Can't send email: $@" ); + return( 0 ); + } else { + Info( "Notification email sent\n" ); + } + my $sql = 'update Events set Emailed = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + return( 1 ); } -sub checkFilter -{ - my $filter = shift; +sub sendMessage { + my $filter = shift; + my $event = shift; - Debug( "Checking filter '$filter->{Name}'". - ($filter->{AutoDelete}?", delete":""). - ($filter->{AutoArchive}?", archive":""). - ($filter->{AutoVideo}?", video":""). - ($filter->{AutoUpload}?", upload":""). - ($filter->{AutoEmail}?", email":""). - ($filter->{AutoMessage}?", message":""). - ($filter->{AutoExecute}?", execute":""). - "\n" - ); + if ( ! $Config{ZM_FROM_EMAIL} ) { + Error( "No 'from' email address defined, not sending message" ); + return( 0 ); + } + if ( ! $Config{ZM_MESSAGE_ADDRESS} ) { + Error( 'No message address defined, not sending message' ); + return( 0 ); + } - foreach my $event ( $filter->Execute() ) { - Debug( "Checking event $event->{Id}\n" ); - my $delete_ok = !undef; -$dbh->ping(); - if ( $filter->{AutoArchive} ) - { - Info( "Archiving event $event->{Id}\n" ); - # Do it individually to avoid locking up the table for new events - my $sql = "update Events set Archived = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Error( "Can't execute '$sql': ".$sth->errstr() ); - } - if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) - { - if ( !$event->{Videoed} ) - { - $delete_ok = undef if ( !generateVideo( $filter, $event ) ); - } - } - if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) - { - if ( !$event->{Emailed} ) - { - $delete_ok = undef if ( !sendEmail( $filter, $event ) ); - } - } - if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) - { - if ( !$event->{Messaged} ) - { - $delete_ok = undef if ( !sendMessage( $filter, $event ) ); - } - } - if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) - { - if ( !$event->{Uploaded} ) - { - $delete_ok = undef if ( !uploadArchFile( $filter, $event ) ); - } - } - if ( $filter->{AutoExecute} ) - { - if ( !$event->{Execute} ) - { - $delete_ok = undef if ( !executeCommand( $filter, $event ) ); - } - } - if ( $filter->{AutoDelete} ) - { - if ( $delete_ok ) - { - Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId}\n" ); - # Do it individually to avoid locking up the table for new events - my $sql = "delete from Events where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + Info( "Creating notification message\n" ); - if ( ! $Config{ZM_OPT_FAST_DELETE} ) - { - my $sql = "delete from Frames where EventId = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $subject = substituteTags( $Config{ZM_MESSAGE_SUBJECT}, $filter, $event ); + return( 0 ) if ( !$subject ); + my @attachments; + my $body = substituteTags( $Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments ); + return( 0 ) if ( !$body ); - $sql = "delete from Stats where EventId = ?"; - $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + Info( "Sending notification message '$subject'\n" ); - deleteEventFiles( $event->{Id}, $event->{MonitorId} ); - } - } - else - { - Error( "Unable to delete event $event->{Id} as previous operations failed\n" ); - } + eval { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { +### Create the multipart container + my $mail = MIME::Lite->new ( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_MESSAGE_ADDRESS}, + Subject => $subject, + Type => 'multipart/mixed' + ); +### Add the text message part + $mail->attach ( + Type => 'TEXT', + Data => $body + ); +### Add the attachments + foreach my $attachment ( @attachments ) { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Disposition => 'attachment' + ); + } +### Send the Message + if ( $Config{ZM_SSMTP_MAIL} ) { + my $ssmtp_location = $Config{ZM_SSMTP_PATH}; + if ( !$ssmtp_location ) { + if ( logDebugging() ) { + Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); + } + $ssmtp_location = qx('which ssmtp'); } + if ( !$ssmtp_location ) { + Debug( "Can't find ssmtp, trying MIME::Lite->send" ); + MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } else { +### Send using SSMTP + $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_MESSAGE_ADDRESS} ); + } + } else { + MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } + } else { + my $mail = MIME::Entity->build( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_MESSAGE_ADDRESS}, + Subject => $subject, + Type => (($body=~//)?'text/html':'text/plain'), + Data => $body + ); + + foreach my $attachment ( @attachments ) { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Encoding => 'base64' + ); + } + $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, + MailFrom => $Config{ZM_FROM_EMAIL} + ); } + }; + if ( $@ ) { + Error( "Can't send email: $@" ); + return( 0 ); + } else { + Info( "Notification message sent\n" ); + } + my $sql = 'update Events set Messaged = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + return( 1 ); } -sub generateVideo -{ - my $filter = shift; - my $event = shift; - my $phone = shift; +sub executeCommand { + my $filter = shift; + my $event = shift; - my $rate = $event->{DefaultRate}/100; - my $scale = $event->{DefaultScale}/100; - my $format; + my $event_path = getEventPath( $event ); - my @ffmpeg_formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} ); - my $default_video_format; - my $default_phone_format; - foreach my $ffmpeg_format( @ffmpeg_formats ) - { - if ( $ffmpeg_format =~ /^(.+)\*\*$/ ) - { - $default_phone_format = $1; - } - elsif ( $ffmpeg_format =~ /^(.+)\*$/ ) - { - $default_video_format = $1; - } - } + my $command = $filter->{AutoExecuteCmd}; + $command .= " $event_path"; + $command = substituteTags( $command, $filter, $event ); - if ( $phone && $default_phone_format ) - { - $format = $default_phone_format; - } - elsif ( $default_video_format ) - { - $format = $default_video_format; - } - else - { - $format = $ffmpeg_formats[0]; - } - - my $command = $Config{ZM_PATH_BIN}."/zmvideo.pl -e " - .$event->{Id}." -r ".$rate." -s ".$scale." -f ".$format; - my $output = qx($command); + Info( "Executing '$command'\n" ); + my $output = qx($command); + my $status = $? >> 8; + if ( $status || logDebugging() ) { chomp( $output ); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - Debug( "Output: $output\n" ); - } - if ( $status ) - { - Error( "Video generation '$command' failed with status: $status\n" ); - if ( wantarray() ) - { - return( undef, undef ); - } - return( 0 ); - } - else - { - my $sql = "update Events set Videoed = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - if ( wantarray() ) - { - return( $format, sprintf( "%s/%s", getEventPath( $event ), $output ) ); - } - } - return( 1 ); -} - -sub uploadArchFile -{ - my $filter = shift; - my $event = shift; - - if ( ! $Config{ZM_UPLOAD_HOST} ) - { - Error( "Cannot upload archive as no upload host defined" ); - return( 0 ); - } - - my $archFile = $event->{MonitorName}.'-'.$event->{Id}; - my $archImagePath = getEventPath( $event ) - ."/" - .( - ( $Config{ZM_UPLOAD_ARCH_ANALYSE} ) - ? '{*analyse,*capture}' - : '*capture' - ) - .".jpg" - ; - my @archImageFiles = glob($archImagePath); - my $archLocPath; - - my $archError = 0; - if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" ) - { - $archFile .= '.zip'; - $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; - my $zip = Archive::Zip->new(); - Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); - - my $status = &AZ_OK; - foreach my $imageFile ( @archImageFiles ) - { - Debug( "Adding $imageFile\n" ); - my $member = $zip->addFile( $imageFile ); - if ( !$member ) - { - Error( "Unable to add image file $imageFile to zip archive $archLocPath" ); - $archError = 1; - last; - } - $member->desiredCompressionMethod( $Config{ZM_UPLOAD_ARCH_COMPRESS} - ? &COMPRESSION_DEFLATED - : &COMPRESSION_STORED - ); - } - if ( !$archError ) - { - $status = $zip->writeToFileNamed( $archLocPath ); - - if ( $archError = ($status != &AZ_OK) ) - { - Error( "Zip error: $status\n " ); - } - } - else - { - Error( "Error adding images to zip archive $archLocPath, not writing" ); - } - } - elsif ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "tar" ) - { - if ( $Config{ZM_UPLOAD_ARCH_COMPRESS} ) - { - $archFile .= '.tar.gz'; - } - else - { - $archFile .= '.tar'; - } - $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; - Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); - - if ( $archError = !Archive::Tar->create_archive( - $archLocPath, - $Config{ZM_UPLOAD_ARCH_COMPRESS}, - @archImageFiles - ) - ) - { - Error( "Tar error: ".Archive::Tar->error()."\n " ); - } - } - - if ( $archError ) - { - return( 0 ); - } - else - { - if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" ) - { - Info( "Uploading to ".$Config{ZM_UPLOAD_HOST}." using FTP\n" ); - my $ftp = Net::FTP->new( - $Config{ZM_UPLOAD_HOST}, - Timeout=>$Config{ZM_UPLOAD_TIMEOUT}, - Passive=>$Config{ZM_UPLOAD_FTP_PASSIVE}, - Debug=>$Config{ZM_UPLOAD_DEBUG} - ); - if ( !$ftp ) - { - Error( "Can't create FTP connection: $@" ); - return( 0 ); - } - $ftp->login( $Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS} ) - or Error( "FTP - Can't login" ); - $ftp->binary() - or Error( "FTP - Can't go binary" ); - $ftp->cwd( $Config{ZM_UPLOAD_REM_DIR} ) - or Error( "FTP - Can't cwd" ) - if ( $Config{ZM_UPLOAD_REM_DIR} ); - $ftp->put( $archLocPath ) - or Error( "FTP - Can't upload '$archLocPath'" ); - $ftp->quit() - or Error( "FTP - Can't quit" ); - } - else - { - my $host = $Config{ZM_UPLOAD_HOST}; - $host .= ":".$Config{ZM_UPLOAD_PORT} - if $Config{ZM_UPLOAD_PORT}; - Info( "Uploading to ".$host." using SFTP\n" ); - my %sftpOptions = ( host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} ); - $sftpOptions{password} = $Config{ZM_UPLOAD_PASS} - if $Config{ZM_UPLOAD_PASS}; - $sftpOptions{port} = $Config{ZM_UPLOAD_PORT} - if $Config{ZM_UPLOAD_PORT}; - $sftpOptions{timeout} = $Config{ZM_UPLOAD_TIMEOUT} - if $Config{ZM_UPLOAD_TIMEOUT}; - my @more_ssh_args; - push @more_ssh_args, ['-o'=>'StrictHostKeyChecking=no'] - if ! $Config{ZM_UPLOAD_STRICT}; - push @more_ssh_args, ['-v'] - if $Config{ZM_UPLOAD_DEBUG}; - $sftpOptions{more} = [@more_ssh_args]; - my $sftp = Net::SFTP::Foreign->new( $Config{ZM_UPLOAD_HOST}, %sftpOptions ); - if ( $sftp->error ) - { - Error( "Can't create SFTP connection: ".$sftp->error ); - return( 0 ); - } - $sftp->setcwd( $Config{ZM_UPLOAD_REM_DIR} ) - or Error( "SFTP - Can't setcwd: ".$sftp->error ) - if $Config{ZM_UPLOAD_REM_DIR}; - $sftp->put( $archLocPath, $archFile ) - or Error( "SFTP - Can't upload '$archLocPath': ".$sftp->error ); - } - unlink( $archLocPath ); - my $sql = "update Events set Uploaded = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - } - return( 1 ); -} - -sub substituteTags -{ - my $text = shift; - my $filter = shift; - my $event = shift; - my $attachments_ref = shift; - - # First we'd better check what we need to get - # We have a filter and an event, do we need any more - # monitor information? - my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; - - my $monitor = {}; - if ( $need_monitor ) - { - my $db_now = strftime( "%Y-%m-%d %H:%M:%S", localtime() ); - my $sql = "SELECT - M.Id, - count(E.Id) as EventCount, - count(if(E.Archived,1,NULL)) - as ArchEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 1 HOUR && E.Archived = 0,1,NULL)) - as HourEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 1 DAY && E.Archived = 0,1,NULL)) - as DayEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 7 DAY && E.Archived = 0,1,NULL)) - as WeekEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 1 MONTH && E.Archived = 0,1,NULL)) - as MonthEventCount - FROM Monitors as M LEFT JOIN Events as E on E.MonitorId = M.Id - WHERE MonitorId = ? - GROUP BY E.MonitorId - ORDER BY Id" - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{MonitorId} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - $monitor = $sth->fetchrow_hashref(); - $sth->finish(); - return() if ( !$monitor ); - } - - # Do we need the image information too? - my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM)%/; - my $first_alarm_frame; - my $max_alarm_frame; - my $max_alarm_score = 0; - if ( $need_images ) - { - my $sql = "SELECT * FROM Frames - WHERE EventId = ? AND Type = 'Alarm' - ORDER BY FrameId" - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - while( my $frame = $sth->fetchrow_hashref() ) - { - if ( !$first_alarm_frame ) - { - $first_alarm_frame = $frame; - } - if ( $frame->{Score} > $max_alarm_score ) - { - $max_alarm_frame = $frame; - $max_alarm_score = $frame->{Score}; - } - } - $sth->finish(); - } - - my $url = $Config{ZM_URL}; - $text =~ s/%ZP%/$url/g; - $text =~ s/%MN%/$event->{MonitorName}/g; - $text =~ s/%MET%/$monitor->{EventCount}/g; - $text =~ s/%MEH%/$monitor->{HourEventCount}/g; - $text =~ s/%MED%/$monitor->{DayEventCount}/g; - $text =~ s/%MEW%/$monitor->{WeekEventCount}/g; - $text =~ s/%MEM%/$monitor->{MonthEventCount}/g; - $text =~ s/%MEA%/$monitor->{ArchEventCount}/g; - $text =~ s/%MP%/$url?view=watch&mid=$event->{MonitorId}/g; - $text =~ s/%MPS%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=stream/g; - $text =~ s/%MPI%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=still/g; - $text =~ s/%EP%/$url?view=event&mid=$event->{MonitorId}&eid=$event->{Id}/g; - $text =~ s/%EPS%/$url?view=event&mode=stream&mid=$event->{MonitorId}&eid=$event->{Id}/g; - $text =~ s/%EPI%/$url?view=event&mode=still&mid=$event->{MonitorId}&eid=$event->{Id}/g; - $text =~ s/%EI%/$event->{Id}/g; - $text =~ s/%EN%/$event->{Name}/g; - $text =~ s/%EC%/$event->{Cause}/g; - $text =~ s/%ED%/$event->{Notes}/g; - $text =~ s/%ET%/$event->{StartTime}/g; - $text =~ s/%EL%/$event->{Length}/g; - $text =~ s/%EF%/$event->{Frames}/g; - $text =~ s/%EFA%/$event->{AlarmFrames}/g; - $text =~ s/%EST%/$event->{TotScore}/g; - $text =~ s/%ESA%/$event->{AvgScore}/g; - $text =~ s/%ESM%/$event->{MaxScore}/g; - if ( $first_alarm_frame ) - { - $text =~ s/%EPI1%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$first_alarm_frame->{FrameId}/g; - $text =~ s/%EPIM%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$max_alarm_frame->{FrameId}/g; - if ( $attachments_ref && $text =~ s/%EI1%//g ) - { - push( @$attachments_ref, - { - type=>"image/jpeg", - path=>sprintf( - "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg", - getEventPath( $event ), - $first_alarm_frame->{FrameId} - ) - } - ); - } - if ( $attachments_ref && $text =~ s/%EIM%//g ) - { - # Don't attach the same image twice - if ( !@$attachments_ref - || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) - ) - { - push( @$attachments_ref, - { - type=>"image/jpeg", - path=>sprintf( - "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg", - getEventPath( $event ), - $max_alarm_frame->{FrameId} - ) - } - ); - } - } - } - if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} ) - { - if ( $text =~ s/%EV%//g ) - { - my ( $format, $path ) = generateVideo( $filter, $event ); - if ( !$format ) - { - return( undef ); - } - push( @$attachments_ref, { type=>"video/$format", path=>$path } ); - } - if ( $text =~ s/%EVM%//g ) - { - my ( $format, $path ) = generateVideo( $filter, $event, 1 ); - if ( !$format ) - { - return( undef ); - } - push( @$attachments_ref, { type=>"video/$format", path=>$path } ); - } - } - $text =~ s/%FN%/$filter->{Name}/g; - ( my $filter_name = $filter->{Name} ) =~ s/ /+/g; - $text =~ s/%FP%/$url?view=filter&mid=$event->{MonitorId}&filter_name=$filter_name/g; - - return( $text ); -} - -sub sendEmail -{ - my $filter = shift; - my $event = shift; - - if ( ! $Config{ZM_FROM_EMAIL} ) - { - Error( "No 'from' email address defined, not sending email" ); - return( 0 ); - } - if ( ! $Config{ZM_EMAIL_ADDRESS} ) - { - Error( "No email address defined, not sending email" ); - return( 0 ); - } - - Info( "Creating notification email\n" ); - - my $subject = substituteTags( $Config{ZM_EMAIL_SUBJECT}, $filter, $event ); - return( 0 ) if ( !$subject ); - my @attachments; - my $body = substituteTags( $Config{ZM_EMAIL_BODY}, $filter, $event, \@attachments ); - return( 0 ) if ( !$body ); - - Info( "Sending notification email '$subject'\n" ); - - eval - { - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - ### Create the multipart container - my $mail = MIME::Lite->new ( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_EMAIL_ADDRESS}, - Subject => $subject, - Type => "multipart/mixed" - ); - ### Add the text message part - $mail->attach ( - Type => "TEXT", - Data => $body - ); - ### Add the attachments - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Disposition => "attachment" - ); - } - if ( $Config{ZM_SSMTP_MAIL} ){ - - my $ssmtp_location = $Config{ZM_SSMTP_PATH}; - - if( ! $ssmtp_location ){ - - $ssmtp_location = qx('which ssmtp'); - - if ( logDebugging() ) - { - Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); - } - - } - - $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS} ); - - }else{ - - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } - ### Send the Message - #MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - #$mail->send(); - } - else - { - my $mail = MIME::Entity->build( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_EMAIL_ADDRESS}, - Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), - Data => $body - ); - - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Encoding => "base64" - ); - } - $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} ); - } - }; - if ( $@ ) - { - Error( "Can't send email: $@" ); - return( 0 ); - } - else - { - Info( "Notification email sent\n" ); - } - my $sql = "update Events set Emailed = 1 where Id = ?"; + Debug( "Output: $output\n" ); + } + if ( $status ) { + Error( "Command '$command' exited with status: $status\n" ); + return( 0 ); + } else { + my $sql = 'update Events set Executed = 1 where Id = ?'; my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - return( 1 ); + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } + return( 1 ); } - -sub sendMessage -{ - my $filter = shift; - my $event = shift; - - if ( ! $Config{ZM_FROM_EMAIL} ) - { - Error( "No 'from' email address defined, not sending message" ); - return( 0 ); - } - if ( ! $Config{ZM_MESSAGE_ADDRESS} ) - { - Error( "No message address defined, not sending message" ); - return( 0 ); - } - - Info( "Creating notification message\n" ); - - my $subject = substituteTags( $Config{ZM_MESSAGE_SUBJECT}, $filter, $event ); - return( 0 ) if ( !$subject ); - my @attachments; - my $body = substituteTags( $Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments ); - return( 0 ) if ( !$body ); - - Info( "Sending notification message '$subject'\n" ); - - eval - { - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - ### Create the multipart container - my $mail = MIME::Lite->new ( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_MESSAGE_ADDRESS}, - Subject => $subject, - Type => "multipart/mixed" - ); - ### Add the text message part - $mail->attach ( - Type => "TEXT", - Data => $body - ); - ### Add the attachments - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Disposition => "attachment" - ); - } - ### Send the Message - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } - else - { - my $mail = MIME::Entity->build( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_MESSAGE_ADDRESS}, - Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), - Data => $body - ); - - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Encoding => "base64" - ); - } - $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, - MailFrom => $Config{ZM_FROM_EMAIL} - ); - } - }; - if ( $@ ) - { - Error( "Can't send email: $@" ); - return( 0 ); - } - else - { - Info( "Notification message sent\n" ); - } - my $sql = "update Events set Messaged = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - return( 1 ); -} - -sub executeCommand -{ - my $filter = shift; - my $event = shift; - - my $event_path = getEventPath( $event ); - - my $command = $filter->{AutoExecuteCmd}; - $command .= " $event_path"; - - Info( "Executing '$command'\n" ); - my $output = qx($command); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - chomp( $output ); - Debug( "Output: $output\n" ); - } - if ( $status ) - { - Error( "Command '$command' exited with status: $status\n" ); - return( 0 ); - } - else - { - my $sql = "update Events set Executed = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - } - return( 1 ); -} - diff --git a/scripts/zmpkg.pl.in b/scripts/zmpkg.pl.in index 42de011fe..597d7ac1a 100644 --- a/scripts/zmpkg.pl.in +++ b/scripts/zmpkg.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== @@ -251,10 +251,6 @@ if ( $command =~ /^(?:start|restart)$/ ) } if ( $monitor->{Function} ne 'Monitor' ) { - if ( $Config{ZM_OPT_FRAME_SERVER} ) - { - runCommand( "zmdc.pl start zmf -m $monitor->{Id}" ); - } runCommand( "zmdc.pl start zma -m $monitor->{Id}" ); } if ( $Config{ZM_OPT_CONTROL} ) diff --git a/scripts/zmsystemctl.pl.in b/scripts/zmsystemctl.pl.in index 75acae242..d4de9cfed 100644 --- a/scripts/zmsystemctl.pl.in +++ b/scripts/zmsystemctl.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== diff --git a/scripts/zmtelemetry.pl.in b/scripts/zmtelemetry.pl.in index 5a5328829..39e7b73ec 100644 --- a/scripts/zmtelemetry.pl.in +++ b/scripts/zmtelemetry.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== diff --git a/scripts/zmtrack.pl.in b/scripts/zmtrack.pl.in index cd00e809d..f12f6bff0 100644 --- a/scripts/zmtrack.pl.in +++ b/scripts/zmtrack.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== diff --git a/scripts/zmtrigger.pl.in b/scripts/zmtrigger.pl.in index 2f180e090..0c526c83e 100644 --- a/scripts/zmtrigger.pl.in +++ b/scripts/zmtrigger.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== @@ -196,8 +196,10 @@ foreach my $connection ( @in_select_connections ) my %spawned_connections; my %monitors; - my $monitor_reload_time = 0; +my $needsReload = 0; +loadMonitors(); + $! = undef; my $rin = ''; @@ -313,6 +315,14 @@ while( 1 ) my @out_messages; foreach my $monitor ( values(%monitors) ) { + + if ( ! zmMemVerify($monitor) ) { + # Our attempt to verify the memory handle failed. We should reload the monitors. + # Don't need to zmMemInvalidate because the monitor reload will do it. + $needsReload = 1; + next; + } + my ( $state, $last_event ) = zmMemRead( $monitor, [ "shared_data:state", @@ -413,7 +423,7 @@ while( 1 ) } # If necessary reload monitors - if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) + if ( $needsReload || ((time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL )) { foreach my $monitor ( values(%monitors) ) { @@ -421,6 +431,7 @@ while( 1 ) zmMemInvalidate( $monitor ); } loadMonitors(); + $needsReload = 0; } } Info( "Trigger daemon exiting\n" ); @@ -443,7 +454,11 @@ sub loadMonitors or Fatal( "Can't execute: ".$sth->errstr() ); while( my $monitor = $sth->fetchrow_hashref() ) { - next if ( !zmMemVerify( $monitor ) ); # Check shared memory ok + # Check shared memory ok + if ( !zmMemVerify( $monitor ) ) { + zmMemInvalidate( $monitor ); + next; + } if ( defined($monitors{$monitor->{Id}}->{LastState}) ) { diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index ccc76ba02..b263ab065 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== @@ -27,7 +27,7 @@ zmupdate.pl - check and upgrade ZoneMinder database =head1 SYNOPSIS - zmupdate.pl -c,--check | -f,--freshen | -v,--version= [-u -p] +zmupdate.pl -c,--check | -f,--freshen | -v,--version= [-u -p] =head1 DESCRIPTION @@ -37,15 +37,15 @@ configuring upgrades etc, including on the fly upgrades. =head1 OPTIONS - -c, --check - Check for updated versions of ZoneMinder - -f, --freshen - Freshen the configuration in the database. Equivalent of old zmconfig.pl -noi - --migrate-events - Update database structures as per USE_DEEP_STORAGE setting. - -v, --version= - Force upgrade to the current version from - -u, --user= - Alternate DB user with privileges to alter DB - -p, --pass= - Password of alternate DB user with privileges to alter DB - -d,--dir= - Directory containing update files if not in default build location - -interactive - interact with the user - -nointeractive - do not interact with the user +-c, --check - Check for updated versions of ZoneMinder +-f, --freshen - Freshen the configuration in the database. Equivalent of old zmconfig.pl -noi +--migrate-events - Update database structures as per USE_DEEP_STORAGE setting. +-v, --version= - Force upgrade to the current version from +-u, --user= - Alternate DB user with privileges to alter DB +-p, --pass= - Password of alternate DB user with privileges to alter DB +-d,--dir= - Directory containing update files if not in default build location +-interactive - interact with the user +-nointeractive - do not interact with the user =cut use strict; @@ -72,7 +72,6 @@ use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::General qw(:all); use ZoneMinder::Database qw(:all); -use ZoneMinder::ConfigAdmin qw( :functions ); use POSIX; use DBI; use Getopt::Long; @@ -115,969 +114,858 @@ GetOptions( 'user:s' =>\$dbUser, 'pass:s' =>\$dbPass, 'dir:s' =>\$updateDir -) or pod2usage(-exitstatus => -1); + ) or pod2usage(-exitstatus => -1); my $dbh = zmDbConnect(); $Config{ZM_DB_USER} = $dbUser; $Config{ZM_DB_PASS} = $dbPass; -if ( ! ($check || $freshen || $rename || $zoneFix || $migrateEvents || $version) ) -{ - if ( $Config{ZM_DYN_DB_VERSION} ) - { - $version = $Config{ZM_DYN_DB_VERSION}; - } - else - { - print( STDERR "Please give a valid option\n" ); - pod2usage(-exitstatus => -1); - } -} - -if ( ($check + $freshen + $rename + $zoneFix + $migrateEvents + ($version?1:0)) > 1 ) -{ - print( STDERR "Please give only one option\n" ); +if ( ! ($check || $freshen || $rename || $zoneFix || $migrateEvents || $version) ) { + if ( $Config{ZM_DYN_DB_VERSION} ) { + $version = $Config{ZM_DYN_DB_VERSION}; + } else { + print( STDERR "Please give a valid option\n" ); pod2usage(-exitstatus => -1); + } } -if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) -{ - print( "Update agent starting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); - - my $currVersion = $Config{ZM_DYN_CURR_VERSION}; - my $lastVersion = $Config{ZM_DYN_LAST_VERSION}; - my $lastCheck = $Config{ZM_DYN_LAST_CHECK}; - - if ( !$currVersion ) - { - $currVersion = $Config{ZM_VERSION}; - - my $sql = "update Config set Value = ? where Name = 'ZM_DYN_CURR_VERSION'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( "$currVersion" ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); - } - - while( 1 ) - { - my $now = time(); - if ( !$lastVersion || !$lastCheck || (($now-$lastCheck) > CHECK_INTERVAL) ) - { - Info( "Checking for updates\n" ); - - use LWP::UserAgent; - my $ua = LWP::UserAgent->new; - $ua->agent( "ZoneMinder Update Agent/".ZM_VERSION ); - if ( $Config{ZM_UPDATE_CHECK_PROXY} ) { - $ua->proxy( "http", $Config{ZM_UPDATE_CHECK_PROXY} ); - } - my $req = HTTP::Request->new( GET=>'https://update.zoneminder.com/version.txt' ); - my $res = $ua->request($req); - - if ( $res->is_success ) - { - $lastVersion = $res->content; - chomp($lastVersion); - $lastCheck = $now; - - Info( "Got version: '".$lastVersion."'\n" ); - - my $lv_sql = "update Config set Value = ? where Name = 'ZM_DYN_LAST_VERSION'"; - my $lv_sth = $dbh->prepare_cached( $lv_sql ) or die( "Can't prepare '$lv_sql': ".$dbh->errstr() ); - my $lv_res = $lv_sth->execute( $lastVersion ) or die( "Can't execute: ".$lv_sth->errstr() ); - $lv_sth->finish(); - - my $lc_sql = "update Config set Value = ? where Name = 'ZM_DYN_LAST_CHECK'"; - my $lc_sth = $dbh->prepare_cached( $lc_sql ) or die( "Can't prepare '$lc_sql': ".$dbh->errstr() ); - my $lc_res = $lc_sth->execute( $lastCheck ) or die( "Can't execute: ".$lc_sth->errstr() ); - $lc_sth->finish(); - } - else - { - Error( "Error check failed: '".$res->status_line()."'\n" ); - } - } - sleep( 3600 ); - } - print( "Update agent exiting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); +if ( ($check + $freshen + $rename + $zoneFix + $migrateEvents + ($version?1:0)) > 1 ) { + print( STDERR "Please give only one option\n" ); + pod2usage(-exitstatus => -1); } -if ( $rename ) -{ - require File::Find; - chdir( EVENT_PATH ); +if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { + print( "Update agent starting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); - sub renameImage - { - my $file = $_; + my $currVersion = $Config{ZM_DYN_CURR_VERSION}; + my $lastVersion = $Config{ZM_DYN_LAST_VERSION}; + my $lastCheck = $Config{ZM_DYN_LAST_CHECK}; - # Ignore directories - if ( -d $file ) - { - print( "Checking directory '$file'\n" ); - return; - } - if ( $file !~ /(capture|analyse)-(\d+)(\.jpg)/ ) - { - return; - } - my $newFile = "$2-$1$3"; + if ( !$currVersion ) { + $currVersion = $Config{ZM_VERSION}; - print( "Renaming '$file' to '$newFile'\n" ); - rename( $file, $newFile ) or warn( "Can't rename '$file' to '$newFile'" ); - } - - File::Find::find( \&renameImage, '.' ); -} -if ( $zoneFix ) -{ - - my $sql = "select Z.*, M.Width as MonitorWidth, M.Height as MonitorHeight from Zones as Z inner join Monitors as M on Z.MonitorId = M.Id where Z.Units = 'Percent'"; + my $sql = "update Config set Value = ? where Name = 'ZM_DYN_CURR_VERSION'"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @zones; - while( my $zone = $sth->fetchrow_hashref() ) - { - push( @zones, $zone ); - } + my $res = $sth->execute( "$currVersion" ) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); + } - $sql = "update Zones set MinAlarmPixels = ?, MaxAlarmPixels = ?, MinFilterPixels = ?, MaxFilterPixels = ?, MinBlobPixels = ?, MaxBlobPixels = ? where Id = ?"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - foreach my $zone ( @zones ) - { - my $zone_width = (($zone->{HiX}*$zone->{MonitorWidth})-($zone->{LoX}*$zone->{MonitorWidth}))/100; - my $zone_height = (($zone->{HiY}*$zone->{MonitorHeight})-($zone->{LoY}*$zone->{MonitorHeight}))/100; - my $zone_area = $zone_width * $zone_height; - my $monitor_area = $zone->{MonitorWidth} * $zone->{MonitorHeight}; - my $res = $sth->execute( - ($zone->{MinAlarmPixels}*$monitor_area)/$zone_area, - ($zone->{MaxAlarmPixels}*$monitor_area)/$zone_area, - ($zone->{MinFilterPixels}*$monitor_area)/$zone_area, - ($zone->{MaxFilterPixels}*$monitor_area)/$zone_area, - ($zone->{MinBlobPixels}*$monitor_area)/$zone_area, - ($zone->{MaxBlobPixels}*$monitor_area)/$zone_area, - $zone->{Id} + while( 1 ) { + my $now = time(); + if ( !$lastVersion || !$lastCheck || (($now-$lastCheck) > CHECK_INTERVAL) ) { + Info( "Checking for updates\n" ); + + use LWP::UserAgent; + my $ua = LWP::UserAgent->new; + $ua->agent( "ZoneMinder Update Agent/".ZM_VERSION ); + if ( $Config{ZM_UPDATE_CHECK_PROXY} ) { + $ua->proxy( "http", $Config{ZM_UPDATE_CHECK_PROXY} ); + } + my $req = HTTP::Request->new( GET=>'https://update.zoneminder.com/version.txt' ); + my $res = $ua->request($req); + + if ( $res->is_success ) { + $lastVersion = $res->content; + chomp($lastVersion); + $lastCheck = $now; + + Info( "Got version: '".$lastVersion."'\n" ); + + my $lv_sql = "update Config set Value = ? where Name = 'ZM_DYN_LAST_VERSION'"; + my $lv_sth = $dbh->prepare_cached( $lv_sql ) or die( "Can't prepare '$lv_sql': ".$dbh->errstr() ); + my $lv_res = $lv_sth->execute( $lastVersion ) or die( "Can't execute: ".$lv_sth->errstr() ); + $lv_sth->finish(); + + my $lc_sql = "update Config set Value = ? where Name = 'ZM_DYN_LAST_CHECK'"; + my $lc_sth = $dbh->prepare_cached( $lc_sql ) or die( "Can't prepare '$lc_sql': ".$dbh->errstr() ); + my $lc_res = $lc_sth->execute( $lastCheck ) or die( "Can't execute: ".$lc_sth->errstr() ); + $lc_sth->finish(); + } else { + Error( "Error check failed: '".$res->status_line()."'\n" ); + } + } + sleep( 3600 ); + } + print( "Update agent exiting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); +} + +if ( $rename ) { + require File::Find; + + chdir( EVENT_PATH ); + + sub renameImage { + my $file = $_; + +# Ignore directories + if ( -d $file ) { + print( "Checking directory '$file'\n" ); + return; + } + if ( $file !~ /(capture|analyse)-(\d+)(\.jpg)/ ) { + return; + } + my $newFile = "$2-$1$3"; + + print( "Renaming '$file' to '$newFile'\n" ); + rename( $file, $newFile ) or warn( "Can't rename '$file' to '$newFile'" ); + } + + File::Find::find( \&renameImage, '.' ); +} +if ( $zoneFix ) { + + my $sql = "select Z.*, M.Width as MonitorWidth, M.Height as MonitorHeight from Zones as Z inner join Monitors as M on Z.MonitorId = M.Id where Z.Units = 'Percent'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @zones; + while( my $zone = $sth->fetchrow_hashref() ) { + push( @zones, $zone ); + } + $sth->finish(); + + $sql = "update Zones set MinAlarmPixels = ?, MaxAlarmPixels = ?, MinFilterPixels = ?, MaxFilterPixels = ?, MinBlobPixels = ?, MaxBlobPixels = ? where Id = ?"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + foreach my $zone ( @zones ) { + my $zone_width = (($zone->{HiX}*$zone->{MonitorWidth})-($zone->{LoX}*$zone->{MonitorWidth}))/100; + my $zone_height = (($zone->{HiY}*$zone->{MonitorHeight})-($zone->{LoY}*$zone->{MonitorHeight}))/100; + my $zone_area = $zone_width * $zone_height; + my $monitor_area = $zone->{MonitorWidth} * $zone->{MonitorHeight}; + my $res = $sth->execute( + ($zone->{MinAlarmPixels}*$monitor_area)/$zone_area, + ($zone->{MaxAlarmPixels}*$monitor_area)/$zone_area, + ($zone->{MinFilterPixels}*$monitor_area)/$zone_area, + ($zone->{MaxFilterPixels}*$monitor_area)/$zone_area, + ($zone->{MinBlobPixels}*$monitor_area)/$zone_area, + ($zone->{MaxBlobPixels}*$monitor_area)/$zone_area, + $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); + } + $sth->finish(); +} +if ( $migrateEvents ) { + my $webUid = (getpwnam( $Config{ZM_WEB_USER} ))[2]; + my $webGid = (getgrnam( $Config{ZM_WEB_USER} ))[2]; + + if ( !(($> == 0) || ($> == $webUid)) ) { + print( "Error, migrating events can only be done as user root or ".$Config{ZM_WEB_USER}.".\n" ); + exit( -1 ); + } + +# Run as web user/group + $( = $webGid; + $) = $webGid; + $< = $webUid; + $> = $webUid; + + print( "\nAbout to convert saved events to deep storage, please ensure that ZoneMinder is fully stopped before proceeding.\nThis process is not easily reversible. Are you sure you wish to proceed?\n\nPress 'y' to continue or 'n' to abort : " ); + my $response = ; + chomp( $response ); + while ( $response !~ /^[yYnN]$/ ) { + print( "Please press 'y' to continue or 'n' to abort only : " ); + $response = ; + chomp( $response ); + } + + if ( $response =~ /^[yY]$/ ) { + print( "Converting all events to deep storage.\n" ); + + chdir( $Config{ZM_PATH_WEB} ); + my $sql = "select *, unix_timestamp(StartTime) as UnixStartTime from Events"; + my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute(); + if ( !$res ) { + Fatal( "Can't fetch Events: ".$sth->errstr() ); + } + + while( my $event = $sth->fetchrow_hashref() ) { + my $oldEventPath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.$event->{Id}; + + if ( !-d $oldEventPath ) { + print( "Warning, can't find old event path '$oldEventPath', already converted?\n" ); + next; + } + + print( "Converting event ".$event->{Id}."\n" ); + my $newDatePath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.strftime( "%y/%m/%d", localtime($event->{UnixStartTime}) ); + my $newTimePath = strftime( "%H/%M/%S", localtime($event->{UnixStartTime}) ); + my $newEventPath = $newDatePath.'/'.$newTimePath; + ( my $truncEventPath = $newEventPath ) =~ s|/\d+$||; + makePath( $truncEventPath, $Config{ZM_PATH_WEB} ); + my $idLink = $newDatePath.'/.'.$event->{Id}; + symlink( $newTimePath, $idLink ) or die( "Can't symlink $newTimePath -> $idLink: $!" ); + rename( $oldEventPath, $newEventPath ) or die( "Can't move $oldEventPath -> $newEventPath: $!" ); } $sth->finish(); + + print( "Updating configuration.\n" ); + $sql = "update Config set Value = ? where Name = 'ZM_USE_DEEP_STORAGE'"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute( 1 ) or die( "Can't execute: ".$sth->errstr() ); + $sth->finish(); + + print( "All events converted.\n\n" ); + } else { + print( "Aborting event conversion.\n\n" ); + } } -if ( $migrateEvents ) -{ - my $webUid = (getpwnam( $Config{ZM_WEB_USER} ))[2]; - my $webGid = (getgrnam( $Config{ZM_WEB_USER} ))[2]; - - if ( !(($> == 0) || ($> == $webUid)) ) - { - print( "Error, migrating events can only be done as user root or ".$Config{ZM_WEB_USER}.".\n" ); - exit( -1 ); - } - - # Run as web user/group - $( = $webGid; - $) = $webGid; - $< = $webUid; - $> = $webUid; - - print( "\nAbout to convert saved events to deep storage, please ensure that ZoneMinder is fully stopped before proceeding.\nThis process is not easily reversible. Are you sure you wish to proceed?\n\nPress 'y' to continue or 'n' to abort : " ); - my $response = ; - chomp( $response ); - while ( $response !~ /^[yYnN]$/ ) - { - print( "Please press 'y' to continue or 'n' to abort only : " ); - $response = ; - chomp( $response ); - } - - if ( $response =~ /^[yY]$/ ) - { - print( "Converting all events to deep storage.\n" ); - - chdir( $Config{ZM_PATH_WEB} ); - my $sql = "select *, unix_timestamp(StartTime) as UnixStartTime from Events"; - my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute(); - if ( !$res ) - { - Fatal( "Can't fetch Events: ".$sth->errstr() ); - } - - while( my $event = $sth->fetchrow_hashref() ) - { - my $oldEventPath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.$event->{Id}; - - if ( !-d $oldEventPath ) - { - print( "Warning, can't find old event path '$oldEventPath', already converted?\n" ); - next; - } - - print( "Converting event ".$event->{Id}."\n" ); - my $newDatePath = $Config{ZM_DIR_EVENTS}.'/'.$event->{MonitorId}.'/'.strftime( "%y/%m/%d", localtime($event->{UnixStartTime}) ); - my $newTimePath = strftime( "%H/%M/%S", localtime($event->{UnixStartTime}) ); - my $newEventPath = $newDatePath.'/'.$newTimePath; - ( my $truncEventPath = $newEventPath ) =~ s|/\d+$||; - makePath( $truncEventPath, $Config{ZM_PATH_WEB} ); - my $idLink = $newDatePath.'/.'.$event->{Id}; - symlink( $newTimePath, $idLink ) or die( "Can't symlink $newTimePath -> $idLink: $!" ); - rename( $oldEventPath, $newEventPath ) or die( "Can't move $oldEventPath -> $newEventPath: $!" ); - } - $sth->finish(); - - print( "Updating configuration.\n" ); - $sql = "update Config set Value = ? where Name = 'ZM_USE_DEEP_STORAGE'"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute( 1 ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); - - print( "All events converted.\n\n" ); - } - else - { - print( "Aborting event conversion.\n\n" ); - } -} -if ( $freshen ) -{ - print( "\nFreshening configuration in database\n" ); - ZoneMinder::Config::loadConfigFromDB(); - ZoneMinder::Config::saveConfigToDB(); +if ( $freshen ) { + print( "\nFreshening configuration in database\n" ); + ZoneMinder::Config::loadConfigFromDB(); + ZoneMinder::Config::saveConfigToDB(); } # Don't do innoDB upgrade if not interactive if ( $interactive ) { - # Now check for MyISAM Tables - my @MyISAM_Tables; - my $sql = "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='zm' AND engine = 'MyISAM'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); +# Now check for MyISAM Tables + my @MyISAM_Tables; + my $sql = "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='zm' AND engine = 'MyISAM'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - while( my $dbTable = $sth->fetchrow() ) { - push @MyISAM_Tables, $dbTable; - } - $sth->finish(); + while( my $dbTable = $sth->fetchrow() ) { + push @MyISAM_Tables, $dbTable; + } + $sth->finish(); - if ( @MyISAM_Tables ) { - print( "\nPrevious versions of ZoneMinder used the MyISAM database engine.\nHowever, the recommended database engine is InnoDB.\n"); - print( "\nHint: InnoDB tables are much less likely to be corrupted during an unclean shutdown.\n\nPress 'y' to convert your tables to InnoDB or 'n' to skip : "); - my $response = ; - chomp( $response ); - if ( $response =~ /^[yY]$/ ) { - $dbh->do(q|SET sql_mode='traditional'|); # Elevate warnings to errors - print "\nConverting MyISAM tables to InnoDB. Please wait.\n"; - foreach (@MyISAM_Tables) { + if ( @MyISAM_Tables ) { + print( "\nPrevious versions of ZoneMinder used the MyISAM database engine.\nHowever, the recommended database engine is InnoDB.\n"); + print( "\nHint: InnoDB tables are much less likely to be corrupted during an unclean shutdown.\n\nPress 'y' to convert your tables to InnoDB or 'n' to skip : "); + my $response = ; + chomp( $response ); + if ( $response =~ /^[yY]$/ ) { + $dbh->do(q|SET sql_mode='traditional'|); # Elevate warnings to errors + print "\nConverting MyISAM tables to InnoDB. Please wait.\n"; + foreach (@MyISAM_Tables) { my $sql = "ALTER TABLE $_ ENGINE = InnoDB"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); - } - $dbh->do(q|SET sql_mode=''|); # Set mode back to default - } - } + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + $sth->finish(); + } + $dbh->do(q|SET sql_mode=''|); # Set mode back to default + } + } } # end if interactive -if ( $version ) -{ - my ( $detaint_version ) = $version =~ /^([\w.]+)$/; - $version = $detaint_version; +if ( $version ) { + my ( $detaint_version ) = $version =~ /^([\w.]+)$/; + $version = $detaint_version; - if ( ZM_VERSION eq $version ) - { - print( "\nDatabase already at version $version, update aborted.\n\n" ); - exit( 0 ); + if ( ZM_VERSION eq $version ) { + print( "\nDatabase already at version $version, update aborted.\n\n" ); + exit( 0 ); + } + + print( "\nInitiating database upgrade to version ".ZM_VERSION." from version $version\n" ); + if ( $interactive ) { + if ( $Config{ZM_DYN_DB_VERSION} && $Config{ZM_DYN_DB_VERSION} ne $version ) { + print( "\nWARNING - You have specified an upgrade from version $version but the database version found is ".$Config{ZM_DYN_DB_VERSION}.". Is this correct?\nPress enter to continue or ctrl-C to abort : " ); + my $response = ; } - print( "\nInitiating database upgrade to version ".ZM_VERSION." from version $version\n" ); - if ( $interactive ) - { - if ( $Config{ZM_DYN_DB_VERSION} && $Config{ZM_DYN_DB_VERSION} ne $version ) - { - print( "\nWARNING - You have specified an upgrade from version $version but the database version found is ".$Config{ZM_DYN_DB_VERSION}.". Is this correct?\nPress enter to continue or ctrl-C to abort : " ); - my $response = ; - } + print( "\nPlease ensure that ZoneMinder is stopped on your system prior to upgrading the database.\nPress enter to continue or ctrl-C to stop : " ); + my $response = ; - print( "\nPlease ensure that ZoneMinder is stopped on your system prior to upgrading the database.\nPress enter to continue or ctrl-C to stop : " ); - my $response = ; - - print( "\nDo you wish to take a backup of your database prior to upgrading?\nThis may result in a large file in @ZM_TMPDIR@ if you have a lot of events.\nPress 'y' for a backup or 'n' to continue : " ); - $response = ; - chomp( $response ); - while ( $response !~ /^[yYnN]$/ ) - { - print( "Please press 'y' for a backup or 'n' to continue only : " ); - $response = ; - chomp( $response ); - } - - if ( $response =~ /^[yY]$/ ) - { - my ( $host, $port ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); - my $command = "mysqldump"; - $command .= " -h".$host if defined($host); - $command .= " -P".$port if defined($port); - if ( $dbUser ) - { - $command .= " -u".$dbUser; - if ( $dbPass ) - { - $command .= " -p".$dbPass; - } - } - my $backup = "@ZM_TMPDIR@/".$Config{ZM_DB_NAME}."-".$version.".dump"; - $command .= " --add-drop-table --databases ".$Config{ZM_DB_NAME}." > ".$backup; - print( "Creating backup to $backup. This may take several minutes.\n" ); - print( "Executing '$command'\n" ) if ( logDebugging() ); - my $output = qx($command); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - chomp( $output ); - print( "Output: $output\n" ); - } - if ( $status ) - { - die( "Command '$command' exited with status: $status\n" ); - } - else - { - print( "Database successfully backed up to $backup, proceeding to upgrade.\n" ); - } - } - elsif ( $response !~ /^[nN]$/ ) - { - die( "Unexpected response '$response'" ); - } - } - sub patchDB - { - my $dbh = shift; - my $version = shift; - - my ( $host, $port ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); - my $command = "mysql -h".$host; - $command .= " -P".$port if defined($port); - if ( $dbUser ) - { - $command .= " -u".$dbUser; - if ( $dbPass ) - { - $command .= " -p".$dbPass; - } - } - $command .= " ".$Config{ZM_DB_NAME}." < "; - if ( $updateDir ) - { - $command .= $updateDir; - } - else - { - $command .= $Config{ZM_PATH_DATA}."/db"; - } - $command .= "/zm_update-".$version.".sql"; - - print( "Executing '$command'\n" ) if ( logDebugging() ); - my $output = qx($command); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - chomp( $output ); - print( "Output: $output\n" ); - } - if ( $status ) - { - die( "Command '$command' exited with status: $status\n" ); - } - else - { - print( "\nDatabase successfully upgraded to version $version.\n" ); - my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() ); - } + print( "\nDo you wish to take a backup of your database prior to upgrading?\nThis may result in a large file in @ZM_TMPDIR@ if you have a lot of events.\nPress 'y' for a backup or 'n' to continue : " ); + $response = ; + chomp( $response ); + while ( $response !~ /^[yYnN]$/ ) { + print( "Please press 'y' for a backup or 'n' to continue only : " ); + $response = ; + chomp( $response ); } - - print( "\nUpgrading database to version ".ZM_VERSION."\n" ); - - # Update config first of all - ZoneMinder::Config::loadConfigFromDB(); - ZoneMinder::Config::saveConfigToDB(); - - my $cascade = undef; - if ( $cascade || $version eq "1.19.0" ) - { - # Patch the database - patchDB( $dbh, "1.19.0" ); - $cascade = !undef; + if ( $response =~ /^[yY]$/ ) { + my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); + my $command = "mysqldump"; + if ( defined($portOrSocket) ) { + if ( $portOrSocket =~ /^\// ) { + $command .= " -S".$portOrSocket; + } else { + $command .= " -h".$host." -P".$portOrSocket; + } + } else { + $command .= " -h".$host; + } + if ( $dbUser ) { + $command .= ' -u'.$dbUser; + $command .= ' -p"'.$dbPass.'"' if $dbPass; + } + my $backup = "@ZM_TMPDIR@/".$Config{ZM_DB_NAME}."-".$version.".dump"; + $command .= " --add-drop-table --databases ".$Config{ZM_DB_NAME}." > ".$backup; + print( "Creating backup to $backup. This may take several minutes.\n" ); + print( "Executing '$command'\n" ) if ( logDebugging() ); + my $output = qx($command); + my $status = $? >> 8; + if ( $status || logDebugging() ) { + chomp( $output ); + print( "Output: $output\n" ); + } + if ( $status ) { + die( "Command '$command' exited with status: $status\n" ); + } else { + print( "Database successfully backed up to $backup, proceeding to upgrade.\n" ); + } + } elsif ( $response !~ /^[nN]$/ ) { + die( "Unexpected response '$response'" ); } - if ( $cascade || $version eq "1.19.1" ) - { - # Patch the database - patchDB( $dbh, "1.19.1"); - $cascade = !undef; - } - if ( $cascade || $version eq "1.19.2" ) - { - # Patch the database - patchDB( $dbh, "1.19.2" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.19.3" ) - { - # Patch the database - patchDB( $dbh, "1.19.3" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.19.4" ) - { - # Rename the event directories and create a new symlink for the names - chdir( EVENT_PATH ); + } + sub patchDB { + my $dbh = shift; + my $version = shift; - my $sql = "select * from Monitors order by Id"; + my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); + my $command = "mysql"; + if ( defined($portOrSocket) ) { + if ( $portOrSocket =~ /^\// ) { + $command .= " -S".$portOrSocket; + } else { + $command .= " -h".$host." -P".$portOrSocket; + } + } else { + $command .= " -h".$host; + } + if ( $dbUser ) { + $command .= " -u".$dbUser; + $command .= ' -p"'.$dbPass.'"' if $dbPass; + } + $command .= " ".$Config{ZM_DB_NAME}." < "; + if ( $updateDir ) { + $command .= $updateDir; + } else { + $command .= $Config{ZM_PATH_DATA}."/db"; + } + $command .= "/zm_update-".$version.".sql"; + + print( "Executing '$command'\n" ) if ( logDebugging() ); + my $output = qx($command); + my $status = $? >> 8; + if ( $status || logDebugging() ) { + chomp( $output ); + print( "Output: $output\n" ); + } + if ( $status ) { + die( "Command '$command' exited with status: $status\n" ); + } else { + print( "\nDatabase successfully upgraded to version $version.\n" ); + my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() ); + } + } + + + print( "\nUpgrading database to version ".ZM_VERSION."\n" ); + +# Update config first of all + ZoneMinder::Config::loadConfigFromDB(); + ZoneMinder::Config::saveConfigToDB(); + + my $cascade = undef; + if ( $cascade || $version eq "1.19.0" ) { +# Patch the database + patchDB( $dbh, "1.19.0" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.19.1" ) { +# Patch the database + patchDB( $dbh, "1.19.1"); + $cascade = !undef; + } + if ( $cascade || $version eq "1.19.2" ) { +# Patch the database + patchDB( $dbh, "1.19.2" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.19.3" ) { +# Patch the database + patchDB( $dbh, "1.19.3" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.19.4" ) { +# Rename the event directories and create a new symlink for the names + chdir( EVENT_PATH ); + + my $sql = "select * from Monitors order by Id"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + while( my $monitor = $sth->fetchrow_hashref() ) { + if ( -d $monitor->{Name} ) { + rename( $monitor->{Name}, $monitor->{Id} ) or warn( "Can't rename existing monitor directory '$monitor->{Name}' to '$monitor->{Id}': $!" ); + symlink( $monitor->{Id}, $monitor->{Name} ) or warn( "Can't symlink monitor directory '$monitor->{Id}' to '$monitor->{Name}': $!" ); + } + } + $sth->finish(); + +# Patch the database + patchDB( $dbh, "1.19.4" ); + + $cascade = !undef; + } + if ( $cascade || $version eq "1.19.5" ) { + print( "\nThis version now only uses one database user.\nPlease ensure you have run zmconfig.pl and re-entered your database username and password prior to upgrading, or the upgrade will fail.\nPress enter to continue or ctrl-C to stop : " ); +# Patch the database + my $dummy = ; + patchDB( $dbh, "1.19.5" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.20.0" ) { +# Patch the database + patchDB( $dbh, "1.20.0" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.20.1" ) { +# Patch the database + patchDB( $dbh, "1.20.1" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.21.0" ) { +# Patch the database + patchDB( $dbh, "1.21.0" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.21.1" ) { +# Patch the database + patchDB( $dbh, "1.21.1" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.21.2" ) { +# Patch the database + patchDB( $dbh, "1.21.2" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.21.3" ) { +# Patch the database + patchDB( $dbh, "1.21.3" ); + +# Add appropriate widths and heights to events + { + print( "Updating events. This may take a few minutes. Please wait.\n" ); + my $sql = "select * from Monitors order by Id"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + while( my $monitor = $sth->fetchrow_hashref() ) { + my $sql = "update Events set Width = ?, Height = ? where MonitorId = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $monitor->{Width}, $monitor->{Height}, $monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); + } + $sth->finish(); + } + +# Add sequence numbers + { + print( "Updating monitor sequences. Please wait.\n" ); + my $sql = "select * from Monitors order by Id"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my $sequence = 1; + while( my $monitor = $sth->fetchrow_hashref() ) { + my $sql = "update Monitors set Sequence = ? where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $sequence++, $monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); + } + $sth->finish(); + } + +# Update saved filters + { + print( "Updating saved filters. Please wait.\n" ); + my $sql = "select * from Filters"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @filters; + while( my $filter = $sth->fetchrow_hashref() ) { + push( @filters, $filter ); + } + $sth->finish(); + $sql = "update Filters set Query = ? where Name = ?"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + foreach my $filter ( @filters ) { + if ( $filter->{Query} =~ /op\d=&/ ) { + ( my $newQuery = $filter->{Query} ) =~ s/(op\d=)&/$1=&/g; + $res = $sth->execute( $newQuery, $filter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); + } + } + } + + $cascade = !undef; + } + if ( $cascade || $version eq "1.21.4" ) { +# Patch the database + patchDB( $dbh, "1.21.4" ); + +# Convert zones to new format + { + print( "Updating zones. Please wait.\n" ); + +# Get the existing zones from the DB + my $sql = "select Z.*,M.Width,M.Height from Zones as Z inner join Monitors as M on (Z.MonitorId = M.Id)"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @zones; + while( my $zone = $sth->fetchrow_hashref() ) { + push( @zones, $zone ); + } + $sth->finish(); + + no strict 'refs'; + foreach my $zone ( @zones ) { +# Create the coordinate strings + if ( $zone->{Units} eq "Pixels" ) { + my $sql = "update Zones set NumCoords = 4, Coords = concat( LoX,',',LoY,' ',HiX,',',LoY,' ',HiX,',',HiY,' ',LoX,',',HiY ), Area = round( ((HiX-LoX)+1)*((HiY-LoY)+1) ) where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); + } else { + my $loX = ($zone->{LoX} * ($zone->{Width}-1) ) / 100; + my $hiX = ($zone->{HiX} * ($zone->{Width}-1) ) / 100; + my $loY = ($zone->{LoY} * ($zone->{Height}-1) ) / 100; + my $hiY = ($zone->{HiY} * ($zone->{Height}-1) ) / 100; + my $area = (($hiX-$loX)+1)*(($hiY-$loY)+1); + my $sql = "update Zones set NumCoords = 4, Coords = concat( round(?),',',round(?),' ',round(?),',',round(?),' ',round(?),',',round(?),' ',round(?),',',round(?) ), Area = round(?), MinAlarmPixels = round(?), MaxAlarmPixels = round(?), MinFilterPixels = round(?), MaxFilterPixels = round(?), MinBlobPixels = round(?), MaxBlobPixels = round(?) where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $loX, $loY, $hiX, $loY, $hiX, $hiY, $loX, $hiY, $area, ($zone->{MinAlarmPixels}*$area)/100, ($zone->{MaxAlarmPixels}*$area)/100, ($zone->{MinFilterPixels}*$area)/100, ($zone->{MaxFilterPixels}*$area)/100, ($zone->{MinBlobPixels}*$area)/100, ($zone->{MaxBlobPixels}*$area)/100, $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); + } + } + } +# Convert run states to new format + { + print( "Updating run states. Please wait.\n" ); + +# Get the existing zones from the DB + my $sql = "select * from States"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @states; + while( my $state = $sth->fetchrow_hashref() ) { + push( @states, $state ); + } + $sth->finish(); + + foreach my $state ( @states ) { + my @new_defns; + foreach my $defn ( split( /,/, $state->{Definition} ) ) { + push( @new_defns, $defn.":1" ); + } + my $sql = "update States set Definition = ? where Name = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( join( ',', @new_defns ), $state->{Name} ) or die( "Can't execute: ".$sth->errstr() ); + } + } + + $cascade = !undef; + } + if ( $cascade || $version eq "1.22.0" ) { +# Patch the database + patchDB( $dbh, "1.22.0" ); + +# Check for maximum FPS setting and update alarm max fps settings + { + print( "Updating monitors. Please wait.\n" ); + if ( defined(&ZM_NO_MAX_FPS_ON_ALARM) && &ZM_NO_MAX_FPS_ON_ALARM ) { +# Update the individual monitor settings to match the previous global one + my $sql = "update Monitors set AlarmMaxFPS = NULL"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - while( my $monitor = $sth->fetchrow_hashref() ) - { - if ( -d $monitor->{Name} ) - { - rename( $monitor->{Name}, $monitor->{Id} ) or warn( "Can't rename existing monitor directory '$monitor->{Name}' to '$monitor->{Id}': $!" ); - symlink( $monitor->{Id}, $monitor->{Name} ) or warn( "Can't symlink monitor directory '$monitor->{Id}' to '$monitor->{Name}': $!" ); - } - } - $sth->finish(); - - # Patch the database - patchDB( $dbh, "1.19.4" ); - - $cascade = !undef; - } - if ( $cascade || $version eq "1.19.5" ) - { - print( "\nThis version now only uses one database user.\nPlease ensure you have run zmconfig.pl and re-entered your database username and password prior to upgrading, or the upgrade will fail.\nPress enter to continue or ctrl-C to stop : " ); - # Patch the database - my $dummy = ; - patchDB( $dbh, "1.19.5" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.20.0" ) - { - # Patch the database - patchDB( $dbh, "1.20.0" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.20.1" ) - { - # Patch the database - patchDB( $dbh, "1.20.1" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.21.0" ) - { - # Patch the database - patchDB( $dbh, "1.21.0" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.21.1" ) - { - # Patch the database - patchDB( $dbh, "1.21.1" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.21.2" ) - { - # Patch the database - patchDB( $dbh, "1.21.2" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.21.3" ) - { - # Patch the database - patchDB( $dbh, "1.21.3" ); - - # Add appropriate widths and heights to events - { - print( "Updating events. This may take a few minutes. Please wait.\n" ); - my $sql = "select * from Monitors order by Id"; + } else { +# Update the individual monitor settings to match the previous global one + my $sql = "update Monitors set AlarmMaxFPS = MaxFPS"; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - while( my $monitor = $sth->fetchrow_hashref() ) - { - my $sql = "update Events set Width = ?, Height = ? where MonitorId = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $monitor->{Width}, $monitor->{Height}, $monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); + } + } + { + print( "Updating mail configuration. Please wait.\n" ); + my ( $sql, $sth, $res ); + if ( defined(&ZM_EMAIL_TEXT) && &ZM_EMAIL_TEXT ) { + my ( $email_subject, $email_body ) = $Config{ZM_EMAIL_TEXT} =~ /subject\s*=\s*"([^\n]*)".*body\s*=\s*"(.*)"?$/ms; + $sql = "replace into Config set Id = 0, Name = 'ZM_EMAIL_SUBJECT', Value = '".$email_subject."', Type = 'string', DefaultValue = 'ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)', Hint = 'string', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The subject of the email used to send matching event details', Help = 'This option is used to define the subject of the email that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_EMAIL=1'"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + $sql = "replace into Config set Id = 0, Name = 'ZM_EMAIL_BODY', Value = '".$email_body."', Hint = 'free text', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The body of the email used to send matching event details', Help = 'This option is used to define the content of the email that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_EMAIL=1'"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + } + if ( defined(&ZM_MESSAGE_TEXT) && &ZM_MESSAGE_TEXT ) { + my ( $message_subject, $message_body ) = $Config{ZM_MESSAGE_TEXT} =~ /subject\s*=\s*"([^\n]*)".*body\s*=\s*"(.*)"?$/ms; + $sql = "replace into Config set Id = 0, Name = 'ZM_MESSAGE_SUBJECT', Value = '".$message_subject."', Type = 'string', DefaultValue = 'ZoneMinder: Alarm - %MN%-%EI%', Hint = 'string', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The subject of the message used to send matching event details', Help = 'This option is used to define the subject of the message that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_MESSAGE=1'"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + $sql = "replace into Config set Id = 0, Name = 'ZM_MESSAGE_BODY', Value = '".$message_body."', Type = 'text', DefaultValue = 'ZM alarm detected - %ED% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.', Hint = 'free text', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The body of the message used to send matching event details', Help = 'This option is used to define the content of the message that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_MESSAGE=1'"; + $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + } + } + $cascade = !undef; + } + if ( $cascade || $version eq "1.22.1" ) { +# Patch the database + patchDB( $dbh, "1.22.1" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.22.2" ) { +# Patch the database + patchDB( $dbh, "1.22.2" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.22.3" ) { +# Patch the database + patchDB( $dbh, "1.22.3" ); + +# Convert timestamp strings to new format + { + my $sql = "select * from Monitors"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @db_monitors; + while( my $db_monitor = $sth->fetchrow_hashref() ) { + push( @db_monitors, $db_monitor ); + } + $sth->finish(); + foreach my $db_monitor ( @db_monitors ) { + if ( $db_monitor->{LabelFormat} =~ /\%\%s/ ) { + $db_monitor->{LabelFormat} =~ s/\%\%s/%N/; + $db_monitor->{LabelFormat} =~ s/\%\%s/%Q/; + + my $sql = "update Monitors set LabelFormat = ? where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $db_monitor->{LabelFormat}, $db_monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); } - $sth->finish(); + } + } + +# Convert filters to new format + { + my $sql = "select * from Filters"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @dbFilters; + while( my $dbFilter = $sth->fetchrow_hashref() ) { + push( @dbFilters, $dbFilter ); + } + $sth->finish(); + foreach my $dbFilter ( @dbFilters ) { + my %filter_terms; + foreach my $filter_parm ( split( /&/, $dbFilter->{Query} ) ) { + my( $key, $value ) = split( /=/, $filter_parm, 2 ); + if ( $key ) { + $filter_terms{$key} = $value; + } } + my $filter = { 'terms' => [] }; + for ( my $i = 1; $i <= $filter_terms{trms}; $i++ ) { + my $term = {}; + my $conjunction_name = "cnj$i"; + my $obracket_name = "obr$i"; + my $cbracket_name = "cbr$i"; + my $attr_name = "attr$i"; + my $op_name = "op$i"; + my $value_name = "val$i"; - # Add sequence numbers - { - print( "Updating monitor sequences. Please wait.\n" ); - my $sql = "select * from Monitors order by Id"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my $sequence = 1; - while( my $monitor = $sth->fetchrow_hashref() ) - { - my $sql = "update Monitors set Sequence = ? where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $sequence++, $monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); - } - $sth->finish(); + $term->{cnj} = $filter_terms{$conjunction_name} if ( $filter_terms{$conjunction_name} ); + $term->{obr} = $filter_terms{$obracket_name} if ( $filter_terms{$obracket_name} ); + $term->{attr} = $filter_terms{$attr_name} if ( $filter_terms{$attr_name} ); + $term->{val} = $filter_terms{$value_name} if ( defined($filter_terms{$value_name}) ); + $term->{op} = $filter_terms{$op_name} if ( $filter_terms{$op_name} ); + $term->{cbr} = $filter_terms{$cbracket_name} if ( $filter_terms{$cbracket_name} ); + push( @{$filter->{terms}}, $term ); } + $filter->{sort_field} = $filter_terms{sort_field} if ( $filter_terms{sort_field} ); + $filter->{sort_asc} = $filter_terms{sort_asc} if ( $filter_terms{sort_asc} ); + $filter->{limit} = $filter_terms{limit} if ( $filter_terms{limit} ); - # Update saved filters - { - print( "Updating saved filters. Please wait.\n" ); - my $sql = "select * from Filters"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @filters; - while( my $filter = $sth->fetchrow_hashref() ) - { - push( @filters, $filter ); - } - $sth->finish(); - $sql = "update Filters set Query = ? where Name = ?"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - foreach my $filter ( @filters ) - { - if ( $filter->{Query} =~ /op\d=&/ ) - { - ( my $newQuery = $filter->{Query} ) =~ s/(op\d=)&/$1=&/g; - $res = $sth->execute( $newQuery, $filter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); - } - } + my $newQuery = 'a:'.int(keys(%$filter)).':{s:5:"terms";a:'.int(@{$filter->{terms}}).':{'; + my $i = 0; + foreach my $term ( @{$filter->{terms}} ) { + $newQuery .= 'i:'.$i.';a:'.int(keys(%$term)).':{'; + while ( my ( $key, $val ) = each( %$term ) ) { + $newQuery .= 's:'.length($key).':"'.$key.'";'; + $newQuery .= 's:'.length($val).':"'.$val.'";'; + } + $newQuery .= '}'; + $i++; } - - $cascade = !undef; - } - if ( $cascade || $version eq "1.21.4" ) - { - # Patch the database - patchDB( $dbh, "1.21.4" ); - - # Convert zones to new format - { - print( "Updating zones. Please wait.\n" ); - - # Get the existing zones from the DB - my $sql = "select Z.*,M.Width,M.Height from Zones as Z inner join Monitors as M on (Z.MonitorId = M.Id)"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @zones; - while( my $zone = $sth->fetchrow_hashref() ) - { - push( @zones, $zone ); - } - $sth->finish(); - - no strict 'refs'; - foreach my $zone ( @zones ) - { - # Create the coordinate strings - if ( $zone->{Units} eq "Pixels" ) - { - my $sql = "update Zones set NumCoords = 4, Coords = concat( LoX,',',LoY,' ',HiX,',',LoY,' ',HiX,',',HiY,' ',LoX,',',HiY ), Area = round( ((HiX-LoX)+1)*((HiY-LoY)+1) ) where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); - } - else - { - my $loX = ($zone->{LoX} * ($zone->{Width}-1) ) / 100; - my $hiX = ($zone->{HiX} * ($zone->{Width}-1) ) / 100; - my $loY = ($zone->{LoY} * ($zone->{Height}-1) ) / 100; - my $hiY = ($zone->{HiY} * ($zone->{Height}-1) ) / 100; - my $area = (($hiX-$loX)+1)*(($hiY-$loY)+1); - my $sql = "update Zones set NumCoords = 4, Coords = concat( round(?),',',round(?),' ',round(?),',',round(?),' ',round(?),',',round(?),' ',round(?),',',round(?) ), Area = round(?), MinAlarmPixels = round(?), MaxAlarmPixels = round(?), MinFilterPixels = round(?), MaxFilterPixels = round(?), MinBlobPixels = round(?), MaxBlobPixels = round(?) where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $loX, $loY, $hiX, $loY, $hiX, $hiY, $loX, $hiY, $area, ($zone->{MinAlarmPixels}*$area)/100, ($zone->{MaxAlarmPixels}*$area)/100, ($zone->{MinFilterPixels}*$area)/100, ($zone->{MaxFilterPixels}*$area)/100, ($zone->{MinBlobPixels}*$area)/100, ($zone->{MaxBlobPixels}*$area)/100, $zone->{Id} ) or die( "Can't execute: ".$sth->errstr() ); - } - } + $newQuery .= '}'; + foreach my $field ( "sort_field", "sort_asc", "limit" ) { + if ( defined($filter->{$field}) ) { + $newQuery .= 's:'.length($field).':"'.$field.'";'; + $newQuery .= 's:'.length($filter->{$field}).':"'.$filter->{$field}.'";'; + } } - # Convert run states to new format - { - print( "Updating run states. Please wait.\n" ); + $newQuery .= '}'; - # Get the existing zones from the DB - my $sql = "select * from States"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @states; - while( my $state = $sth->fetchrow_hashref() ) - { - push( @states, $state ); - } - $sth->finish(); + my $sql = "update Filters set Query = ? where Name = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $newQuery, $dbFilter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); + } + } - foreach my $state ( @states ) - { - my @new_defns; - foreach my $defn ( split( /,/, $state->{Definition} ) ) - { - push( @new_defns, $defn.":1" ); - } - my $sql = "update States set Definition = ? where Name = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( join( ',', @new_defns ), $state->{Name} ) or die( "Can't execute: ".$sth->errstr() ); - } - } +# Update the stream quality setting to the old image quality ones + { + my $dbh = zmDbConnect(); - $cascade = !undef; + my $sql = "update Config set Value = ? where Name = 'ZM_JPEG_STREAM_QUALITY'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $Config{ZM_JPEG_IMAGE_QUALITY} ) or die( "Can't execute: ".$sth->errstr() ); } - if ( $cascade || $version eq "1.22.0" ) - { - # Patch the database - patchDB( $dbh, "1.22.0" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.23.0" ) { +# Patch the database + patchDB( $dbh, "1.23.0" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.23.1" ) { +# Patch the database + patchDB( $dbh, "1.23.1" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.23.2" ) { +# Patch the database + patchDB( $dbh, "1.23.2" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.23.3" ) { +# Patch the database + patchDB( $dbh, "1.23.3" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.24.0" ) { +# Patch the database + patchDB( $dbh, "1.24.0" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.24.1" ) { +# Patch the database + patchDB( $dbh, "1.24.1" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.24.2" ) { +# Patch the database + patchDB( $dbh, "1.24.2" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.24.3" ) { + my $result = eval { + require PHP::Serialization; + PHP::Serialization->import(); + }; + die( "Unable to perform upgrade from 1.24.3, PHP::Serialization module not found" ) if ( $result ); - # Check for maximum FPS setting and update alarm max fps settings - { - print( "Updating monitors. Please wait.\n" ); - if ( defined(&ZM_NO_MAX_FPS_ON_ALARM) && &ZM_NO_MAX_FPS_ON_ALARM ) - { - # Update the individual monitor settings to match the previous global one - my $sql = "update Monitors set AlarmMaxFPS = NULL"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - } - else - { - # Update the individual monitor settings to match the previous global one - my $sql = "update Monitors set AlarmMaxFPS = MaxFPS"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - } - } - { - print( "Updating mail configuration. Please wait.\n" ); - my ( $sql, $sth, $res ); - if ( defined(&ZM_EMAIL_TEXT) && &ZM_EMAIL_TEXT ) - { - my ( $email_subject, $email_body ) = $Config{ZM_EMAIL_TEXT} =~ /subject\s*=\s*"([^\n]*)".*body\s*=\s*"(.*)"?$/ms; - $sql = "replace into Config set Id = 0, Name = 'ZM_EMAIL_SUBJECT', Value = '".$email_subject."', Type = 'string', DefaultValue = 'ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)', Hint = 'string', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The subject of the email used to send matching event details', Help = 'This option is used to define the subject of the email that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_EMAIL=1'"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - $sql = "replace into Config set Id = 0, Name = 'ZM_EMAIL_BODY', Value = '".$email_body."', Hint = 'free text', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The body of the email used to send matching event details', Help = 'This option is used to define the content of the email that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_EMAIL=1'"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - } - if ( defined(&ZM_MESSAGE_TEXT) && &ZM_MESSAGE_TEXT ) - { - my ( $message_subject, $message_body ) = $Config{ZM_MESSAGE_TEXT} =~ /subject\s*=\s*"([^\n]*)".*body\s*=\s*"(.*)"?$/ms; - $sql = "replace into Config set Id = 0, Name = 'ZM_MESSAGE_SUBJECT', Value = '".$message_subject."', Type = 'string', DefaultValue = 'ZoneMinder: Alarm - %MN%-%EI%', Hint = 'string', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The subject of the message used to send matching event details', Help = 'This option is used to define the subject of the message that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_MESSAGE=1'"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - $sql = "replace into Config set Id = 0, Name = 'ZM_MESSAGE_BODY', Value = '".$message_body."', Type = 'text', DefaultValue = 'ZM alarm detected - %ED% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.', Hint = 'free text', Pattern = '(?-xism:^(.+)\$)', Format = ' \$1 ', Prompt = 'The body of the message used to send matching event details', Help = 'This option is used to define the content of the message that is sent for any events that match the appropriate filters.', Category = 'mail', Readonly = '0', Requires = 'ZM_OPT_MESSAGE=1'"; - $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - } - } - $cascade = !undef; - } - if ( $cascade || $version eq "1.22.1" ) - { - # Patch the database - patchDB( $dbh, "1.22.1" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.22.2" ) - { - # Patch the database - patchDB( $dbh, "1.22.2" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.22.3" ) - { - # Patch the database - patchDB( $dbh, "1.22.3" ); +# Patch the database + patchDB( $dbh, "1.24.3" ); - # Convert timestamp strings to new format - { - my $sql = "select * from Monitors"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @db_monitors; - while( my $db_monitor = $sth->fetchrow_hashref() ) - { - push( @db_monitors, $db_monitor ); - } - $sth->finish(); - foreach my $db_monitor ( @db_monitors ) - { - if ( $db_monitor->{LabelFormat} =~ /\%\%s/ ) - { - $db_monitor->{LabelFormat} =~ s/\%\%s/%N/; - $db_monitor->{LabelFormat} =~ s/\%\%s/%Q/; - - my $sql = "update Monitors set LabelFormat = ? where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $db_monitor->{LabelFormat}, $db_monitor->{Id} ) or die( "Can't execute: ".$sth->errstr() ); - } - } - } - - # Convert filters to new format - { - my $sql = "select * from Filters"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @dbFilters; - while( my $dbFilter = $sth->fetchrow_hashref() ) - { - push( @dbFilters, $dbFilter ); - } - $sth->finish(); - foreach my $dbFilter ( @dbFilters ) - { - my %filter_terms; - foreach my $filter_parm ( split( /&/, $dbFilter->{Query} ) ) - { - my( $key, $value ) = split( /=/, $filter_parm, 2 ); - if ( $key ) - { - $filter_terms{$key} = $value; - } - } - my $filter = { 'terms' => [] }; - for ( my $i = 1; $i <= $filter_terms{trms}; $i++ ) - { - my $term = {}; - my $conjunction_name = "cnj$i"; - my $obracket_name = "obr$i"; - my $cbracket_name = "cbr$i"; - my $attr_name = "attr$i"; - my $op_name = "op$i"; - my $value_name = "val$i"; - - $term->{cnj} = $filter_terms{$conjunction_name} if ( $filter_terms{$conjunction_name} ); - $term->{obr} = $filter_terms{$obracket_name} if ( $filter_terms{$obracket_name} ); - $term->{attr} = $filter_terms{$attr_name} if ( $filter_terms{$attr_name} ); - $term->{val} = $filter_terms{$value_name} if ( defined($filter_terms{$value_name}) ); - $term->{op} = $filter_terms{$op_name} if ( $filter_terms{$op_name} ); - $term->{cbr} = $filter_terms{$cbracket_name} if ( $filter_terms{$cbracket_name} ); - push( @{$filter->{terms}}, $term ); - } - $filter->{sort_field} = $filter_terms{sort_field} if ( $filter_terms{sort_field} ); - $filter->{sort_asc} = $filter_terms{sort_asc} if ( $filter_terms{sort_asc} ); - $filter->{limit} = $filter_terms{limit} if ( $filter_terms{limit} ); - - my $newQuery = 'a:'.int(keys(%$filter)).':{s:5:"terms";a:'.int(@{$filter->{terms}}).':{'; - my $i = 0; - foreach my $term ( @{$filter->{terms}} ) - { - $newQuery .= 'i:'.$i.';a:'.int(keys(%$term)).':{'; - while ( my ( $key, $val ) = each( %$term ) ) - { - $newQuery .= 's:'.length($key).':"'.$key.'";'; - $newQuery .= 's:'.length($val).':"'.$val.'";'; - } - $newQuery .= '}'; - $i++; - } - $newQuery .= '}'; - foreach my $field ( "sort_field", "sort_asc", "limit" ) - { - if ( defined($filter->{$field}) ) - { - $newQuery .= 's:'.length($field).':"'.$field.'";'; - $newQuery .= 's:'.length($filter->{$field}).':"'.$filter->{$field}.'";'; - } - } - $newQuery .= '}'; - - my $sql = "update Filters set Query = ? where Name = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $newQuery, $dbFilter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); - } - } - - # Update the stream quality setting to the old image quality ones - { - my $dbh = zmDbConnect(); - - my $sql = "update Config set Value = ? where Name = 'ZM_JPEG_STREAM_QUALITY'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $Config{ZM_JPEG_IMAGE_QUALITY} ) or die( "Can't execute: ".$sth->errstr() ); - } - $cascade = !undef; - } - if ( $cascade || $version eq "1.23.0" ) +# Convert filters to JSON from PHP format serialisation { - # Patch the database - patchDB( $dbh, "1.23.0" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.23.1" ) - { - # Patch the database - patchDB( $dbh, "1.23.1" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.23.2" ) - { - # Patch the database - patchDB( $dbh, "1.23.2" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.23.3" ) - { - # Patch the database - patchDB( $dbh, "1.23.3" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.24.0" ) - { - # Patch the database - patchDB( $dbh, "1.24.0" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.24.1" ) - { - # Patch the database - patchDB( $dbh, "1.24.1" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.24.2" ) - { - # Patch the database - patchDB( $dbh, "1.24.2" ); - $cascade = !undef; - } - if ( $cascade || $version eq "1.24.3" ) - { - my $result = eval - { - require PHP::Serialization; - PHP::Serialization->import(); + print( "\nConverting filters from PHP to JSON format\n" ); + my $sql = "select * from Filters"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); + my @dbFilters; + while( my $dbFilter = $sth->fetchrow_hashref() ) { + push( @dbFilters, $dbFilter ); + } + $sth->finish(); + foreach my $dbFilter ( @dbFilters ) { + print( " ".$dbFilter->{Name} ); + eval { + my $phpQuery = $dbFilter->{Query}; + my $query = PHP::Serialization::unserialize( $phpQuery ); + my $jsonQuery = jsonEncode( $query ); + my $sql = "update Filters set Query = ? where Name = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $jsonQuery, $dbFilter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); }; - die( "Unable to perform upgrade from 1.24.3, PHP::Serialization module not found" ) if ( $result ); - - # Patch the database - patchDB( $dbh, "1.24.3" ); - - # Convert filters to JSON from PHP format serialisation - { - print( "\nConverting filters from PHP to JSON format\n" ); - my $sql = "select * from Filters"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() ); - my @dbFilters; - while( my $dbFilter = $sth->fetchrow_hashref() ) - { - push( @dbFilters, $dbFilter ); - } - $sth->finish(); - foreach my $dbFilter ( @dbFilters ) - { - print( " ".$dbFilter->{Name} ); - eval { - my $phpQuery = $dbFilter->{Query}; - my $query = PHP::Serialization::unserialize( $phpQuery ); - my $jsonQuery = jsonEncode( $query ); - my $sql = "update Filters set Query = ? where Name = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $jsonQuery, $dbFilter->{Name} ) or die( "Can't execute: ".$sth->errstr() ); - }; - if ( $@ ) - { - print( " - failed, please check or report. Query is '".$dbFilter->{Query}."'\n" ); - print( $@ ); - } - else - { - print( " - complete\n" ); - } - } - print( "Conversion complete\n" ); + if ( $@ ) { + print( " - failed, please check or report. Query is '".$dbFilter->{Query}."'\n" ); + print( $@ ); + } else { + print( " - complete\n" ); } - $cascade = !undef; + } + print( "Conversion complete\n" ); } - if ( $cascade || $version eq "1.24.4" ) - { - # Patch the database - patchDB( $dbh, "1.24.4" ); + $cascade = !undef; + } + if ( $cascade || $version eq "1.24.4" ) { +# Patch the database + patchDB( $dbh, "1.24.4" ); - # Copy the FTP specific values to the new general config - my $fetchSql = "select * from Config where Name like 'ZM_UPLOAD_FTP_%'"; - my $fetchSth = $dbh->prepare_cached( $fetchSql ) or die( "Can't prepare '$fetchSql': ".$dbh->errstr() ); - my $updateSql = "update Config set Value = ? where Name = ?"; - my $updateSth = $dbh->prepare_cached( $updateSql ) or die( "Can't prepare '$updateSql': ".$dbh->errstr() ); - my $fetchRes = $fetchSth->execute() or die( "Can't execute: ".$fetchSth->errstr() ); - while( my $config = $fetchSth->fetchrow_hashref() ) - { - ( my $name = $config->{Name} ) =~ s/_FTP_/_/; - my $updateRes = $updateSth->execute( $config->{Value}, $name ) or die( "Can't execute: ".$updateSth->errstr() ); - } - $cascade = !undef; +# Copy the FTP specific values to the new general config + my $fetchSql = "select * from Config where Name like 'ZM_UPLOAD_FTP_%'"; + my $fetchSth = $dbh->prepare_cached( $fetchSql ) or die( "Can't prepare '$fetchSql': ".$dbh->errstr() ); + my $updateSql = "update Config set Value = ? where Name = ?"; + my $updateSth = $dbh->prepare_cached( $updateSql ) or die( "Can't prepare '$updateSql': ".$dbh->errstr() ); + my $fetchRes = $fetchSth->execute() or die( "Can't execute: ".$fetchSth->errstr() ); + while( my $config = $fetchSth->fetchrow_hashref() ) { + ( my $name = $config->{Name} ) =~ s/_FTP_/_/; + my $updateRes = $updateSth->execute( $config->{Value}, $name ) or die( "Can't execute: ".$updateSth->errstr() ); } - if ( $cascade || $version lt "1.26.0" ) - { - my $sth = $dbh->prepare_cached( 'select * from Monitors LIMIT 0,1' ); - die "Error: " . $dbh->errstr . "\n" unless ($sth); - die "Error: " . $sth->errstr . "\n" unless ($sth->execute); + $cascade = !undef; + } + if ( $cascade || $version lt "1.26.0" ) { + my $sth = $dbh->prepare_cached( 'select * from Monitors LIMIT 0,1' ); + die "Error: " . $dbh->errstr . "\n" unless ($sth); + die "Error: " . $sth->errstr . "\n" unless ($sth->execute); - my $columns = $sth->{'NAME'}; - if ( ! grep(/^Colours$/, @$columns ) ) { - $dbh->do(q{alter table Monitors add column `Colours` tinyint(3) unsigned NOT NULL default '1' after `Height`;}); - } # end if - if ( ! grep(/^Deinterlacing$/, @$columns ) ) { - $dbh->do(q{alter table Monitors add column `Deinterlacing` INT unsigned NOT NULL default '0' after `Orientation`;}); - } # end if - $sth->finish(); + my $columns = $sth->{'NAME'}; + if ( ! grep(/^Colours$/, @$columns ) ) { + $dbh->do(q{alter table Monitors add column `Colours` tinyint(3) unsigned NOT NULL default '1' after `Height`;}); + } # end if + if ( ! grep(/^Deinterlacing$/, @$columns ) ) { + $dbh->do(q{alter table Monitors add column `Deinterlacing` INT unsigned NOT NULL default '0' after `Orientation`;}); + } # end if + $sth->finish(); + $cascade = !undef; + $version = '1.26.0'; + } - $cascade = !undef; - $version = '1.26.0'; - } + if ( $version ge '1.26.0' ) { - if ( $version ge '1.26.0' ) { + my @files; + $updateDir = $Config{ZM_PATH_DATA}."/db" if ! $updateDir; + opendir( my $dh, $updateDir ) || die "Can't open updateDir $!"; +#@files = sort grep { (!/^\./) && /^zm_update\-[\d\.]+\.sql$/ && -f "$updateDir/$_" } readdir($dh); +#PP - use perl version sort + @files = sort { + my ($x) = ($a =~ m/^zm_update\-(.*)\.sql$/); + my ($y) = ($b =~ m/^zm_update\-(.*)\.sql$/); + version->parse($x) <=> version->parse($y) + } grep { (!/^\./) && /^zm_update\-[\d\.]+\.sql$/ && -f "$updateDir/$_" } readdir($dh); + closedir $dh; + if ( ! @files ) { + die "Should have found upgrade scripts at $updateDir\n"; + } # end if - my @files; - $updateDir = $Config{ZM_PATH_DATA}."/db" if ! $updateDir; - opendir( my $dh, $updateDir ) || die "Can't open updateDir $!"; - #@files = sort grep { (!/^\./) && /^zm_update\-[\d\.]+\.sql$/ && -f "$updateDir/$_" } readdir($dh); - #PP - use perl version sort - @files = sort { - my ($x) = ($a =~ m/^zm_update\-(.*)\.sql$/); - my ($y) = ($b =~ m/^zm_update\-(.*)\.sql$/); - version->parse($x) <=> version->parse($y) - } grep { (!/^\./) && /^zm_update\-[\d\.]+\.sql$/ && -f "$updateDir/$_" } readdir($dh); - closedir $dh; - if ( ! @files ) { - die "Should have found upgrade scripts at $updateDir\n"; - } # end if - - $dbh->{'AutoCommit'} = 0; - foreach my $patch ( @files ) { - my ( $v ) = $patch =~ /^zm_update\-([\d\.]+)\.sql$/; - #PP make sure we use version compare - if ( version->parse('v' . $v) > version->parse('v' . $version) ) { - print( "Upgrading DB to $v from $version\n" ); - patchDB( $dbh, $v ); - if ( $dbh->errstr() ) { - $dbh->rollback(); - die "Error: " . $dbh->errstr(). ". Rolled back.\n"; - } # end if error - } # end if - } # end foreach patchfile - $dbh->{'AutoCommit'} = 1; - $cascade = !undef; - } # end if + $dbh->{'AutoCommit'} = 0; + foreach my $patch ( @files ) { + my ( $v ) = $patch =~ /^zm_update\-([\d\.]+)\.sql$/; +#PP make sure we use version compare + if ( version->parse('v' . $v) > version->parse('v' . $version) ) { + print( "Upgrading DB to $v from $version\n" ); + patchDB( $dbh, $v ); + if ( $dbh->errstr() ) { + $dbh->rollback(); + die "Error: " . $dbh->errstr(). ". Rolled back.\n"; + } # end if error + } # end if + } # end foreach patchfile + $dbh->{'AutoCommit'} = 1; + $cascade = !undef; + } # end if - if ( $cascade ) - { - my $installed_version = ZM_VERSION; - my $sql = "update Config set Value = ? where Name = ?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( "$installed_version", "ZM_DYN_DB_VERSION" ) or die( "Can't execute: ".$sth->errstr() ); - $res = $sth->execute( "$installed_version", "ZM_DYN_CURR_VERSION" ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); - } - else - { - zmDbDisconnect(); - die( "Can't find upgrade from version '$version'" ); - } - print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" ); + if ( $cascade ) { + my $installed_version = ZM_VERSION; + my $sql = "update Config set Value = ? where Name = ?"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( "$installed_version", "ZM_DYN_DB_VERSION" ) or die( "Can't execute: ".$sth->errstr() ); + $res = $sth->execute( "$installed_version", "ZM_DYN_CURR_VERSION" ) or die( "Can't execute: ".$sth->errstr() ); + $sth->finish(); + } else { + zmDbDisconnect(); + die( "Can't find upgrade from version '$version'" ); + } + print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" ); } zmDbDisconnect(); exit( 0 ); diff --git a/scripts/zmvideo.pl.in b/scripts/zmvideo.pl.in index 754213af6..5f71451a3 100644 --- a/scripts/zmvideo.pl.in +++ b/scripts/zmvideo.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== @@ -222,6 +222,7 @@ foreach my $event_id ( @event_ids ) { my $video_file = $Event->GenerateVideo( $rate, $fps, $scale, $size, $overwrite, $format ); if ( $video_file ) { push @video_files, $video_file; + print( STDOUT $video_file."\n" ); } } # end foreach event_id @@ -253,9 +254,6 @@ if ( $concat_name ) { Error( "Unable to generate video, check /ffmpeg.log for details"); exit(-1); } - + print( STDOUT $video_file."\n" ); } -#unlink $input_file_list; -#print( STDOUT $event->{MonitorId}.'/'.$event->{Id}.'/'.$video_file."\n" ); -#print( STDOUT $video_file."\n" ); exit( 0 ); diff --git a/scripts/zmwatch.pl.in b/scripts/zmwatch.pl.in index 4e2ff927c..c5ef158ea 100644 --- a/scripts/zmwatch.pl.in +++ b/scripts/zmwatch.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== @@ -87,20 +87,19 @@ while( 1 ) { next if $monitor->{Function} eq 'None'; my $restart = 0; - if ( zmMemVerify( $monitor ) - && zmMemRead( $monitor, "shared_data:valid" ) - ) - { + if ( zmMemVerify( $monitor ) ) { # Check we have got an image recently my $image_time = zmGetLastWriteTime( $monitor ); if ( !defined($image_time) ) { # Can't read from shared data Debug( "LastWriteTime is not defined." ); + zmMemInvalidate( $monitor ); next; } if ( !$image_time ) { # We can't get the last capture time so can't be sure it's died. Debug( "LastWriteTime is = $image_time." ); + zmMemInvalidate( $monitor ); next; } diff --git a/scripts/zmx10.pl.in b/scripts/zmx10.pl.in index 8daa63c87..fab30c9af 100644 --- a/scripts/zmx10.pl.in +++ b/scripts/zmx10.pl.in @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== @@ -474,7 +474,11 @@ sub loadTasks or Fatal( "Can't execute: ".$sth->errstr() ); while( my $monitor = $sth->fetchrow_hashref() ) { - next if ( !zmMemVerify( $monitor ) ); # Check shared memory ok +# Check shared memory ok + if ( !zmMemVerify( $monitor ) ) { + zmMemInvalidate( $monitor ); + next ; + } $monitor_hash{$monitor->{Id}} = $monitor; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9876e2a87..92a3027fe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY) # Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc) -set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_zone.cpp) +set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packet.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp) # A fix for cmake recompiling the source files for every target. add_library(zm STATIC ${ZM_BIN_SRC_FILES}) @@ -12,21 +12,19 @@ add_library(zm STATIC ${ZM_BIN_SRC_FILES}) add_executable(zmc zmc.cpp) add_executable(zma zma.cpp) add_executable(zmu zmu.cpp) -add_executable(zmf zmf.cpp) add_executable(zms zms.cpp) target_link_libraries(zmc zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS}) target_link_libraries(zma zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS}) target_link_libraries(zmu zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS}) -target_link_libraries(zmf zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS}) target_link_libraries(zms zm ${ZM_EXTRA_LIBS} ${ZM_BIN_LIBS}) # Generate man files for the binaries destined for the bin folder -FOREACH(CBINARY zma zmc zmf zmu) +FOREACH(CBINARY zma zmc zmu) POD2MAN(${CMAKE_CURRENT_SOURCE_DIR}/${CBINARY}.cpp zoneminder-${CBINARY} 8) -ENDFOREACH(CBINARY zma zmc zmf zmu) +ENDFOREACH(CBINARY zma zmc zmu) -install(TARGETS zmc zma zmu zmf RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(TARGETS zmc zma zmu RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(TARGETS zms RUNTIME DESTINATION "${ZM_CGIDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(CODE "execute_process(COMMAND ln -sf zms nph-zms WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})" ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/nph-zms DESTINATION "${ZM_CGIDIR}") diff --git a/src/snprintf.cpp b/src/snprintf.cpp new file mode 100644 index 000000000..645534e9c --- /dev/null +++ b/src/snprintf.cpp @@ -0,0 +1,25 @@ +snprintf( swap_path, sizeof(swap_path), "%s/zmswap-m%d/zmswap-q%06d", config.path_swap, monitor->Id(), connkey ); + +int len = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id()); + + + int swap_path_length = strlen(config.path_swap) + snprintf(NULL, 0, "/zmswap-m%d", monitor->Id() ) + snprintf(NULL, 0, "/zmswap-q%06d", connkey ) + 1; // +1 for NULL terminator + + if ( connkey && playback_buffer > 0 ) { + + if ( swap_path_length + max_swap_len_suffix > PATH_MAX ) { + Error( "Swap Path is too long. %d > %d ", swap_path_length+max_swap_len_suffix, PATH_MAX ); + } else { + swap_path = (char *)malloc( swap_path_length+max_swap_len_suffix ); + Debug( 3, "Checking swap image path %s", config.path_swap ); + strncpy( swap_path, config.path_swap, swap_path_length ); + if ( checkSwapPath( swap_path, false ) ) { + snprintf( &(swap_path[swap_path_length]), max_swap_len_suffix, "/zmswap-m%d", monitor->Id() ); + if ( checkSwapPath( swap_path, true ) ) { + snprintf( &(swap_path[swap_path_length]), max_swap_len_suffix, "/zmswap-q%06d", connkey ); + if ( checkSwapPath( swap_path, true ) ) { + buffered_playback = true; + } + } + } + diff --git a/src/zm.cpp b/src/zm.cpp index b840d11fb..e25ce45ce 100644 --- a/src/zm.cpp +++ b/src/zm.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" diff --git a/src/zm.h b/src/zm.h index 0c780b244..092561378 100644 --- a/src/zm.h +++ b/src/zm.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #if !defined(PATH_MAX) diff --git a/src/zm_box.cpp b/src/zm_box.cpp index 2cb8c9a5e..4a69fbf6d 100644 --- a/src/zm_box.cpp +++ b/src/zm_box.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" diff --git a/src/zm_box.h b/src/zm_box.h index f5fe69a38..3432bacc5 100644 --- a/src/zm_box.h +++ b/src/zm_box.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_BOX_H diff --git a/src/zm_buffer.cpp b/src/zm_buffer.cpp index b46cb2f15..73c9603e1 100644 --- a/src/zm_buffer.cpp +++ b/src/zm_buffer.cpp @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include diff --git a/src/zm_buffer.h b/src/zm_buffer.h index 620fce1a8..6fa18984e 100644 --- a/src/zm_buffer.h +++ b/src/zm_buffer.h @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef ZM_BUFFER_H @@ -153,7 +153,7 @@ public: mHead = mTail = mStorage; else if ( level ) { - if ( (mHead-mStorage) > mSize ) + if ( ((uintptr_t)mHead-(uintptr_t)mStorage) > mSize ) { memcpy( mStorage, mHead, mSize ); mHead = mStorage; diff --git a/src/zm_camera.cpp b/src/zm_camera.cpp index bbf2edcb6..0ea7af373 100644 --- a/src/zm_camera.cpp +++ b/src/zm_camera.cpp @@ -14,39 +14,49 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" #include "zm_camera.h" -Camera::Camera( int p_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - id( p_id ), - type( p_type ), - width( p_width), - height( p_height ), - colours( p_colours ), - subpixelorder( p_subpixelorder ), - brightness( p_brightness ), - hue( p_hue ), - colour( p_colour ), - contrast( p_contrast ), - capture( p_capture ) +Camera::Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + monitor_id( p_monitor_id ), + type( p_type ), + width( p_width), + height( p_height ), + colours( p_colours ), + subpixelorder( p_subpixelorder ), + brightness( p_brightness ), + hue( p_hue ), + colour( p_colour ), + contrast( p_contrast ), + capture( p_capture ), + record_audio( p_record_audio ) { pixels = width * height; imagesize = pixels * colours; - Debug(2,"New camera id: %d width: %d height: %d colours: %d subpixelorder: %d capture: %d",id,width,height,colours,subpixelorder,capture); + Debug(2,"New camera id: %d width: %d height: %d colours: %d subpixelorder: %d capture: %d",monitor_id,width,height,colours,subpixelorder,capture); /* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */ - if((colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB32) && (imagesize % 16) != 0) { - Fatal("Image size is not multiples of 16"); - } else if(colours == ZM_COLOUR_RGB24 && ((imagesize % 16) != 0 || (imagesize % 12) != 0)) { - Fatal("Image size is not multiples of 12 and 16"); + if((colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB32) && (imagesize % 64) != 0) { + Fatal("Image size is not multiples of 64"); + } else if(colours == ZM_COLOUR_RGB24 && ((imagesize % 64) != 0 || (imagesize % 12) != 0)) { + Fatal("Image size is not multiples of 12 and 64"); } } -Camera::~Camera() -{ +Camera::~Camera() { } +Monitor *Camera::getMonitor() { + if ( ! monitor ) + monitor = Monitor::Load( monitor_id, false, Monitor::QUERY ); + return monitor; +} + +void Camera::setMonitor( Monitor *p_monitor ) { + monitor = p_monitor; + monitor_id = monitor->Id(); +} diff --git a/src/zm_camera.h b/src/zm_camera.h index ba8224c09..4d991d495 100644 --- a/src/zm_camera.h +++ b/src/zm_camera.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_CAMERA_H @@ -25,6 +25,10 @@ #include "zm_image.h" +class Camera; + +#include "zm_monitor.h" + // // Abstract base class for cameras. This is intended just to express // common attributes @@ -34,7 +38,8 @@ class Camera protected: typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC } SourceType; - int id; + unsigned int monitor_id; + Monitor * monitor; // Null on instantiation, set as soon as possible. SourceType type; unsigned int width; unsigned int height; @@ -42,17 +47,20 @@ protected: unsigned int subpixelorder; unsigned int pixels; unsigned int imagesize; - int brightness; - int hue; - int colour; - int contrast; - bool capture; + int brightness; + int hue; + int colour; + int contrast; + bool capture; + bool record_audio; public: - Camera( int p_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); virtual ~Camera(); - int getId() const { return( id ); } + unsigned int getId() const { return( monitor_id ); } + Monitor *getMonitor(); + void setMonitor( Monitor *p_monitor ); SourceType Type() const { return( type ); } bool IsLocal() const { return( type == LOCAL_SRC ); } bool IsRemote() const { return( type == REMOTE_SRC ); } @@ -73,11 +81,14 @@ public: virtual int Contrast( int/*p_contrast*/=-1 ) { return( -1 ); } bool CanCapture() const { return( capture ); } - + + bool SupportsNativeVideo() const { return( (type == FFMPEG_SRC )||(type == REMOTE_SRC)); } + virtual int PrimeCapture() { return( 0 ); } virtual int PreCapture()=0; virtual int Capture( Image &image )=0; virtual int PostCapture()=0; + virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) = 0; }; #endif // ZM_CAMERA_H diff --git a/src/zm_comms.cpp b/src/zm_comms.cpp index d5fb49c2e..24d1a1a63 100644 --- a/src/zm_comms.cpp +++ b/src/zm_comms.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm_comms.h" diff --git a/src/zm_comms.h b/src/zm_comms.h index f00691f70..cf108c1ea 100644 --- a/src/zm_comms.h +++ b/src/zm_comms.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_COMMS_H diff --git a/src/zm_config.cpp b/src/zm_config.cpp index c08b65e56..eedebaf45 100644 --- a/src/zm_config.cpp +++ b/src/zm_config.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" @@ -24,89 +24,41 @@ #include #include #include +#include +#include #include "zm_utils.h" -void zmLoadConfig() -{ - FILE *cfg; - char line[512]; - if ( (cfg = fopen( ZM_CONFIG, "r")) == NULL ) - { - Fatal( "Can't open %s: %s", ZM_CONFIG, strerror(errno) ); +void zmLoadConfig() { + + // Process name, value pairs from the main config file first + char configFile[PATH_MAX] = ZM_CONFIG; + process_configfile(configFile); + + // Search for user created config files. If one or more are found then + // update the Config hash with those values + DIR* configSubFolder = opendir(ZM_CONFIG_SUBDIR); + if ( configSubFolder ) { // subfolder exists and is readable + char glob_pattern[PATH_MAX] = ""; + snprintf( glob_pattern, sizeof(glob_pattern), "%s/*.conf", ZM_CONFIG_SUBDIR ); + + glob_t pglob; + int glob_status = glob( glob_pattern, 0, 0, &pglob ); + if ( glob_status != 0 ) { + if ( glob_status < 0 ) { + Error( "Can't glob '%s': %s", glob_pattern, strerror(errno) ); + } else { + Debug( 1, "Can't glob '%s': %d", glob_pattern, glob_status ); + } + } else { + for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) { + process_configfile(pglob.gl_pathv[i]); + } + closedir(configSubFolder); + } + globfree( &pglob ); } - while ( fgets( line, sizeof(line), cfg ) != NULL ) - { - char *line_ptr = line; - // Trim off any cr/lf line endings - int chomp_len = strcspn( line_ptr, "\r\n" ); - line_ptr[chomp_len] = '\0'; - - // Remove leading white space - int white_len = strspn( line_ptr, " \t" ); - line_ptr += white_len; - - // Check for comment or empty line - if ( *line_ptr == '\0' || *line_ptr == '#' ) - continue; - - // Remove trailing white space - char *temp_ptr = line_ptr+strlen(line_ptr)-1; - while ( *temp_ptr == ' ' || *temp_ptr == '\t' ) - { - *temp_ptr-- = '\0'; - temp_ptr--; - } - - // Now look for the '=' in the middle of the line - temp_ptr = strchr( line_ptr, '=' ); - if ( !temp_ptr ) - { - Warning( "Invalid data in %s: '%s'", ZM_CONFIG, line ); - continue; - } - - // Assign the name and value parts - char *name_ptr = line_ptr; - char *val_ptr = temp_ptr+1; - - // Trim trailing space from the name part - do - { - *temp_ptr = '\0'; - temp_ptr--; - } - while ( *temp_ptr == ' ' || *temp_ptr == '\t' ); - - // Remove leading white space from the value part - white_len = strspn( val_ptr, " \t" ); - val_ptr += white_len; - - if ( strcasecmp( name_ptr, "ZM_DB_HOST" ) == 0 ) - staticConfig.DB_HOST = std::string(val_ptr); - else if ( strcasecmp( name_ptr, "ZM_DB_NAME" ) == 0 ) - staticConfig.DB_NAME = std::string(val_ptr); - else if ( strcasecmp( name_ptr, "ZM_DB_USER" ) == 0 ) - staticConfig.DB_USER = std::string(val_ptr); - else if ( strcasecmp( name_ptr, "ZM_DB_PASS" ) == 0 ) - staticConfig.DB_PASS = std::string(val_ptr); - else if ( strcasecmp( name_ptr, "ZM_PATH_WEB" ) == 0 ) - staticConfig.PATH_WEB = std::string(val_ptr); - else if ( strcasecmp( name_ptr, "ZM_SERVER_HOST" ) == 0 ) - staticConfig.SERVER_NAME = std::string(val_ptr); - else if ( strcasecmp( name_ptr, "ZM_SERVER_NAME" ) == 0 ) - staticConfig.SERVER_NAME = std::string(val_ptr); - else if ( strcasecmp( name_ptr, "ZM_SERVER_ID" ) == 0 ) - staticConfig.SERVER_ID = atoi(val_ptr); - else - { - // We ignore this now as there may be more parameters than the - // c/c++ binaries are bothered about - // Warning( "Invalid parameter '%s' in %s", name_ptr, ZM_CONFIG ); - } - } // end foreach line of the config - fclose( cfg ); zmDbConnect(); config.Load(); config.Assign(); @@ -141,10 +93,83 @@ void zmLoadConfig() } } +void process_configfile( char* configFile) { + FILE *cfg; + char line[512]; + if ( (cfg = fopen( configFile, "r")) == NULL ) { + Fatal( "Can't open %s: %s", configFile, strerror(errno) ); + } + while ( fgets( line, sizeof(line), cfg ) != NULL ) { + char *line_ptr = line; + + // Trim off any cr/lf line endings + int chomp_len = strcspn( line_ptr, "\r\n" ); + line_ptr[chomp_len] = '\0'; + + // Remove leading white space + int white_len = strspn( line_ptr, " \t" ); + line_ptr += white_len; + + // Check for comment or empty line + if ( *line_ptr == '\0' || *line_ptr == '#' ) + continue; + + // Remove trailing white space + char *temp_ptr = line_ptr+strlen(line_ptr)-1; + while ( *temp_ptr == ' ' || *temp_ptr == '\t' ) { + *temp_ptr-- = '\0'; + temp_ptr--; + } + + // Now look for the '=' in the middle of the line + temp_ptr = strchr( line_ptr, '=' ); + if ( !temp_ptr ) { + Warning( "Invalid data in %s: '%s'", configFile, line ); + continue; + } + + // Assign the name and value parts + char *name_ptr = line_ptr; + char *val_ptr = temp_ptr+1; + + // Trim trailing space from the name part + do { + *temp_ptr = '\0'; + temp_ptr--; + } while ( *temp_ptr == ' ' || *temp_ptr == '\t' ); + + // Remove leading white space from the value part + white_len = strspn( val_ptr, " \t" ); + val_ptr += white_len; + + if ( strcasecmp( name_ptr, "ZM_DB_HOST" ) == 0 ) + staticConfig.DB_HOST = std::string(val_ptr); + else if ( strcasecmp( name_ptr, "ZM_DB_NAME" ) == 0 ) + staticConfig.DB_NAME = std::string(val_ptr); + else if ( strcasecmp( name_ptr, "ZM_DB_USER" ) == 0 ) + staticConfig.DB_USER = std::string(val_ptr); + else if ( strcasecmp( name_ptr, "ZM_DB_PASS" ) == 0 ) + staticConfig.DB_PASS = std::string(val_ptr); + else if ( strcasecmp( name_ptr, "ZM_PATH_WEB" ) == 0 ) + staticConfig.PATH_WEB = std::string(val_ptr); + else if ( strcasecmp( name_ptr, "ZM_SERVER_HOST" ) == 0 ) + staticConfig.SERVER_NAME = std::string(val_ptr); + else if ( strcasecmp( name_ptr, "ZM_SERVER_NAME" ) == 0 ) + staticConfig.SERVER_NAME = std::string(val_ptr); + else if ( strcasecmp( name_ptr, "ZM_SERVER_ID" ) == 0 ) + staticConfig.SERVER_ID = atoi(val_ptr); + else { + // We ignore this now as there may be more parameters than the + // c/c++ binaries are bothered about + // Warning( "Invalid parameter '%s' in %s", name_ptr, ZM_CONFIG ); + } + } // end foreach line of the config + fclose( cfg ); +} + StaticConfig staticConfig; -ConfigItem::ConfigItem( const char *p_name, const char *p_value, const char *const p_type ) -{ +ConfigItem::ConfigItem( const char *p_name, const char *p_value, const char *const p_type ) { name = new char[strlen(p_name)+1]; strcpy( name, p_name ); value = new char[strlen(p_value)+1]; @@ -157,50 +182,37 @@ ConfigItem::ConfigItem( const char *p_name, const char *p_value, const char *con accessed = false; } -ConfigItem::~ConfigItem() -{ +ConfigItem::~ConfigItem() { delete[] name; delete[] value; delete[] type; } -void ConfigItem::ConvertValue() const -{ - if ( !strcmp( type, "boolean" ) ) - { +void ConfigItem::ConvertValue() const { + if ( !strcmp( type, "boolean" ) ) { cfg_type = CFG_BOOLEAN; cfg_value.boolean_value = (bool)strtol( value, 0, 0 ); - } - else if ( !strcmp( type, "integer" ) ) - { + } else if ( !strcmp( type, "integer" ) ) { cfg_type = CFG_INTEGER; cfg_value.integer_value = strtol( value, 0, 10 ); - } - else if ( !strcmp( type, "hexadecimal" ) ) - { + } else if ( !strcmp( type, "hexadecimal" ) ) { cfg_type = CFG_INTEGER; cfg_value.integer_value = strtol( value, 0, 16 ); - } - else if ( !strcmp( type, "decimal" ) ) - { + } else if ( !strcmp( type, "decimal" ) ) { cfg_type = CFG_DECIMAL; cfg_value.decimal_value = strtod( value, 0 ); - } - else - { + } else { cfg_type = CFG_STRING; cfg_value.string_value = value; } accessed = true; } -bool ConfigItem::BooleanValue() const -{ +bool ConfigItem::BooleanValue() const { if ( !accessed ) ConvertValue(); - if ( cfg_type != CFG_BOOLEAN ) - { + if ( cfg_type != CFG_BOOLEAN ) { Error( "Attempt to fetch boolean value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); exit( -1 ); } @@ -208,13 +220,11 @@ bool ConfigItem::BooleanValue() const return( cfg_value.boolean_value ); } -int ConfigItem::IntegerValue() const -{ +int ConfigItem::IntegerValue() const { if ( !accessed ) ConvertValue(); - if ( cfg_type != CFG_INTEGER ) - { + if ( cfg_type != CFG_INTEGER ) { Error( "Attempt to fetch integer value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); exit( -1 ); } @@ -222,13 +232,11 @@ int ConfigItem::IntegerValue() const return( cfg_value.integer_value ); } -double ConfigItem::DecimalValue() const -{ +double ConfigItem::DecimalValue() const { if ( !accessed ) ConvertValue(); - if ( cfg_type != CFG_DECIMAL ) - { + if ( cfg_type != CFG_DECIMAL ) { Error( "Attempt to fetch decimal value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); exit( -1 ); } @@ -236,13 +244,11 @@ double ConfigItem::DecimalValue() const return( cfg_value.decimal_value ); } -const char *ConfigItem::StringValue() const -{ +const char *ConfigItem::StringValue() const { if ( !accessed ) ConvertValue(); - if ( cfg_type != CFG_STRING ) - { + if ( cfg_type != CFG_STRING ) { Error( "Attempt to fetch string value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); exit( -1 ); } @@ -250,80 +256,66 @@ const char *ConfigItem::StringValue() const return( cfg_value.string_value ); } -Config::Config() -{ +Config::Config() { n_items = 0; items = 0; } -Config::~Config() -{ - if ( items ) - { - for ( int i = 0; i < n_items; i++ ) - { +Config::~Config() { + if ( items ) { + for ( int i = 0; i < n_items; i++ ) { delete items[i]; } delete[] items; } } -void Config::Load() -{ +void Config::Load() { static char sql[ZM_SQL_SML_BUFSIZ]; strncpy( sql, "select Name, Value, Type from Config order by Id", sizeof(sql) ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } n_items = mysql_num_rows( result ); - if ( n_items <= ZM_MAX_CFG_ID ) - { + if ( n_items <= ZM_MAX_CFG_ID ) { Error( "Config mismatch, expected %d items, read %d. Try running 'zmupdate.pl -f' to reload config.", ZM_MAX_CFG_ID+1, n_items ); exit( -1 ); } items = new ConfigItem *[n_items]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { items[i] = new ConfigItem( dbrow[0], dbrow[1], dbrow[2] ); } mysql_free_result( result ); } -void Config::Assign() -{ +void Config::Assign() { ZM_CFG_ASSIGN_LIST } -const ConfigItem &Config::Item( int id ) -{ - if ( !n_items ) - { +const ConfigItem &Config::Item( int id ) { + if ( !n_items ) { Load(); Assign(); } - if ( id < 0 || id > ZM_MAX_CFG_ID ) - { + if ( id < 0 || id > ZM_MAX_CFG_ID ) { Error( "Attempt to access invalid config, id = %d. Try running 'zmupdate.pl -f' to reload config.", id ); exit( -1 ); } ConfigItem *item = items[id]; - if ( !item ) - { + if ( !item ) { Error( "Can't find config item %d", id ); exit( -1 ); } diff --git a/src/zm_config.h.in b/src/zm_config.h.in index 1deeff861..49b983fa7 100644 --- a/src/zm_config.h.in +++ b/src/zm_config.h.in @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_CONFIG_H @@ -26,6 +26,7 @@ #include #define ZM_CONFIG "@ZM_CONFIG@" // Path to config file +#define ZM_CONFIG_SUBDIR "@ZM_CONFIG_SUBDIR@" // Path to the ZoneMinder config subfolder #define ZM_VERSION "@VERSION@" // ZoneMinder Version #define ZM_HAS_V4L1 @ZM_HAS_V4L1@ @@ -58,6 +59,8 @@ extern void zmLoadConfig(); +extern void process_configfile( char* configFile ); + struct StaticConfig { std::string DB_HOST; diff --git a/src/zm_coord.cpp b/src/zm_coord.cpp index cef188b63..df9bc0a87 100644 --- a/src/zm_coord.cpp +++ b/src/zm_coord.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" diff --git a/src/zm_coord.h b/src/zm_coord.h index 7316244a2..5858cef23 100644 --- a/src/zm_coord.h +++ b/src/zm_coord.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_COORD_H diff --git a/src/zm_curl_camera.cpp b/src/zm_curl_camera.cpp index f9b6d0712..3db890bf9 100644 --- a/src/zm_curl_camera.cpp +++ b/src/zm_curl_camera.cpp @@ -14,12 +14,15 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" + #include "zm_curl_camera.h" +#include "zm_packetqueue.h" + #if HAVE_LIBCURL #define CURL_MAXRETRY 5 @@ -30,28 +33,24 @@ const char* content_type_match = "Content-Type:"; size_t content_length_match_len; size_t content_type_match_len; -cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), +cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET ) { - if ( capture ) - { + if ( capture ) { Initialise(); } } -cURLCamera::~cURLCamera() -{ - if ( capture ) - { +cURLCamera::~cURLCamera() { + if ( capture ) { Terminate(); } } -void cURLCamera::Initialise() -{ +void cURLCamera::Initialise() { content_length_match_len = strlen(content_length_match); content_type_match_len = strlen(content_type_match); @@ -88,8 +87,7 @@ void cURLCamera::Initialise() } } -void cURLCamera::Terminate() -{ +void cURLCamera::Terminate() { /* Signal the thread to terminate */ bTerminate = true; @@ -108,20 +106,17 @@ void cURLCamera::Terminate() } -int cURLCamera::PrimeCapture() -{ +int cURLCamera::PrimeCapture() { //Info( "Priming capture from %s", mPath.c_str() ); return 0; } -int cURLCamera::PreCapture() -{ - // Nothing to do here - return( 0 ); +int cURLCamera::PreCapture() { + // Nothing to do here + return( 0 ); } -int cURLCamera::Capture( Image &image ) -{ +int cURLCamera::Capture( Image &image ) { bool frameComplete = false; /* MODE_STREAM specific variables */ @@ -305,14 +300,18 @@ int cURLCamera::Capture( Image &image ) return 0; } -int cURLCamera::PostCapture() -{ +int cURLCamera::PostCapture() { + // Nothing to do here + return( 0 ); +} + +int cURLCamera::CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ) { + Error("Capture and Record not implemented for the cURL camera type"); // Nothing to do here return( 0 ); } -size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) -{ +size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { lock(); /* Append the data we just received to our buffer */ @@ -331,10 +330,7 @@ size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void * return size*nmemb; } - - -size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) -{ +size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) { std::string header; header.assign((const char*)buffer, size*nmemb); @@ -374,8 +370,7 @@ size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, voi return size*nmemb; } -void* cURLCamera::thread_func() -{ +void* cURLCamera::thread_func() { long tRet; double dSize; @@ -521,8 +516,7 @@ int cURLCamera::unlock() { return nRet; } -int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) -{ +int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) { /* Signal the curl thread to terminate */ if(bTerminate) return -10; @@ -531,18 +525,15 @@ int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, } /* These functions call the functions in the class for the correct object */ -size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) -{ +size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) { return ((cURLCamera*)userdata)->data_callback(buffer,size,nmemb,userdata); } -size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) -{ +size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) { return ((cURLCamera*)userdata)->header_callback(buffer,size,nmemb,userdata); } -int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) -{ +int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) { return ((cURLCamera*)userdata)->progress_callback(userdata,dltotal,dlnow,ultotal,ulnow); } @@ -550,6 +541,4 @@ void* thread_func_dispatcher(void* object) { return ((cURLCamera*)object)->thread_func(); } - - #endif // HAVE_LIBCURL diff --git a/src/zm_curl_camera.h b/src/zm_curl_camera.h index f7c3540fe..c9dc2e935 100644 --- a/src/zm_curl_camera.h +++ b/src/zm_curl_camera.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_CURL_CAMERA_H @@ -39,8 +39,7 @@ // Class representing 'curl' cameras, i.e. those which are // accessed using the curl library // -class cURLCamera : public Camera -{ +class cURLCamera : public Camera { protected: typedef enum {MODE_UNSET, MODE_SINGLE, MODE_STREAM} mode_t; @@ -65,7 +64,7 @@ protected: pthread_cond_t request_complete_cond; public: - cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, unsigned int p_width, unsigned int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~cURLCamera(); const std::string &Path() const { return( mPath ); } @@ -79,6 +78,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); + int CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ); size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata); size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata); diff --git a/src/zm_db.cpp b/src/zm_db.cpp index 8f2527fc2..8eed90569 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include @@ -37,12 +37,10 @@ void zmDbConnect() my_bool reconnect = 1; if ( mysql_options( &dbconn, MYSQL_OPT_RECONNECT, &reconnect ) ) Fatal( "Can't set database auto reconnect option: %s", mysql_error( &dbconn ) ); - std::string::size_type colonIndex = staticConfig.DB_HOST.find( ":/" ); - if ( colonIndex != std::string::npos ) + std::string::size_type colonIndex = staticConfig.DB_HOST.find( ":" ); + if ( colonIndex == std::string::npos ) { - std::string dbHost = staticConfig.DB_HOST.substr( 0, colonIndex ); - std::string dbPort = staticConfig.DB_HOST.substr( colonIndex+1 ); - if ( !mysql_real_connect( &dbconn, dbHost.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), 0, atoi(dbPort.c_str()), 0, 0 ) ) + if ( !mysql_real_connect( &dbconn, staticConfig.DB_HOST.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, NULL, 0 ) ) { Error( "Can't connect to server: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); @@ -50,10 +48,24 @@ void zmDbConnect() } else { - if ( !mysql_real_connect( &dbconn, staticConfig.DB_HOST.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), 0, 0, 0, 0 ) ) + std::string dbHost = staticConfig.DB_HOST.substr( 0, colonIndex ); + std::string dbPortOrSocket = staticConfig.DB_HOST.substr( colonIndex+1 ); + if ( dbPortOrSocket[0] == '/' ) { - Error( "Can't connect to server: %s", mysql_error( &dbconn ) ); - exit( mysql_errno( &dbconn ) ); + if ( !mysql_real_connect( &dbconn, NULL, staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, dbPortOrSocket.c_str(), 0 ) ) + { + Error( "Can't connect to server: %s", mysql_error( &dbconn ) ); + exit( mysql_errno( &dbconn ) ); + } + + } + else + { + if ( !mysql_real_connect( &dbconn, dbHost.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, atoi(dbPortOrSocket.c_str()), NULL, 0 ) ) + { + Error( "Can't connect to server: %s", mysql_error( &dbconn ) ); + exit( mysql_errno( &dbconn ) ); + } } } if ( mysql_select_db( &dbconn, staticConfig.DB_NAME.c_str() ) ) diff --git a/src/zm_db.h b/src/zm_db.h index 6ec1b5e4e..4cadcf587 100644 --- a/src/zm_db.h +++ b/src/zm_db.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_DB_H diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 50028ffa6..e50009c76 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include @@ -42,8 +42,6 @@ extern "C" #include "zm_sendfile.h" } -#include "zmf.h" - #if HAVE_SYS_SENDFILE_H #include #endif @@ -54,15 +52,18 @@ bool Event::initialised = false; char Event::capture_file_format[PATH_MAX]; char Event::analyse_file_format[PATH_MAX]; char Event::general_file_format[PATH_MAX]; +char Event::video_file_format[PATH_MAX]; int Event::pre_alarm_count = 0; Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = { { 0 } }; -Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap ) : +Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) : monitor( p_monitor ), start_time( p_start_time ), cause( p_cause ), - noteSetMap( p_noteSetMap ) + noteSetMap( p_noteSetMap ), + videoEvent( p_videoEvent ), + videowriter( NULL ) { if ( !initialised ) Initialise(); @@ -71,8 +72,7 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string createNotes( notes ); bool untimedEvent = false; - if ( !start_time.tv_sec ) - { + if ( !start_time.tv_sec ) { untimedEvent = true; gettimeofday( &start_time, 0 ); } @@ -80,15 +80,13 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string static char sql[ZM_SQL_MED_BUFSIZ]; struct tm *stime = localtime( &start_time.tv_sec ); - snprintf( sql, sizeof(sql), "insert into Events ( MonitorId, Name, StartTime, Width, Height, Cause, Notes ) values ( %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s' )", monitor->Id(), start_time.tv_sec, monitor->Width(), monitor->Height(), cause.c_str(), notes.c_str() ); - if ( mysql_query( &dbconn, sql ) ) - { - Error( "Can't insert event: %s", mysql_error( &dbconn ) ); + snprintf( sql, sizeof(sql), "insert into Events ( MonitorId, Name, StartTime, Width, Height, Cause, Notes, Videoed ) values ( %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', '%d' )", monitor->Id(), start_time.tv_sec, monitor->Width(), monitor->Height(), cause.c_str(), notes.c_str(), videoEvent ); + if ( mysql_query( &dbconn, sql ) ) { + Error( "Can't insert event: %s. sql was (%s)", mysql_error( &dbconn ), sql ); exit( mysql_errno( &dbconn ) ); } id = mysql_insert_id( &dbconn ); - if ( untimedEvent ) - { + if ( untimedEvent ) { Warning( "Event %d has zero time, setting to current", id ); } end_time.tv_sec = 0; @@ -97,8 +95,10 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string tot_score = 0; max_score = 0; - if ( config.use_deep_storage ) - { + struct stat statbuf; + char id_file[PATH_MAX]; + + if ( config.use_deep_storage ) { char *path_ptr = path; path_ptr += snprintf( path_ptr, sizeof(path), "%s/%d", config.dir_events, monitor->Id() ); @@ -113,17 +113,13 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string char date_path[PATH_MAX] = ""; char time_path[PATH_MAX] = ""; char *time_path_ptr = time_path; - for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ ) - { + for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ ) { path_ptr += snprintf( path_ptr, sizeof(path)-(path_ptr-path), "/%02d", dt_parts[i] ); - struct stat statbuf; errno = 0; if ( stat( path, &statbuf ) ) { - if ( errno == ENOENT || errno == ENOTDIR ) - { - if ( mkdir( path, 0755 ) ) - { + if ( errno == ENOENT || errno == ENOTDIR ) { + if ( mkdir( path, 0755 ) ) { Fatal( "Can't mkdir %s: %s", path, strerror(errno)); } } else { @@ -135,83 +131,116 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string else if ( i >= 3 ) time_path_ptr += snprintf( time_path_ptr, sizeof(time_path)-(time_path_ptr-time_path), "%s%02d", i>3?"/":"", dt_parts[i] ); } - char id_file[PATH_MAX]; // Create event id symlink snprintf( id_file, sizeof(id_file), "%s/.%d", date_path, id ); if ( symlink( time_path, id_file ) < 0 ) Fatal( "Can't symlink %s -> %s: %s", id_file, path, strerror(errno)); - // Create empty id tag file - snprintf( id_file, sizeof(id_file), "%s/.%d", path, id ); - if ( FILE *id_fp = fopen( id_file, "w" ) ) - fclose( id_fp ); - else - Fatal( "Can't fopen %s: %s", id_file, strerror(errno)); - } - else - { + } else { snprintf( path, sizeof(path), "%s/%d/%d", config.dir_events, monitor->Id(), id ); - - struct stat statbuf; + errno = 0; stat( path, &statbuf ); - if ( errno == ENOENT || errno == ENOTDIR ) - { - if ( mkdir( path, 0755 ) ) - { + if ( errno == ENOENT || errno == ENOTDIR ) { + if ( mkdir( path, 0755 ) ) { Error( "Can't mkdir %s: %s", path, strerror(errno)); } } - char id_file[PATH_MAX]; - // Create empty id tag file - snprintf( id_file, sizeof(id_file), "%s/.%d", path, id ); - if ( FILE *id_fp = fopen( id_file, "w" ) ) - fclose( id_fp ); - else - Fatal( "Can't fopen %s: %s", id_file, strerror(errno)); - } - last_db_frame = 0; -} + } // deep storage or not -Event::~Event() -{ - if ( frames > last_db_frame ) - { - struct DeltaTimeval delta_time; - DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 ); + // Create empty id tag file + snprintf( id_file, sizeof(id_file), "%s/.%d", path, id ); + if ( FILE *id_fp = fopen( id_file, "w" ) ) + fclose( id_fp ); + else + Fatal( "Can't fopen %s: %s", id_file, strerror(errno)); + + last_db_frame = 0; + + video_name[0] = 0; + + /* Save as video */ + + if ( monitor->GetOptVideoWriter() != 0 ) { + snprintf( video_name, sizeof(video_name), "%d-%s", id, "video.mp4" ); + snprintf( video_file, sizeof(video_file), video_file_format, path, video_name ); + + /* X264 MP4 video writer */ + if ( monitor->GetOptVideoWriter() == Monitor::X264ENCODE ) { +#if ZM_HAVE_VIDEOWRITER_X264MP4 + videowriter = new X264MP4Writer(video_file, monitor->Width(), monitor->Height(), monitor->Colours(), monitor->SubpixelOrder(), monitor->GetOptEncoderParams()); +#else + Error("ZoneMinder was not compiled with the X264 MP4 video writer, check dependencies (x264 and mp4v2)"); +#endif + } + + if ( videowriter != NULL ) { + /* Open the video stream */ + int nRet = videowriter->Open(); + if(nRet != 0) { + Error("Failed opening video stream"); + delete videowriter; + videowriter = NULL; + } + + snprintf( timecodes_name, sizeof(timecodes_name), "%d-%s", id, "video.timecodes" ); + snprintf( timecodes_file, sizeof(timecodes_file), video_file_format, path, timecodes_name ); + + /* Create timecodes file */ + timecodes_fd = fopen(timecodes_file, "wb"); + if ( timecodes_fd == NULL ) { + Error("Failed creating timecodes file"); + } + } + } else { + /* No video object */ + videowriter = NULL; + } + +} // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) + +Event::~Event() { + static char sql[ZM_SQL_MED_BUFSIZ]; + struct DeltaTimeval delta_time; + DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 ); + + if ( frames > last_db_frame ) { Debug( 1, "Adding closing frame %d to DB", frames ); - static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, TimeStamp, Delta ) values ( %d, %d, from_unixtime( %ld ), %s%ld.%02ld )", id, frames, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert frame: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } } - static char sql[ZM_SQL_MED_BUFSIZ]; + /* Close the video file */ + if ( videowriter != NULL ) { + int nRet = videowriter->Close(); + if(nRet != 0) { + Error("Failed closing video stream"); + } + delete videowriter; + videowriter = NULL; - struct DeltaTimeval delta_time; - DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 ); + /* Close the timecodes file */ + fclose(timecodes_fd); + timecodes_fd = NULL; + } - snprintf( sql, sizeof(sql), "update Events set Name='%s%d', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, id ); - if ( mysql_query( &dbconn, sql ) ) - { + snprintf( sql, sizeof(sql), "update Events set Name='%s%d', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo = '%s' where Id = %d", monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, video_name, id ); + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't update event: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } } -void Event::createNotes( std::string ¬es ) -{ +void Event::createNotes( std::string ¬es ) { notes.clear(); - for ( StringSetMap::const_iterator mapIter = noteSetMap.begin(); mapIter != noteSetMap.end(); mapIter++ ) - { + for ( StringSetMap::const_iterator mapIter = noteSetMap.begin(); mapIter != noteSetMap.end(); mapIter++ ) { notes += mapIter->first; notes += ": "; const StringSet &stringSet = mapIter->second; - for ( StringSet::const_iterator setIter = stringSet.begin(); setIter != stringSet.end(); setIter++ ) - { + for ( StringSet::const_iterator setIter = stringSet.begin(); setIter != stringSet.end(); setIter++ ) { if ( setIter != stringSet.begin() ) notes += ", "; notes += *setIter; @@ -221,208 +250,95 @@ void Event::createNotes( std::string ¬es ) int Event::sd = -1; -bool Event::OpenFrameSocket( int monitor_id ) -{ - if ( sd > 0 ) - { - close( sd ); - } - - sd = socket( AF_UNIX, SOCK_STREAM, 0); - if ( sd < 0 ) - { - Error( "Can't create socket: %s", strerror(errno) ); - return( false ); - } - - int socket_buffer_size = config.frame_socket_size; - if ( socket_buffer_size > 0 ) - { - if ( setsockopt( sd, SOL_SOCKET, SO_SNDBUF, &socket_buffer_size, sizeof(socket_buffer_size) ) < 0 ) - { - Error( "Can't get socket buffer size to %d, error = %s", socket_buffer_size, strerror(errno) ); - close( sd ); - sd = -1; - return( false ); - } - } - - int flags; - if ( (flags = fcntl( sd, F_GETFL )) < 0 ) - { - Error( "Can't get socket flags, error = %s", strerror(errno) ); - close( sd ); - sd = -1; - return( false ); - } - flags |= O_NONBLOCK; - if ( fcntl( sd, F_SETFL, flags ) < 0 ) - { - Error( "Can't set socket flags, error = %s", strerror(errno) ); - close( sd ); - sd = -1; - return( false ); - } - - char sock_path[PATH_MAX] = ""; - snprintf( sock_path, sizeof(sock_path), "%s/zmf-%d.sock", config.path_socks, monitor_id ); - - struct sockaddr_un addr; - - strncpy( addr.sun_path, sock_path, sizeof(addr.sun_path) ); - addr.sun_family = AF_UNIX; - - if ( connect( sd, (struct sockaddr *)&addr, strlen(addr.sun_path)+sizeof(addr.sun_family)) < 0 ) - { - Warning( "Can't connect to frame server: %s", strerror(errno) ); - close( sd ); - sd = -1; - return( false ); - } - - Debug( 1, "Opened connection to frame server" ); - return( true ); -} - -bool Event::ValidateFrameSocket( int monitor_id ) -{ - if ( sd < 0 ) - { - return( OpenFrameSocket( monitor_id ) ); - } - return( true ); -} - -bool Event::SendFrameImage( const Image *image, bool alarm_frame ) -{ - if ( !ValidateFrameSocket( monitor->Id() ) ) - { - return( false ); - } - - static int jpg_buffer_size = 0; - static unsigned char jpg_buffer[ZM_MAX_IMAGE_SIZE]; - - image->EncodeJpeg( jpg_buffer, &jpg_buffer_size, (alarm_frame&&(config.jpeg_alarm_file_quality>config.jpeg_file_quality))?config.jpeg_alarm_file_quality:config.jpeg_file_quality ); - - static FrameHeader frame_header; - - frame_header.event_id = id; - if ( config.use_deep_storage ) - frame_header.event_time = start_time.tv_sec; - frame_header.frame_id = frames; - frame_header.alarm_frame = alarm_frame; - frame_header.image_length = jpg_buffer_size; - - struct iovec iovecs[2]; - iovecs[0].iov_base = &frame_header; - iovecs[0].iov_len = sizeof(frame_header); - iovecs[1].iov_base = jpg_buffer; - iovecs[1].iov_len = jpg_buffer_size; - - ssize_t writev_size = sizeof(frame_header)+jpg_buffer_size; - ssize_t writev_result = writev( sd, iovecs, sizeof(iovecs)/sizeof(*iovecs)); - if ( writev_result != writev_size ) - { - if ( writev_result < 0 ) - { - if ( errno == EAGAIN ) - { - Warning( "Blocking write detected" ); - } - else - { - Error( "Can't write frame: %s", strerror(errno) ); - close( sd ); - sd = -1; - } - } - else - { - Error( "Incomplete frame write: %zd of %zd bytes written", writev_result, writev_size ); - close( sd ); - sd = -1; - } - return( false ); - } - Debug( 1, "Wrote frame image, %d bytes", jpg_buffer_size ); - - return( true ); -} - -bool Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame ) -{ +bool Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame ) { Image* ImgToWrite; Image* ts_image = NULL; - if ( config.timestamp_on_capture ) // stash the image we plan to use in another pointer regardless if timestamped. + if ( !config.timestamp_on_capture ) // stash the image we plan to use in another pointer regardless if timestamped. { ts_image = new Image(*image); monitor->TimestampImage( ts_image, ×tamp ); ImgToWrite=ts_image; - } - else + } else ImgToWrite=image; - if ( !config.opt_frame_server || !SendFrameImage(ImgToWrite, alarm_frame) ) - { - int thisquality = ( alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality) ) ? config.jpeg_alarm_file_quality : 0 ; // quality to use, zero is default - ImgToWrite->WriteJpeg( event_file, thisquality, (monitor->Exif() ? timestamp : (timeval){0,0}) ); // exif is only timestamp at present this switches on or off for write - } + int thisquality = ( alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality) ) ? config.jpeg_alarm_file_quality : 0 ; // quality to use, zero is default + ImgToWrite->WriteJpeg( event_file, thisquality, (monitor->Exif() ? timestamp : (timeval){0,0}) ); // exif is only timestamp at present this switches on or off for write + if(ts_image) delete(ts_image); // clean up if used. return( true ); } -void Event::updateNotes( const StringSetMap &newNoteSetMap ) -{ +bool Event::WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow ) { + const Image* frameimg = image; + Image ts_image; + + /* Checking for invalid parameters */ + if ( videow == NULL ) { + Error("NULL Video object"); + return false; + } + + /* If the image does not contain a timestamp, add the timestamp */ + if (!config.timestamp_on_capture) { + ts_image = *image; + monitor->TimestampImage( &ts_image, ×tamp ); + frameimg = &ts_image; + } + + /* Calculate delta time */ + struct DeltaTimeval delta_time3; + DELTA_TIMEVAL( delta_time3, timestamp, start_time, DT_PREC_3 ); + unsigned int timeMS = (delta_time3.sec * delta_time3.prec) + delta_time3.fsec; + + /* Encode and write the frame */ + if(videowriter->Encode(frameimg, timeMS) != 0) { + Error("Failed encoding video frame"); + } + + /* Add the frame to the timecodes file */ + fprintf(timecodes_fd, "%u\n", timeMS); + + return( true ); +} + +void Event::updateNotes( const StringSetMap &newNoteSetMap ) { bool update = false; //Info( "Checking notes, %d <> %d", noteSetMap.size(), newNoteSetMap.size() ); - if ( newNoteSetMap.size() > 0 ) - { - if ( noteSetMap.size() == 0 ) - { + if ( newNoteSetMap.size() > 0 ) { + if ( noteSetMap.size() == 0 ) { noteSetMap = newNoteSetMap; update = true; - } - else - { - for ( StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); newNoteSetMapIter != newNoteSetMap.end(); newNoteSetMapIter++ ) - { + } else { + for ( StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); newNoteSetMapIter != newNoteSetMap.end(); newNoteSetMapIter++ ) { const std::string &newNoteGroup = newNoteSetMapIter->first; const StringSet &newNoteSet = newNoteSetMapIter->second; //Info( "Got %d new strings", newNoteSet.size() ); - if ( newNoteSet.size() > 0 ) - { + if ( newNoteSet.size() > 0 ) { StringSetMap::iterator noteSetMapIter = noteSetMap.find( newNoteGroup ); - if ( noteSetMapIter == noteSetMap.end() ) - { + if ( noteSetMapIter == noteSetMap.end() ) { //Info( "Can't find note group %s, copying %d strings", newNoteGroup.c_str(), newNoteSet.size() ); noteSetMap.insert( StringSetMap::value_type( newNoteGroup, newNoteSet ) ); update = true; - } - else - { + } else { StringSet ¬eSet = noteSetMapIter->second; //Info( "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size() ); - for ( StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); newNoteSetIter != newNoteSet.end(); newNoteSetIter++ ) - { + for ( StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); newNoteSetIter != newNoteSet.end(); newNoteSetIter++ ) { const std::string &newNote = *newNoteSetIter; StringSet::iterator noteSetIter = noteSet.find( newNote ); - if ( noteSetIter == noteSet.end() ) - { + if ( noteSetIter == noteSet.end() ) { noteSet.insert( newNote ); update = true; } - } - } - } - } - } - } + } // end for + } // end if ( noteSetMap.size() == 0 + } // end if newNoteSetupMap.size() > 0 + } // end foreach newNoteSetMap + } // end if have old notes + } // end if have new notes - if ( update ) - { + if ( update ) { std::string notes; createNotes( notes ); @@ -434,19 +350,16 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) char notesStr[ZM_SQL_MED_BUFSIZ] = ""; unsigned long notesLen = 0; - if ( !stmt ) - { + if ( !stmt ) { const char *sql = "update Events set Notes = ? where Id = ?"; stmt = mysql_stmt_init( &dbconn ); - if ( mysql_stmt_prepare( stmt, sql, strlen(sql) ) ) - { + if ( mysql_stmt_prepare( stmt, sql, strlen(sql) ) ) { Fatal( "Unable to prepare sql '%s': %s", sql, mysql_stmt_error(stmt) ); } /* Get the parameter count from the statement */ - if ( mysql_stmt_param_count( stmt ) != 2 ) - { + if ( mysql_stmt_param_count( stmt ) != 2 ) { Fatal( "Unexpected parameter count %ld in sql '%s'", mysql_stmt_param_count( stmt ), sql ); } @@ -466,8 +379,7 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) bind[1].length= 0; /* Bind the buffers */ - if ( mysql_stmt_bind_param( stmt, bind ) ) - { + if ( mysql_stmt_bind_param( stmt, bind ) ) { Fatal( "Unable to bind sql '%s': %s", sql, mysql_stmt_error(stmt) ); } } @@ -475,8 +387,7 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) strncpy( notesStr, notes.c_str(), sizeof(notesStr) ); notesLen = notes.length(); - if ( mysql_stmt_execute( stmt ) ) - { + if ( mysql_stmt_execute( stmt ) ) { Fatal( "Unable to execute sql '%s': %s", sql, mysql_stmt_error(stmt) ); } #else @@ -485,30 +396,25 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) mysql_real_escape_string( &dbconn, escapedNotes, notes.c_str(), notes.length() ); snprintf( sql, sizeof(sql), "update Events set Notes = '%s' where Id = %d", escapedNotes, id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert event: %s", mysql_error( &dbconn ) ); } #endif } } -void Event::AddFrames( int n_frames, Image **images, struct timeval **timestamps ) -{ +void Event::AddFrames( int n_frames, Image **images, struct timeval **timestamps ) { for (int i = 0; i < n_frames; i += ZM_SQL_BATCH_SIZE) { AddFramesInternal(n_frames, i, images, timestamps); } } -void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ) -{ +void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ) { static char sql[ZM_SQL_LGE_BUFSIZ]; strncpy( sql, "insert into Frames ( EventId, FrameId, TimeStamp, Delta ) values ", sizeof(sql) ); int frameCount = 0; - for ( int i = start_frame; i < n_frames && i - start_frame < ZM_SQL_BATCH_SIZE; i++ ) - { - if ( !timestamps[i]->tv_sec ) - { + for ( int i = start_frame; i < n_frames && i - start_frame < ZM_SQL_BATCH_SIZE; i++ ) { + if ( !timestamps[i]->tv_sec ) { Debug( 1, "Not adding pre-capture frame %d, zero timestamp", i ); continue; } @@ -517,9 +423,21 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st static char event_file[PATH_MAX]; snprintf( event_file, sizeof(event_file), capture_file_format, path, frames ); - - Debug( 1, "Writing pre-capture frame %d", frames ); - WriteFrameImage( images[i], *(timestamps[i]), event_file ); + if ( monitor->GetOptSaveJPEGs() & 4) { + //If this is the first frame, we should add a thumbnail to the event directory + if(frames == 10){ + char snapshot_file[PATH_MAX]; + snprintf( snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path ); + WriteFrameImage( images[i], *(timestamps[i]), snapshot_file ); + } + } + if ( monitor->GetOptSaveJPEGs() & 1) { + Debug( 1, "Writing pre-capture frame %d", frames ); + WriteFrameImage( images[i], *(timestamps[i]), event_file ); + } + if ( videowriter != NULL ) { + WriteFrameVideo( images[i], *(timestamps[i]), videowriter ); + } struct DeltaTimeval delta_time; DELTA_TIMEVAL( delta_time, *(timestamps[i]), start_time, DT_PREC_2 ); @@ -530,27 +448,21 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st frameCount++; } - if ( frameCount ) - { + if ( frameCount ) { Debug( 1, "Adding %d/%d frames to DB", frameCount, n_frames ); *(sql+strlen(sql)-2) = '\0'; - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert frames: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } last_db_frame = frames; - } - else - { + } else { Debug( 1, "No valid pre-capture frames to add" ); } } -void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *alarm_image ) -{ - if ( !timestamp.tv_sec ) - { +void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *alarm_image ) { + if ( !timestamp.tv_sec ) { Debug( 1, "Not adding new frame, zero timestamp" ); return; } @@ -560,8 +472,21 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * static char event_file[PATH_MAX]; snprintf( event_file, sizeof(event_file), capture_file_format, path, frames ); - Debug( 1, "Writing capture frame %d", frames ); - WriteFrameImage( image, timestamp, event_file ); + if ( monitor->GetOptSaveJPEGs() & 4) { + //If this is the first frame, we should add a thumbnail to the event directory + if(frames == 10){ + char snapshot_file[PATH_MAX]; + snprintf( snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path ); + WriteFrameImage( image, timestamp, snapshot_file ); + } + } + if( monitor->GetOptSaveJPEGs() & 1) { + Debug( 1, "Writing capture frame %d", frames ); + WriteFrameImage( image, timestamp, event_file ); + } + if ( videowriter != NULL ) { + WriteFrameVideo( image, timestamp, videowriter ); + } struct DeltaTimeval delta_time; DELTA_TIMEVAL( delta_time, timestamp, start_time, DT_PREC_2 ); @@ -571,25 +496,30 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * score = 0; bool db_frame = (strcmp(frame_type,"Bulk") != 0) || ((frames%config.bulk_frame_interval)==0) || !frames; - if ( db_frame ) - { + if ( db_frame ) { Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, frame_type ); static char sql[ZM_SQL_MED_BUFSIZ]; snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score ) values ( %d, %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d )", id, frames, frame_type, timestamp.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, score ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert frame: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } last_db_frame = frames; // We are writing a Bulk frame - if ( !strcmp( frame_type,"Bulk") ) - { - snprintf( sql, sizeof(sql), "update Events set Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( !strcmp( frame_type,"Bulk") ) { + snprintf( sql, sizeof(sql), "update Events set Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", + ( delta_time.positive?"":"-" ), + delta_time.sec, delta_time.fsec, + frames, + alarm_frames, + tot_score, + (int)(alarm_frames?(tot_score/alarm_frames):0), + max_score, + id + ); + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't update event: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -598,58 +528,48 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * end_time = timestamp; - // We are writing an Alarm frame - if ( !strcmp( frame_type,"Alarm") ) - { + // We are writing an Alarm frame + if ( !strcmp( frame_type,"Alarm") ) { alarm_frames++; tot_score += score; if ( score > (int)max_score ) max_score = score; - if ( alarm_image ) - { + if ( alarm_image ) { snprintf( event_file, sizeof(event_file), analyse_file_format, path, frames ); Debug( 1, "Writing analysis frame %d", frames ); - WriteFrameImage( alarm_image, timestamp, event_file, true ); + if ( monitor->GetOptSaveJPEGs() & 2) { + WriteFrameImage( alarm_image, timestamp, event_file, true ); + } } } - + /* This makes viewing the diagnostic images impossible because it keeps deleting them - if ( config.record_diag_images ) - { + if ( config.record_diag_images ) { char diag_glob[PATH_MAX] = ""; snprintf( diag_glob, sizeof(diag_glob), "%s/%d/diag-*.jpg", config.dir_events, monitor->Id() ); glob_t pglob; int glob_status = glob( diag_glob, 0, 0, &pglob ); - if ( glob_status != 0 ) - { - if ( glob_status < 0 ) - { + if ( glob_status != 0 ) { + if ( glob_status < 0 ) { Error( "Can't glob '%s': %s", diag_glob, strerror(errno) ); - } - else - { + } else { Debug( 1, "Can't glob '%s': %d", diag_glob, glob_status ); } - } - else - { + } else { char new_diag_path[PATH_MAX] = ""; - for ( int i = 0; i < pglob.gl_pathc; i++ ) - { + for ( int i = 0; i < pglob.gl_pathc; i++ ) { char *diag_path = pglob.gl_pathv[i]; char *diag_file = strstr( diag_path, "diag-" ); - if ( diag_file ) - { + if ( diag_file ) { snprintf( new_diag_path, sizeof(new_diag_path), general_file_format, path, frames, diag_file ); - if ( rename( diag_path, new_diag_path ) < 0 ) - { + if ( rename( diag_path, new_diag_path ) < 0 ) { Error( "Can't rename '%s' to '%s': %s", diag_path, new_diag_path, strerror(errno) ); } } @@ -660,28 +580,24 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * */ } -bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) -{ +bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) { static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "select Id from Events where MonitorId = %d and unix_timestamp( EndTime ) > %ld order by Id asc limit 1", monitor_id, event_time ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_ROW dbrow = mysql_fetch_row( result ); - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -692,17 +608,13 @@ bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) loadEventData( init_event_id ); - if ( event_time ) - { + if ( event_time ) { curr_stream_time = event_time; curr_frame_id = 1; - if ( event_time >= event_data->start_time ) - { - for (unsigned int i = 0; i < event_data->frame_count; i++ ) - { + if ( event_time >= event_data->start_time ) { + for (unsigned int i = 0; i < event_data->frame_count; i++ ) { //Info( "eft %d > et %d", event_data->frames[i].timestamp, event_time ); - if ( event_data->frames[i].timestamp >= event_time ) - { + if ( event_data->frames[i].timestamp >= event_time ) { curr_frame_id = i+1; Debug( 3, "Set cst:%.2f", curr_stream_time ); Debug( 3, "Set cfid:%d", curr_frame_id ); @@ -715,51 +627,42 @@ bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) return( true ); } -bool EventStream::loadInitialEventData( int init_event_id, unsigned int init_frame_id ) -{ +bool EventStream::loadInitialEventData( int init_event_id, unsigned int init_frame_id ) { loadEventData( init_event_id ); - if ( init_frame_id ) - { + if ( init_frame_id ) { curr_stream_time = event_data->frames[init_frame_id-1].timestamp; curr_frame_id = init_frame_id; - } - else - { + } else { curr_stream_time = event_data->start_time; } return( true ); } -bool EventStream::loadEventData( int event_id ) -{ +bool EventStream::loadEventData( int event_id ) { static char sql[ZM_SQL_MED_BUFSIZ]; - snprintf( sql, sizeof(sql), "select M.Id, M.Name, E.Frames, unix_timestamp( StartTime ) as StartTimestamp, max(F.Delta)-min(F.Delta) as Duration from Events as E inner join Monitors as M on E.MonitorId = M.Id inner join Frames as F on E.Id = F.EventId where E.Id = %d group by E.Id", event_id ); + snprintf( sql, sizeof(sql), "SELECT MonitorId, Frames, unix_timestamp( StartTime ) AS StartTimestamp, (SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id) AS Duration, DefaultVideo FROM Events WHERE Id = %d", event_id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } - if ( !mysql_num_rows( result ) ) - { + if ( !mysql_num_rows( result ) ) { Fatal( "Unable to load event %d, not found in DB", event_id ); } MYSQL_ROW dbrow = mysql_fetch_row( result ); - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -768,39 +671,35 @@ bool EventStream::loadEventData( int event_id ) event_data = new EventData; event_data->event_id = event_id; event_data->monitor_id = atoi( dbrow[0] ); - event_data->start_time = atoi(dbrow[3]); - if ( config.use_deep_storage ) - { + event_data->start_time = atoi(dbrow[2]); + if ( config.use_deep_storage ) { struct tm *event_time = localtime( &event_data->start_time ); if ( config.dir_events[0] == '/' ) snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", config.dir_events, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec ); else snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", staticConfig.PATH_WEB.c_str(), config.dir_events, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec ); - } - else - { + } else { if ( config.dir_events[0] == '/' ) snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%ld", config.dir_events, event_data->monitor_id, event_data->event_id ); else snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%ld", staticConfig.PATH_WEB.c_str(), config.dir_events, event_data->monitor_id, event_data->event_id ); } - event_data->frame_count = dbrow[2] == NULL ? 0 : atoi(dbrow[2]); - event_data->duration = atof(dbrow[4]); + event_data->frame_count = dbrow[1] == NULL ? 0 : atoi(dbrow[1]); + event_data->duration = atof(dbrow[3]); + strncpy( event_data->video_file, dbrow[4], sizeof( event_data->video_file )-1 ); updateFrameRate( (double)event_data->frame_count/event_data->duration ); mysql_free_result( result ); snprintf( sql, sizeof(sql), "select FrameId, unix_timestamp( `TimeStamp` ), Delta from Frames where EventId = %d order by FrameId asc", event_id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -811,17 +710,14 @@ bool EventStream::loadEventData( int event_id ) int id, last_id = 0; time_t timestamp, last_timestamp = event_data->start_time; double delta, last_delta = 0.0; - while ( ( dbrow = mysql_fetch_row( result ) ) ) - { + while ( ( dbrow = mysql_fetch_row( result ) ) ) { id = atoi(dbrow[0]); timestamp = atoi(dbrow[1]); delta = atof(dbrow[2]); int id_diff = id - last_id; double frame_delta = (delta-last_delta)/id_diff; - if ( id_diff > 1 ) - { - for ( int i = last_id+1; i < id; i++ ) - { + if ( id_diff > 1 ) { + for ( int i = last_id+1; i < id; i++ ) { event_data->frames[i-1].timestamp = (time_t)(last_timestamp + ((i-last_id)*frame_delta)); event_data->frames[i-1].offset = (time_t)(event_data->frames[i-1].timestamp-event_data->start_time); event_data->frames[i-1].delta = frame_delta; @@ -836,8 +732,7 @@ bool EventStream::loadEventData( int event_id ) last_delta = delta; last_timestamp = timestamp; } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -849,8 +744,7 @@ bool EventStream::loadEventData( int event_id ) mysql_free_result( result ); - if ( forceEventChange || mode == MODE_ALL_GAPLESS ) - { + if ( forceEventChange || mode == MODE_ALL_GAPLESS ) { if ( replay_rate > 0 ) curr_stream_time = event_data->frames[0].timestamp; else @@ -861,245 +755,237 @@ bool EventStream::loadEventData( int event_id ) return( true ); } -void EventStream::processCommand( const CmdMsg *msg ) -{ - Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] ) +void EventStream::processCommand( const CmdMsg *msg ) { + Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] ); // Check for incoming command - switch( (MsgCommand)msg->msg_data[0] ) - { + switch( (MsgCommand)msg->msg_data[0] ) { case CMD_PAUSE : - { - Debug( 1, "Got PAUSE command" ); + { + Debug( 1, "Got PAUSE command" ); - // Set paused flag - paused = true; - replay_rate = ZM_RATE_BASE; - last_frame_sent = TV_2_FLOAT( now ); - break; - } + // Set paused flag + paused = true; + replay_rate = ZM_RATE_BASE; + last_frame_sent = TV_2_FLOAT( now ); + break; + } case CMD_PLAY : - { - Debug( 1, "Got PLAY command" ); - if ( paused ) { - // Clear paused flag - paused = false; + Debug( 1, "Got PLAY command" ); + if ( paused ) + { + // Clear paused flag + paused = false; + } + + // If we are in single event mode and at the last frame, replay the current event + if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) + curr_frame_id = 1; + + replay_rate = ZM_RATE_BASE; + break; } - - // If we are in single event mode and at the last frame, replay the current event - if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) - curr_frame_id = 1; - - replay_rate = ZM_RATE_BASE; - break; - } case CMD_VARPLAY : - { - Debug( 1, "Got VARPLAY command" ); - if ( paused ) { - // Clear paused flag - paused = false; + Debug( 1, "Got VARPLAY command" ); + if ( paused ) + { + // Clear paused flag + paused = false; + } + replay_rate = ntohs(((unsigned char)msg->msg_data[2]<<8)|(unsigned char)msg->msg_data[1])-32768; + break; } - replay_rate = ntohs(((unsigned char)msg->msg_data[2]<<8)|(unsigned char)msg->msg_data[1])-32768; - break; - } case CMD_STOP : - { - Debug( 1, "Got STOP command" ); + { + Debug( 1, "Got STOP command" ); - // Clear paused flag - paused = false; - break; - } + // Clear paused flag + paused = false; + break; + } case CMD_FASTFWD : - { - Debug( 1, "Got FAST FWD command" ); - if ( paused ) { - // Clear paused flag - paused = false; + Debug( 1, "Got FAST FWD command" ); + if ( paused ) { + // Clear paused flag + paused = false; + } + // Set play rate + switch ( replay_rate ) { + case 2 * ZM_RATE_BASE : + replay_rate = 5 * ZM_RATE_BASE; + break; + case 5 * ZM_RATE_BASE : + replay_rate = 10 * ZM_RATE_BASE; + break; + case 10 * ZM_RATE_BASE : + replay_rate = 25 * ZM_RATE_BASE; + break; + case 25 * ZM_RATE_BASE : + case 50 * ZM_RATE_BASE : + replay_rate = 50 * ZM_RATE_BASE; + break; + default : + replay_rate = 2 * ZM_RATE_BASE; + break; + } + break; } - // Set play rate - switch ( replay_rate ) - { - case 2 * ZM_RATE_BASE : - replay_rate = 5 * ZM_RATE_BASE; - break; - case 5 * ZM_RATE_BASE : - replay_rate = 10 * ZM_RATE_BASE; - break; - case 10 * ZM_RATE_BASE : - replay_rate = 25 * ZM_RATE_BASE; - break; - case 25 * ZM_RATE_BASE : - case 50 * ZM_RATE_BASE : - replay_rate = 50 * ZM_RATE_BASE; - break; - default : - replay_rate = 2 * ZM_RATE_BASE; - break; - } - break; - } case CMD_SLOWFWD : - { - Debug( 1, "Got SLOW FWD command" ); - // Set paused flag - paused = true; - // Set play rate - replay_rate = ZM_RATE_BASE; - // Set step - step = 1; - break; - } + { + Debug( 1, "Got SLOW FWD command" ); + // Set paused flag + paused = true; + // Set play rate + replay_rate = ZM_RATE_BASE; + // Set step + step = 1; + break; + } case CMD_SLOWREV : - { - Debug( 1, "Got SLOW REV command" ); - // Set paused flag - paused = true; - // Set play rate - replay_rate = ZM_RATE_BASE; - // Set step - step = -1; - break; - } + { + Debug( 1, "Got SLOW REV command" ); + // Set paused flag + paused = true; + // Set play rate + replay_rate = ZM_RATE_BASE; + // Set step + step = -1; + break; + } case CMD_FASTREV : - { - Debug( 1, "Got FAST REV command" ); - if ( paused ) { - // Clear paused flag - paused = false; + Debug( 1, "Got FAST REV command" ); + if ( paused ) { + // Clear paused flag + paused = false; + } + // Set play rate + switch ( replay_rate ) { + case -2 * ZM_RATE_BASE : + replay_rate = -5 * ZM_RATE_BASE; + break; + case -5 * ZM_RATE_BASE : + replay_rate = -10 * ZM_RATE_BASE; + break; + case -10 * ZM_RATE_BASE : + replay_rate = -25 * ZM_RATE_BASE; + break; + case -25 * ZM_RATE_BASE : + case -50 * ZM_RATE_BASE : + replay_rate = -50 * ZM_RATE_BASE; + break; + default : + replay_rate = -2 * ZM_RATE_BASE; + break; + } + break; } - // Set play rate - switch ( replay_rate ) - { - case -2 * ZM_RATE_BASE : - replay_rate = -5 * ZM_RATE_BASE; - break; - case -5 * ZM_RATE_BASE : - replay_rate = -10 * ZM_RATE_BASE; - break; - case -10 * ZM_RATE_BASE : - replay_rate = -25 * ZM_RATE_BASE; - break; - case -25 * ZM_RATE_BASE : - case -50 * ZM_RATE_BASE : - replay_rate = -50 * ZM_RATE_BASE; - break; - default : - replay_rate = -2 * ZM_RATE_BASE; - break; - } - break; - } case CMD_ZOOMIN : - { - x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; - y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; - Debug( 1, "Got ZOOM IN command, to %d,%d", x, y ); - switch ( zoom ) { - case 100: - zoom = 150; - break; - case 150: - zoom = 200; - break; - case 200: - zoom = 300; - break; - case 300: - zoom = 400; - break; - case 400: - default : - zoom = 500; - break; + x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; + y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; + Debug( 1, "Got ZOOM IN command, to %d,%d", x, y ); + switch ( zoom ) { + case 100: + zoom = 150; + break; + case 150: + zoom = 200; + break; + case 200: + zoom = 300; + break; + case 300: + zoom = 400; + break; + case 400: + default : + zoom = 500; + break; + } + break; } - break; - } case CMD_ZOOMOUT : - { - Debug( 1, "Got ZOOM OUT command" ); - switch ( zoom ) { - case 500: - zoom = 400; - break; - case 400: - zoom = 300; - break; - case 300: - zoom = 200; - break; - case 200: - zoom = 150; - break; - case 150: - default : - zoom = 100; - break; + Debug( 1, "Got ZOOM OUT command" ); + switch ( zoom ) { + case 500: + zoom = 400; + break; + case 400: + zoom = 300; + break; + case 300: + zoom = 200; + break; + case 200: + zoom = 150; + break; + case 150: + default : + zoom = 100; + break; + } + break; } - break; - } case CMD_PAN : - { - x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; - y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; - Debug( 1, "Got PAN command, to %d,%d", x, y ); - break; - } + { + x = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; + y = ((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; + Debug( 1, "Got PAN command, to %d,%d", x, y ); + break; + } case CMD_SCALE : - { - scale = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; - Debug( 1, "Got SCALE command, to %d", scale ); - break; - } + { + scale = ((unsigned char)msg->msg_data[1]<<8)|(unsigned char)msg->msg_data[2]; + Debug( 1, "Got SCALE command, to %d", scale ); + break; + } case CMD_PREV : - { - Debug( 1, "Got PREV command" ); - if ( replay_rate >= 0 ) - curr_frame_id = 0; - else - curr_frame_id = event_data->frame_count+1; - paused = false; - forceEventChange = true; - break; - } + { + Debug( 1, "Got PREV command" ); + if ( replay_rate >= 0 ) + curr_frame_id = 0; + else + curr_frame_id = event_data->frame_count+1; + paused = false; + forceEventChange = true; + break; + } case CMD_NEXT : - { - Debug( 1, "Got NEXT command" ); - if ( replay_rate >= 0 ) - curr_frame_id = event_data->frame_count+1; - else - curr_frame_id = 0; - paused = false; - forceEventChange = true; - break; - } + { + Debug( 1, "Got NEXT command" ); + if ( replay_rate >= 0 ) + curr_frame_id = event_data->frame_count+1; + else + curr_frame_id = 0; + paused = false; + forceEventChange = true; + break; + } case CMD_SEEK : - { - int offset = ((unsigned char)msg->msg_data[1]<<24)|((unsigned char)msg->msg_data[2]<<16)|((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; - curr_frame_id = (int)(event_data->frame_count*offset/event_data->duration); - Debug( 1, "Got SEEK command, to %d (new cfid: %d)", offset, curr_frame_id ); - break; - } + { + int offset = ((unsigned char)msg->msg_data[1]<<24)|((unsigned char)msg->msg_data[2]<<16)|((unsigned char)msg->msg_data[3]<<8)|(unsigned char)msg->msg_data[4]; + curr_frame_id = (int)(event_data->frame_count*offset/event_data->duration); + Debug( 1, "Got SEEK command, to %d (new cfid: %d)", offset, curr_frame_id ); + break; + } case CMD_QUERY : - { - Debug( 1, "Got QUERY command, sending STATUS" ); - break; - } - case CMD_QUIT : - { - Info ("User initiated exit - CMD_QUIT"); - break; - } + { + Debug( 1, "Got QUERY command, sending STATUS" ); + break; + } + case CMD_QUIT : + { + Info ("User initiated exit - CMD_QUIT"); + break; + } default : - { - // Do nothing, for now - } + { + // Do nothing, for now + } } struct { int event; @@ -1125,8 +1011,7 @@ void EventStream::processCommand( const CmdMsg *msg ) DataMsg status_msg; status_msg.msg_type = MSG_DATA_EVENT; memcpy( &status_msg.msg_data, &status_data, sizeof(status_data) ); - if ( sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) ) < 0 ) - { + if ( sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) ) < 0 ) { //if ( errno != EAGAIN ) { Error( "Can't sendto on sd %d: %s", sd, strerror(errno) ); @@ -1140,49 +1025,39 @@ void EventStream::processCommand( const CmdMsg *msg ) updateFrameRate( (double)event_data->frame_count/event_data->duration ); } -void EventStream::checkEventLoaded() -{ +void EventStream::checkEventLoaded() { bool reload_event = false; static char sql[ZM_SQL_SML_BUFSIZ]; - if ( curr_frame_id <= 0 ) - { + if ( curr_frame_id <= 0 ) { snprintf( sql, sizeof(sql), "select Id from Events where MonitorId = %ld and Id < %ld order by Id desc limit 1", event_data->monitor_id, event_data->event_id ); reload_event = true; - } - else if ( (unsigned int)curr_frame_id > event_data->frame_count ) - { + } else if ( (unsigned int)curr_frame_id > event_data->frame_count ) { snprintf( sql, sizeof(sql), "select Id from Events where MonitorId = %ld and Id > %ld order by Id asc limit 1", event_data->monitor_id, event_data->event_id ); reload_event = true; } - if ( reload_event ) - { - if ( forceEventChange || mode != MODE_SINGLE ) - { + if ( reload_event ) { + if ( forceEventChange || mode != MODE_SINGLE ) { //Info( "SQL:%s", sql ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_ROW dbrow = mysql_fetch_row( result ); - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } - if ( dbrow ) - { + if ( dbrow ) { int event_id = atoi(dbrow[0]); Debug( 1, "Loading new event %d", event_id ); @@ -1194,9 +1069,7 @@ void EventStream::checkEventLoaded() else curr_frame_id = 1; Debug( 2, "New frame id = %d", curr_frame_id ); - } - else - { + } else { if ( curr_frame_id <= 0 ) curr_frame_id = 1; else @@ -1205,9 +1078,7 @@ void EventStream::checkEventLoaded() } mysql_free_result( result ); forceEventChange = false; - } - else - { + } else { if ( curr_frame_id <= 0 ) curr_frame_id = 1; else @@ -1217,32 +1088,41 @@ void EventStream::checkEventLoaded() } } -bool EventStream::sendFrame( int delta_us ) -{ +bool EventStream::sendFrame( int delta_us ) { Debug( 2, "Sending frame %d", curr_frame_id ); static char filepath[PATH_MAX]; static struct stat filestat; FILE *fdj = NULL; - - snprintf( filepath, sizeof(filepath), Event::capture_file_format, event_data->path, curr_frame_id ); + + // This needs to be abstracted. If we are saving jpgs, then load the capture file. If we are only saving analysis frames, then send that. + if ( monitor->GetOptSaveJPEGs() & 1 ) { + snprintf( filepath, sizeof(filepath), Event::capture_file_format, event_data->path, curr_frame_id ); + } else if ( monitor->GetOptSaveJPEGs() & 2 ) { + snprintf( filepath, sizeof(filepath), Event::analyse_file_format, event_data->path, curr_frame_id ); + if ( stat( filepath, &filestat ) < 0 ) { + Debug(1, "%s not found, dalling back to capture"); + snprintf( filepath, sizeof(filepath), Event::capture_file_format, event_data->path, curr_frame_id ); + } + + } else { + Fatal("JPEGS not saved.zms is not capable of streaming jpegs from mp4 yet"); + return false; + } #if HAVE_LIBAVCODEC - if ( type == STREAM_MPEG ) - { + if ( type == STREAM_MPEG ) { Image image( filepath ); Image *send_image = prepareImage( &image ); - if ( !vid_stream ) - { + if ( !vid_stream ) { vid_stream = new VideoStream( "pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height() ); fprintf( stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType() ); vid_stream->OpenStream(); } /* double pts = */ vid_stream->EncodeFrame( send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_us*1000 ); - } - else + } else #endif // HAVE_LIBAVCODEC { static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; @@ -1257,31 +1137,26 @@ bool EventStream::sendFrame( int delta_us ) if ( type != STREAM_JPEG ) send_raw = false; - if ( send_raw ) - { + if ( send_raw ) { fdj = fopen( filepath, "rb" ); - if ( !fdj ) - { + if ( !fdj ) { Error( "Can't open %s: %s", filepath, strerror(errno) ); return( false ); } -#if HAVE_SENDFILE +#if HAVE_SENDFILE if( fstat(fileno(fdj),&filestat) < 0 ) { - Error( "Failed getting information about file %s: %s", filepath, strerror(errno) ); - return( false ); - } + Error( "Failed getting information about file %s: %s", filepath, strerror(errno) ); + return( false ); + } #else - img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj ); + img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj ); #endif - } - else - { + } else { Image image( filepath ); Image *send_image = prepareImage( &image ); - switch( type ) - { + switch( type ) { case STREAM_JPEG : send_image->EncodeJpeg( img_buffer, &img_buffer_size ); break; @@ -1305,8 +1180,7 @@ bool EventStream::sendFrame( int delta_us ) } } - switch( type ) - { + switch( type ) { case STREAM_JPEG : fprintf( stdout, "Content-Type: image/jpeg\r\n" ); break; @@ -1322,34 +1196,33 @@ bool EventStream::sendFrame( int delta_us ) } - if(send_raw) { -#if HAVE_SENDFILE - fprintf( stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size ); - if(zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size) != (int)filestat.st_size) { - /* sendfile() failed, use standard way instead */ - img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj ); + if(send_raw) { +#if HAVE_SENDFILE + fprintf( stdout, "Content-Length: %d\r\n\r\n", (int)filestat.st_size ); + if(zm_sendfile(fileno(stdout), fileno(fdj), 0, (int)filestat.st_size) != (int)filestat.st_size) { + /* sendfile() failed, use standard way instead */ + img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj ); + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { + Error("Unable to send raw frame %u: %s",curr_frame_id,strerror(errno)); + return( false ); + } + } +#else + fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { Error("Unable to send raw frame %u: %s",curr_frame_id,strerror(errno)); return( false ); } +#endif + fclose(fdj); /* Close the file handle */ + } else { + fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { + Error( "Unable to send stream frame: %s", strerror(errno) ); + return( false ); + } } -#else - fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); - if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { - Error("Unable to send raw frame %u: %s",curr_frame_id,strerror(errno)); - return( false ); - } -#endif - fclose(fdj); /* Close the file handle */ - } else { - fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); - if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) - { - Error( "Unable to send stream frame: %s", strerror(errno) ); - return( false ); - } - } - + fprintf( stdout, "\r\n\r\n" ); fflush( stdout ); } @@ -1357,8 +1230,7 @@ bool EventStream::sendFrame( int delta_us ) return( true ); } -void EventStream::runStream() -{ +void EventStream::runStream() { Event::Initialise(); openComms(); @@ -1370,15 +1242,13 @@ void EventStream::runStream() if ( type == STREAM_JPEG ) fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" ); - if ( !event_data ) - { + if ( !event_data ) { sendTextFrame( "No event data found" ); exit( 0 ); } unsigned int delta_us = 0; - while( !zm_terminate ) - { + while( !zm_terminate ) { gettimeofday( &now, NULL ); while(checkCommandQueue()); @@ -1394,27 +1264,21 @@ void EventStream::runStream() //Info( "cst:%.2f", curr_stream_time ); //Info( "cfid:%d", curr_frame_id ); //Info( "fdt:%d", frame_data->timestamp ); - if ( !paused ) - { + if ( !paused ) { bool in_event = true; double time_to_event = 0; - if ( replay_rate > 0 ) - { + if ( replay_rate > 0 ) { time_to_event = event_data->frames[0].timestamp - curr_stream_time; if ( time_to_event > 0 ) in_event = false; - } - else if ( replay_rate < 0 ) - { + } else if ( replay_rate < 0 ) { time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp; if ( time_to_event > 0 ) in_event = false; } - if ( !in_event ) - { + if ( !in_event ) { double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; - if ( actual_delta_time > 1 ) - { + if ( actual_delta_time > 1 ) { static char frame_text[64]; snprintf( frame_text, sizeof(frame_text), "Time to next event = %d seconds", (int)time_to_event ); if ( !sendTextFrame( frame_text ) ) @@ -1432,31 +1296,24 @@ void EventStream::runStream() // Figure out if we should send this frame bool send_frame = false; - if ( !paused ) - { + if ( !paused ) { // If we are streaming and this frame is due to be sent - if ( ((curr_frame_id-1)%frame_mod) == 0 ) - { + if ( ((curr_frame_id-1)%frame_mod) == 0 ) { delta_us = (unsigned int)(frame_data->delta * 1000000); // if effective > base we should speed up frame delivery delta_us = (unsigned int)((delta_us * base_fps)/effective_fps); // but must not exceed maxfps - delta_us = max(delta_us, 1000000 / maxfps); + delta_us = max(delta_us, 1000000 / maxfps); send_frame = true; } - } - else if ( step != 0 ) - { + } else if ( step != 0 ) { // We are paused and are just stepping forward or backward one frame step = 0; send_frame = true; - } - else - { + } else { // We are paused, and doing nothing double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; - if ( actual_delta_time > MAX_STREAM_DELAY ) - { + if ( actual_delta_time > MAX_STREAM_DELAY ) { // Send keepalive Debug( 2, "Sending keepalive frame" ); send_frame = true; @@ -1469,20 +1326,16 @@ void EventStream::runStream() curr_stream_time = frame_data->timestamp; - if ( !paused ) - { + if ( !paused ) { curr_frame_id += replay_rate>0?1:-1; - if ( send_frame && type != STREAM_MPEG ) - { + if ( send_frame && type != STREAM_MPEG ) { Debug( 3, "dUs: %d", delta_us ); usleep( delta_us ); } - } - else - { + } else { usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) ); } - } + } // end while ! zm_terminate #if HAVE_LIBAVCODEC if ( type == STREAM_MPEG ) delete vid_stream; diff --git a/src/zm_event.h b/src/zm_event.h index f3c5d7961..d6dd154fa 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_EVENT_H @@ -37,6 +37,7 @@ #include "zm.h" #include "zm_image.h" #include "zm_stream.h" +#include "zm_video.h" class Zone; class Monitor; @@ -46,8 +47,7 @@ class Monitor; // // Class describing events, i.e. captured periods of activity. // -class Event -{ +class Event { friend class EventStream; protected: @@ -55,6 +55,7 @@ protected: static char capture_file_format[PATH_MAX]; static char analyse_file_format[PATH_MAX]; static char general_file_format[PATH_MAX]; + static char video_file_format[PATH_MAX]; protected: static int sd; @@ -66,8 +67,7 @@ public: protected: typedef enum { NORMAL, BULK, ALARM } FrameType; - struct PreAlarmData - { + struct PreAlarmData { Image *image; struct timeval timestamp; unsigned int score; @@ -84,24 +84,31 @@ protected: struct timeval end_time; std::string cause; StringSetMap noteSetMap; + bool videoEvent; int frames; int alarm_frames; unsigned int tot_score; unsigned int max_score; char path[PATH_MAX]; + VideoWriter* videowriter; + FILE* timecodes_fd; + char video_name[PATH_MAX]; + char video_file[PATH_MAX]; + char timecodes_name[PATH_MAX]; + char timecodes_file[PATH_MAX]; protected: int last_db_frame; protected: - static void Initialise() - { + static void Initialise() { if ( initialised ) return; snprintf( capture_file_format, sizeof(capture_file_format), "%%s/%%0%dd-capture.jpg", config.event_image_digits ); snprintf( analyse_file_format, sizeof(analyse_file_format), "%%s/%%0%dd-analyse.jpg", config.event_image_digits ); snprintf( general_file_format, sizeof(general_file_format), "%%s/%%0%dd-%%s", config.event_image_digits ); + snprintf( video_file_format, sizeof(video_file_format), "%%s/%%s"); initialised = true; } @@ -113,7 +120,7 @@ public: static bool ValidateFrameSocket( int ); public: - Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap ); + Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent=false ); ~Event(); int Id() const { return( id ); } @@ -127,6 +134,7 @@ public: bool SendFrameImage( const Image *image, bool alarm_frame=false ); bool WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame=false ); + bool WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow ); void updateNotes( const StringSetMap &stringSetMap ); @@ -137,28 +145,26 @@ private: void AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ); public: - static const char *getSubPath( struct tm *time ) - { + static const char *getSubPath( struct tm *time ) { static char subpath[PATH_MAX] = ""; snprintf( subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec ); return( subpath ); } - static const char *getSubPath( time_t *time ) - { + static const char *getSubPath( time_t *time ) { return( Event::getSubPath( localtime( time ) ) ); } + char* getEventFile(void) { + return video_file; + } + public: - static int PreAlarmCount() - { + static int PreAlarmCount() { return( pre_alarm_count ); } - static void EmptyPreAlarmFrames() - { - if ( pre_alarm_count > 0 ) - { - for ( int i = 0; i < MAX_PRE_ALARM_FRAMES; i++ ) - { + static void EmptyPreAlarmFrames() { + if ( pre_alarm_count > 0 ) { + for ( int i = 0; i < MAX_PRE_ALARM_FRAMES; i++ ) { delete pre_alarm_data[i].image; delete pre_alarm_data[i].alarm_frame; } @@ -166,29 +172,24 @@ public: } pre_alarm_count = 0; } - static void AddPreAlarmFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL ) - { + static void AddPreAlarmFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL ) { pre_alarm_data[pre_alarm_count].image = new Image( *image ); pre_alarm_data[pre_alarm_count].timestamp = timestamp; pre_alarm_data[pre_alarm_count].score = score; - if ( alarm_frame ) - { + if ( alarm_frame ) { pre_alarm_data[pre_alarm_count].alarm_frame = new Image( *alarm_frame ); } pre_alarm_count++; } - void SavePreAlarmFrames() - { - for ( int i = 0; i < pre_alarm_count; i++ ) - { + void SavePreAlarmFrames() { + for ( int i = 0; i < pre_alarm_count; i++ ) { AddFrame( pre_alarm_data[i].image, pre_alarm_data[i].timestamp, pre_alarm_data[i].score, pre_alarm_data[i].alarm_frame ); } EmptyPreAlarmFrames(); } }; -class EventStream : public StreamBase -{ +class EventStream : public StreamBase { public: typedef enum { MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode; @@ -201,16 +202,16 @@ protected: bool in_db; }; - struct EventData - { + struct EventData { unsigned long event_id; unsigned long monitor_id; unsigned long frame_count; - time_t start_time; - double duration; - char path[PATH_MAX]; - int n_frames; - FrameData *frames; + time_t start_time; + double duration; + char path[PATH_MAX]; + int n_frames; + FrameData *frames; + char video_file[PATH_MAX]; }; protected: @@ -238,8 +239,7 @@ protected: bool sendFrame( int delta_us ); public: - EventStream() - { + EventStream() { mode = DEFAULT_MODE; forceEventChange = false; @@ -249,18 +249,15 @@ public: event_data = 0; } - void setStreamStart( int init_event_id, unsigned int init_frame_id=0 ) - { + void setStreamStart( int init_event_id, unsigned int init_frame_id=0 ) { loadInitialEventData( init_event_id, init_frame_id ); loadMonitor( event_data->monitor_id ); } - void setStreamStart( int monitor_id, time_t event_time ) - { + void setStreamStart( int monitor_id, time_t event_time ) { loadInitialEventData( monitor_id, event_time ); loadMonitor( monitor_id ); } - void setStreamMode( StreamMode p_mode ) - { + void setStreamMode( StreamMode p_mode ) { mode = p_mode; } void runStream(); diff --git a/src/zm_exception.cpp b/src/zm_exception.cpp index b7fe9e9a4..993a56866 100644 --- a/src/zm_exception.cpp +++ b/src/zm_exception.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm_exception.h" diff --git a/src/zm_exception.h b/src/zm_exception.h index a503be7c9..83d1ecab1 100644 --- a/src/zm_exception.h +++ b/src/zm_exception.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_EXCEPTION_H diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index e7c9edbef..3cccdf3e8 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "zm_ffmpeg.h" @@ -23,6 +23,16 @@ #if HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE +void FFMPEGInit() { + static bool bInit = false; + + if(!bInit) { + av_register_all(); + av_log_set_level(AV_LOG_DEBUG); + bInit = true; + } +} + #if HAVE_LIBAVUTIL enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder) { enum _AVPIXELFORMAT pf; @@ -31,44 +41,93 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp switch(p_colours) { case ZM_COLOUR_RGB24: - { - if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { - /* BGR subpixel order */ - pf = AV_PIX_FMT_BGR24; - } else { - /* Assume RGB subpixel order */ - pf = AV_PIX_FMT_RGB24; - } - break; - } + { + if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { + /* BGR subpixel order */ + pf = AV_PIX_FMT_BGR24; + } else { + /* Assume RGB subpixel order */ + pf = AV_PIX_FMT_RGB24; + } + break; + } case ZM_COLOUR_RGB32: - { - if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - /* ARGB subpixel order */ - pf = AV_PIX_FMT_ARGB; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - /* ABGR subpixel order */ - pf = AV_PIX_FMT_ABGR; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - /* BGRA subpixel order */ - pf = AV_PIX_FMT_BGRA; - } else { - /* Assume RGBA subpixel order */ - pf = AV_PIX_FMT_RGBA; - } - break; - } + { + if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + /* ARGB subpixel order */ + pf = AV_PIX_FMT_ARGB; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + /* ABGR subpixel order */ + pf = AV_PIX_FMT_ABGR; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + /* BGRA subpixel order */ + pf = AV_PIX_FMT_BGRA; + } else { + /* Assume RGBA subpixel order */ + pf = AV_PIX_FMT_RGBA; + } + break; + } case ZM_COLOUR_GRAY8: - pf = AV_PIX_FMT_GRAY8; - break; + pf = AV_PIX_FMT_GRAY8; + break; default: - Panic("Unexpected colours: %d",p_colours); - pf = AV_PIX_FMT_GRAY8; /* Just to shush gcc variable may be unused warning */ - break; + Panic("Unexpected colours: %d",p_colours); + pf = AV_PIX_FMT_GRAY8; /* Just to shush gcc variable may be unused warning */ + break; } return pf; } +/* The following is copied directly from newer ffmpeg. */ +#if LIBAVUTIL_VERSION_CHECK(52, 7, 0, 17, 100) +#else +static int parse_key_value_pair(AVDictionary **pm, const char **buf, + const char *key_val_sep, const char *pairs_sep, + int flags) +{ + char *key = av_get_token(buf, key_val_sep); + char *val = NULL; + int ret; + + if (key && *key && strspn(*buf, key_val_sep)) { + (*buf)++; + val = av_get_token(buf, pairs_sep); + } + + if (key && *key && val && *val) + ret = av_dict_set(pm, key, val, flags); + else + ret = AVERROR(EINVAL); + + av_freep(&key); + av_freep(&val); + + return ret; +} +int av_dict_parse_string(AVDictionary **pm, const char *str, + const char *key_val_sep, const char *pairs_sep, + int flags) + { + int ret; + + if (!str) + return 0; + + /* ignore STRDUP flags */ + flags &= ~(AV_DICT_DONT_STRDUP_KEY | AV_DICT_DONT_STRDUP_VAL); + + while (*str) { + if ((ret = parse_key_value_pair(pm, &str, key_val_sep, pairs_sep, flags)) < 0) + return ret; + + if (*str) + str++; + } + + return 0; + } +#endif #endif // HAVE_LIBAVUTIL #if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL @@ -99,25 +158,17 @@ SWScale::SWScale() : gotdefaults(false), swscale_ctx(NULL), input_avframe(NULL), SWScale::~SWScale() { /* Free up everything */ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) av_frame_free( &input_avframe ); -#else - av_freep( &input_avframe ); -#endif //input_avframe = NULL; -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) av_frame_free( &output_avframe ); -#else - av_freep( &output_avframe ); -#endif //output_avframe = NULL; if(swscale_ctx) { sws_freeContext(swscale_ctx); swscale_ctx = NULL; } - + Debug(4,"SWScale object destroyed"); } @@ -140,11 +191,11 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint Error("NULL Input or output buffer"); return -1; } - if(in_pf == 0 || out_pf == 0) { - Error("Invalid input or output pixel formats"); - return -2; - } - if(!width || !height) { + // if(in_pf == 0 || out_pf == 0) { + // Error("Invalid input or output pixel formats"); + // return -2; + // } + if (!width || !height) { Error("Invalid width or height"); return -3; } @@ -174,24 +225,37 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint #else size_t outsize = avpicture_get_size(out_pf, width, height); #endif + if(outsize < out_buffer_size) { Error("The output buffer is undersized for the output format. Required: %d Available: %d", outsize, out_buffer_size); return -5; } /* Get the context */ - swscale_ctx = sws_getCachedContext( NULL, width, height, in_pf, width, height, out_pf, 0, NULL, NULL, NULL ); + swscale_ctx = sws_getCachedContext( swscale_ctx, width, height, in_pf, width, height, out_pf, SWS_FAST_BILINEAR, NULL, NULL, NULL ); if(swscale_ctx == NULL) { Error("Failed getting swscale context"); return -6; } /* Fill in the buffers */ - if(!avpicture_fill( (AVPicture*)input_avframe, (uint8_t*)in_buffer, in_pf, width, height ) ) { +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + if (av_image_fill_arrays(input_avframe->data, input_avframe->linesize, + (uint8_t*) in_buffer, in_pf, width, height, 1) <= 0) { +#else + if (avpicture_fill((AVPicture*) input_avframe, (uint8_t*) in_buffer, + in_pf, width, height) <= 0) { +#endif Error("Failed filling input frame with input buffer"); return -7; } - if(!avpicture_fill( (AVPicture*)output_avframe, out_buffer, out_pf, width, height ) ) { +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + if (av_image_fill_arrays(output_avframe->data, output_avframe->linesize, + out_buffer, out_pf, width, height, 1) <= 0) { +#else + if (avpicture_fill((AVPicture*) output_avframe, out_buffer, out_pf, width, + height) <= 0) { +#endif Error("Failed filling output frame with output buffer"); return -8; } @@ -240,4 +304,208 @@ int SWScale::ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_si } #endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL + #endif // HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE + +#if HAVE_LIBAVUTIL +int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb){ + int64_t a, b, this_thing; + + av_assert0(in_ts != AV_NOPTS_VALUE); + av_assert0(duration >= 0); + + if (*last == AV_NOPTS_VALUE || !duration || in_tb.num*(int64_t)out_tb.den <= out_tb.num*(int64_t)in_tb.den) { +simple_round: + *last = av_rescale_q(in_ts, in_tb, fs_tb) + duration; + return av_rescale_q(in_ts, in_tb, out_tb); + } + + a = av_rescale_q_rnd(2*in_ts-1, in_tb, fs_tb, AV_ROUND_DOWN) >>1; + b = (av_rescale_q_rnd(2*in_ts+1, in_tb, fs_tb, AV_ROUND_UP )+1)>>1; + if (*last < 2*a - b || *last > 2*b - a) + goto simple_round; + + this_thing = av_clip64(*last, a, b); + *last = this_thing + duration; + + return av_rescale_q(this_thing, fs_tb, out_tb); +} +#endif + +int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename) { + AVFormatContext *s = avformat_alloc_context(); + int ret = 0; + + *avctx = NULL; + if (!s) { + av_log(s, AV_LOG_ERROR, "Out of memory\n"); + ret = AVERROR(ENOMEM); + return ret; + } + + if (!oformat) { + if (format) { + oformat = av_guess_format(format, NULL, NULL); + if (!oformat) { + av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format); + ret = AVERROR(EINVAL); + } + } else { + oformat = av_guess_format(NULL, filename, NULL); + if (!oformat) { + ret = AVERROR(EINVAL); + av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", filename); + } + } + } + + if (!oformat) { + if (format) { + oformat = av_guess_format(format, NULL, NULL); + if (!oformat) { + av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format); + ret = AVERROR(EINVAL); + } + } else { + oformat = av_guess_format(NULL, filename, NULL); + if (!oformat) { + ret = AVERROR(EINVAL); + av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", filename); + } + } + } + + if (ret) { + avformat_free_context(s); + return ret; + } else { + s->oformat = oformat; + if (s->oformat->priv_data_size > 0) { + s->priv_data = av_mallocz(s->oformat->priv_data_size); + if (s->priv_data) { + if (s->oformat->priv_class) { + *(const AVClass**)s->priv_data= s->oformat->priv_class; + av_opt_set_defaults(s->priv_data); + } + } else { + av_log(s, AV_LOG_ERROR, "Out of memory\n"); + ret = AVERROR(ENOMEM); + return ret; + } + s->priv_data = NULL; + } + + if (filename) strncpy(s->filename, filename, sizeof(s->filename)); + *avctx = s; + return 0; + } +} + +static void zm_log_fps(double d, const char *postfix) { + uint64_t v = lrintf(d * 100); + if (!v) { + Debug(1, "%1.4f %s", d, postfix); + } else if (v % 100) { + Debug(1, "%3.2f %s", d, postfix); + } else if (v % (100 * 1000)) { + Debug(1, "%1.0f %s", d, postfix); + } else + Debug(1, "%1.0fk %s", d / 1000, postfix); +} + +/* "user interface" functions */ +void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) { + char buf[256]; + Debug(1, "Dumping stream index i(%d) index(%d)", i, index ); + int flags = (is_output ? ic->oformat->flags : ic->iformat->flags); + AVStream *st = ic->streams[i]; + AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0); + + avcodec_string(buf, sizeof(buf), st->codec, is_output); + Debug(1, " Stream #%d:%d", index, i); + + /* the pid is an important information, so we display it */ + /* XXX: add a generic system */ + if (flags & AVFMT_SHOW_IDS) + Debug(1, "[0x%x]", st->id); + if (lang) + Debug(1, "(%s)", lang->value); + Debug(1, ", %d, %d/%d", st->codec_info_nb_frames, st->time_base.num, st->time_base.den); + Debug(1, ": %s", buf); + + if (st->sample_aspect_ratio.num && // default + av_cmp_q(st->sample_aspect_ratio, st->codec->sample_aspect_ratio)) { + AVRational display_aspect_ratio; + av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, + st->codec->width * (int64_t)st->sample_aspect_ratio.num, + st->codec->height * (int64_t)st->sample_aspect_ratio.den, + 1024 * 1024); + Debug(1, ", SAR %d:%d DAR %d:%d", + st->sample_aspect_ratio.num, st->sample_aspect_ratio.den, + display_aspect_ratio.num, display_aspect_ratio.den); + } + + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + int fps = st->avg_frame_rate.den && st->avg_frame_rate.num; + int tbn = st->time_base.den && st->time_base.num; + int tbc = st->codec->time_base.den && st->codec->time_base.num; + + if (fps || tbn || tbc) + Debug(3, "\n" ); + + if (fps) + zm_log_fps(av_q2d(st->avg_frame_rate), tbn || tbc ? "fps, " : "fps"); + if (tbn) + zm_log_fps(1 / av_q2d(st->time_base), tbc ? "stream tb numerator , " : "stream tb numerator"); + if (tbc) + zm_log_fps(1 / av_q2d(st->codec->time_base), "codec time base:"); + } + + if (st->disposition & AV_DISPOSITION_DEFAULT) + Debug(1, " (default)"); + if (st->disposition & AV_DISPOSITION_DUB) + Debug(1, " (dub)"); + if (st->disposition & AV_DISPOSITION_ORIGINAL) + Debug(1, " (original)"); + if (st->disposition & AV_DISPOSITION_COMMENT) + Debug(1, " (comment)"); + if (st->disposition & AV_DISPOSITION_LYRICS) + Debug(1, " (lyrics)"); + if (st->disposition & AV_DISPOSITION_KARAOKE) + Debug(1, " (karaoke)"); + if (st->disposition & AV_DISPOSITION_FORCED) + Debug(1, " (forced)"); + if (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED) + Debug(1, " (hearing impaired)"); + if (st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED) + Debug(1, " (visual impaired)"); + if (st->disposition & AV_DISPOSITION_CLEAN_EFFECTS) + Debug(1, " (clean effects)"); + Debug(1, "\n"); + + //dump_metadata(NULL, st->metadata, " "); + + //dump_sidedata(NULL, st, " "); +} + +int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt) { + const enum AVSampleFormat *p = codec->sample_fmts; + + while (*p != AV_SAMPLE_FMT_NONE) { + if (*p == sample_fmt) + return 1; + else Debug(2, "Not %s", av_get_sample_fmt_name( *p ) ); + p++; + } + return 0; +} + +#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) +#else +unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ) { + dst->data = reinterpret_cast(new uint64_t[(src->size + FF_INPUT_BUFFER_PADDING_SIZE)/sizeof(uint64_t) + 1]); + memcpy(dst->data, src->data, src->size ); + return 0; +} +#endif + diff --git a/src/zm_ffmpeg.h b/src/zm_ffmpeg.h index 894408437..f94c575f5 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef ZM_FFMPEG_H @@ -29,9 +29,11 @@ extern "C" { // AVUTIL #if HAVE_LIBAVUTIL_AVUTIL_H +#include "libavutil/avassert.h" #include #include #include +#include /* LIBAVUTIL_VERSION_CHECK checks for the right version of libav and FFmpeg * The original source is vlc (in modules/codec/avcodec/avcommon_compat.h) @@ -39,8 +41,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBAVUTIL_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #if LIBAVUTIL_VERSION_CHECK(50, 29, 0, 29, 0) #include @@ -57,55 +59,55 @@ extern "C" { #include #include #endif /* HAVE_LIBAVUTIL_AVUTIL_H */ - + #if defined(HAVE_LIBAVUTIL_AVUTIL_H) #if LIBAVUTIL_VERSION_CHECK(51, 42, 0, 74, 100) - #define _AVPIXELFORMAT AVPixelFormat + #define _AVPIXELFORMAT AVPixelFormat #else - #define _AVPIXELFORMAT PixelFormat - #define AV_PIX_FMT_NONE PIX_FMT_NONE - #define AV_PIX_FMT_RGB444 PIX_FMT_RGB444 - #define AV_PIX_FMT_RGB555 PIX_FMT_RGB555 - #define AV_PIX_FMT_RGB565 PIX_FMT_RGB565 - #define AV_PIX_FMT_BGR24 PIX_FMT_BGR24 - #define AV_PIX_FMT_RGB24 PIX_FMT_RGB24 - #define AV_PIX_FMT_BGRA PIX_FMT_BGRA - #define AV_PIX_FMT_ARGB PIX_FMT_ARGB - #define AV_PIX_FMT_ABGR PIX_FMT_ABGR - #define AV_PIX_FMT_RGBA PIX_FMT_RGBA - #define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8 - #define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422 - #define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P - #define AV_PIX_FMT_YUV411P PIX_FMT_YUV411P - #define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P - #define AV_PIX_FMT_YUV410P PIX_FMT_YUV410P - #define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P - #define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P - #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 - #define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P - #define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P - #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 - #define AV_PIX_FMT_UYYVYY411 PIX_FMT_UYYVYY411 - #define AV_PIX_FMT_BGR565 PIX_FMT_BGR565 - #define AV_PIX_FMT_BGR555 PIX_FMT_BGR555 - #define AV_PIX_FMT_BGR8 PIX_FMT_BGR8 - #define AV_PIX_FMT_BGR4 PIX_FMT_BGR4 - #define AV_PIX_FMT_BGR4_BYTE PIX_FMT_BGR4_BYTE - #define AV_PIX_FMT_RGB8 PIX_FMT_RGB8 - #define AV_PIX_FMT_RGB4 PIX_FMT_RGB4 - #define AV_PIX_FMT_RGB4_BYTE PIX_FMT_RGB4_BYTE - #define AV_PIX_FMT_NV12 PIX_FMT_NV12 - #define AV_PIX_FMT_NV21 PIX_FMT_NV21 - #define AV_PIX_FMT_RGB32_1 PIX_FMT_RGB32_1 - #define AV_PIX_FMT_BGR32_1 PIX_FMT_BGR32_1 - #define AV_PIX_FMT_GRAY16BE PIX_FMT_GRAY16BE - #define AV_PIX_FMT_GRAY16LE PIX_FMT_GRAY16LE - #define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P - #define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P - #define AV_PIX_FMT_YUVA420P PIX_FMT_YUVA420P - //#define AV_PIX_FMT_VDPAU_H264 PIX_FMT_VDPAU_H264 - //#define AV_PIX_FMT_VDPAU_MPEG1 PIX_FMT_VDPAU_MPEG1 - //#define AV_PIX_FMT_VDPAU_MPEG2 PIX_FMT_VDPAU_MPEG2 + #define _AVPIXELFORMAT PixelFormat + #define AV_PIX_FMT_NONE PIX_FMT_NONE + #define AV_PIX_FMT_RGB444 PIX_FMT_RGB444 + #define AV_PIX_FMT_RGB555 PIX_FMT_RGB555 + #define AV_PIX_FMT_RGB565 PIX_FMT_RGB565 + #define AV_PIX_FMT_BGR24 PIX_FMT_BGR24 + #define AV_PIX_FMT_RGB24 PIX_FMT_RGB24 + #define AV_PIX_FMT_BGRA PIX_FMT_BGRA + #define AV_PIX_FMT_ARGB PIX_FMT_ARGB + #define AV_PIX_FMT_ABGR PIX_FMT_ABGR + #define AV_PIX_FMT_RGBA PIX_FMT_RGBA + #define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8 + #define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422 + #define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P + #define AV_PIX_FMT_YUV411P PIX_FMT_YUV411P + #define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P + #define AV_PIX_FMT_YUV410P PIX_FMT_YUV410P + #define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P + #define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P + #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 + #define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P + #define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P + #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 + #define AV_PIX_FMT_UYYVYY411 PIX_FMT_UYYVYY411 + #define AV_PIX_FMT_BGR565 PIX_FMT_BGR565 + #define AV_PIX_FMT_BGR555 PIX_FMT_BGR555 + #define AV_PIX_FMT_BGR8 PIX_FMT_BGR8 + #define AV_PIX_FMT_BGR4 PIX_FMT_BGR4 + #define AV_PIX_FMT_BGR4_BYTE PIX_FMT_BGR4_BYTE + #define AV_PIX_FMT_RGB8 PIX_FMT_RGB8 + #define AV_PIX_FMT_RGB4 PIX_FMT_RGB4 + #define AV_PIX_FMT_RGB4_BYTE PIX_FMT_RGB4_BYTE + #define AV_PIX_FMT_NV12 PIX_FMT_NV12 + #define AV_PIX_FMT_NV21 PIX_FMT_NV21 + #define AV_PIX_FMT_RGB32_1 PIX_FMT_RGB32_1 + #define AV_PIX_FMT_BGR32_1 PIX_FMT_BGR32_1 + #define AV_PIX_FMT_GRAY16BE PIX_FMT_GRAY16BE + #define AV_PIX_FMT_GRAY16LE PIX_FMT_GRAY16LE + #define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P + #define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P + #define AV_PIX_FMT_YUVA420P PIX_FMT_YUVA420P + //#define AV_PIX_FMT_VDPAU_H264 PIX_FMT_VDPAU_H264 + //#define AV_PIX_FMT_VDPAU_MPEG1 PIX_FMT_VDPAU_MPEG1 + //#define AV_PIX_FMT_VDPAU_MPEG2 PIX_FMT_VDPAU_MPEG2 #endif #endif /* HAVE_LIBAVUTIL_AVUTIL_H */ @@ -120,8 +122,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBAVCODEC_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #elif HAVE_FFMPEG_AVCODEC_H #include @@ -129,9 +131,9 @@ extern "C" { #if defined(HAVE_LIBAVCODEC_AVCODEC_H) #if LIBAVCODEC_VERSION_CHECK(54, 25, 0, 51, 100) - #define _AVCODECID AVCodecID + #define _AVCODECID AVCodecID #else - #define _AVCODECID CodecID + #define _AVCODECID CodecID #endif #endif /* HAVE_LIBAVCODEC_AVCODEC_H */ @@ -145,8 +147,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBAVFORMAT_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #elif HAVE_FFMPEG_AVFORMAT_H #include @@ -161,8 +163,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBAVDEVICE_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVDEVICE_VERSION_MICRO < 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVDEVICE_VERSION_MICRO >= 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBAVDEVICE_VERSION_MICRO < 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBAVDEVICE_VERSION_MICRO >= 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #elif HAVE_FFMPEG_AVDEVICE_H #include @@ -177,8 +179,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBSWSCALE_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBSWSCALE_VERSION_MICRO < 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBSWSCALE_VERSION_MICRO >= 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBSWSCALE_VERSION_MICRO < 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBSWSCALE_VERSION_MICRO >= 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #elif HAVE_FFMPEG_SWSCALE_H #include @@ -198,6 +200,9 @@ extern "C" { #endif #endif +/* A single function to initialize ffmpeg, to avoid multiple initializations */ +void FFMPEGInit(); + #if HAVE_LIBAVUTIL enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder); #endif // HAVE_LIBAVUTIL @@ -207,23 +212,23 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp #if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL class SWScale { public: - SWScale(); - ~SWScale(); - int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); - int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size); - int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size); - int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); - int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); + SWScale(); + ~SWScale(); + int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); + int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size); + int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size); + int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); + int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); protected: - bool gotdefaults; - struct SwsContext* swscale_ctx; - AVFrame* input_avframe; - AVFrame* output_avframe; - enum _AVPIXELFORMAT default_input_pf; - enum _AVPIXELFORMAT default_output_pf; - unsigned int default_width; - unsigned int default_height; + bool gotdefaults; + struct SwsContext* swscale_ctx; + AVFrame* input_avframe; + AVFrame* output_avframe; + enum _AVPIXELFORMAT default_input_pf; + enum _AVPIXELFORMAT default_output_pf; + unsigned int default_width; + unsigned int default_height; }; #endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL @@ -260,23 +265,87 @@ protected: */ #ifdef __cplusplus - inline static const std::string av_make_error_string(int errnum) - { - char errbuf[AV_ERROR_MAX_STRING_SIZE]; + inline static const std::string av_make_error_string(int errnum) + { + char errbuf[AV_ERROR_MAX_STRING_SIZE]; #if LIBAVUTIL_VERSION_CHECK(50, 13, 0, 13, 0) - av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE); + av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE); #else - snprintf(errbuf, AV_ERROR_MAX_STRING_SIZE, "libav error %d", errnum); + snprintf(errbuf, AV_ERROR_MAX_STRING_SIZE, "libav error %d", errnum); #endif - return (std::string)errbuf; - } + return (std::string)errbuf; + } - #undef av_err2str - #define av_err2str(errnum) av_make_error_string(errnum).c_str() + #undef av_err2str + #define av_err2str(errnum) av_make_error_string(errnum).c_str() - #endif // __cplusplus + /* The following is copied directly from newer ffmpeg */ + #if LIBAVUTIL_VERSION_CHECK(52, 7, 0, 17, 100) + #else + int av_dict_parse_string(AVDictionary **pm, const char *str, + const char *key_val_sep, const char *pairs_sep, + int flags); + #endif + +#endif // __cplusplus #endif // ( HAVE_LIBAVUTIL_AVUTIL_H || HAVE_LIBAVCODEC_AVCODEC_H || HAVE_LIBAVFORMAT_AVFORMAT_H || HAVE_LIBAVDEVICE_AVDEVICE_H ) +#ifndef avformat_alloc_output_context2 +int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename); +#define avformat_alloc_output_context2(x,y,z,a) hacked_up_context2_for_older_ffmpeg(x,y,z,a) +#endif + +#ifndef av_rescale_delta +/** + * Rescale a timestamp while preserving known durations. + */ +int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb); +#endif + +#ifndef av_clip64 +/** + * Clip a signed 64bit integer value into the amin-amax range. + * @param a value to clip + * @param amin minimum value of the clip range + * @param amax maximum value of the clip range + * @return clipped value + */ +static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, int64_t amax) +{ + if (a < amin) return amin; + else if (a > amax) return amax; + else return a; +} + +#define av_clip64 av_clip64_c +#endif + +void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output); +#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) + #define zm_av_packet_unref( packet ) av_packet_unref( packet ) + #define zm_av_packet_ref( dst, src ) av_packet_ref( dst, src ) +#else + #define zm_av_packet_unref( packet ) av_free_packet( packet ) +unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ); +#endif +#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) + #define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) avcodec_decode_video2( context, rawFrame, frameComplete, packet ) +#else + #define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet ) avcodec_decode_video( context, rawFrame, frameComplete, packet->data, packet->size) +#endif + +#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) + #define zm_av_frame_alloc() av_frame_alloc() +#else + #define zm_av_frame_alloc() avcodec_alloc_frame() +#endif + +#if ! LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) + #define av_frame_free( input_avframe ) av_freep( input_avframe ) +#endif + +int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt); + #endif // ZM_FFMPEG_H diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 12456a560..7ebbeae07 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" @@ -23,6 +23,9 @@ #include "zm_ffmpeg_camera.h" +extern "C" { +#include "libavutil/time.h" +} #ifndef AV_ERROR_MAX_STRING_SIZE #define AV_ERROR_MAX_STRING_SIZE 64 #endif @@ -33,29 +36,34 @@ #include #endif -FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - Camera( p_id, FFMPEG_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), +FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + Camera( p_id, FFMPEG_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), mPath( p_path ), mMethod( p_method ), mOptions( p_options ) { - if ( capture ) - { + if ( capture ) { Initialise(); } - + mFormatContext = NULL; mVideoStreamId = -1; - mCodecContext = NULL; - mCodec = NULL; + mAudioStreamId = -1; + mVideoCodecContext = NULL; + mAudioCodecContext = NULL; + mVideoCodec = NULL; + mAudioCodec = NULL; mRawFrame = NULL; mFrame = NULL; frameCount = 0; + startTime=0; mIsOpening = false; mCanCapture = false; mOpenStart = 0; mReopenThread = 0; - + videoStore = NULL; + video_last_pts = 0; + #if HAVE_LIBSWSCALE mConvertContext = NULL; #endif @@ -72,35 +80,37 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri } else { Panic("Unexpected colours: %d",colours); } - + } -FfmpegCamera::~FfmpegCamera() -{ +FfmpegCamera::~FfmpegCamera() { + + if ( videoStore ) { + delete videoStore; + } CloseFfmpeg(); - if ( capture ) - { + if ( capture ) { Terminate(); } } -void FfmpegCamera::Initialise() -{ +void FfmpegCamera::Initialise() { if ( logDebugging() ) av_log_set_level( AV_LOG_DEBUG ); else av_log_set_level( AV_LOG_QUIET ); av_register_all(); + avformat_network_init(); } -void FfmpegCamera::Terminate() -{ +void FfmpegCamera::Terminate() { } -int FfmpegCamera::PrimeCapture() -{ +int FfmpegCamera::PrimeCapture() { + mVideoStreamId = -1; + mAudioStreamId = -1; Info( "Priming capture from %s", mPath.c_str() ); if (OpenFfmpeg() != 0){ @@ -115,109 +125,102 @@ int FfmpegCamera::PreCapture() return( 0 ); } -int FfmpegCamera::Capture( Image &image ) -{ +int FfmpegCamera::Capture( Image &image ) { if (!mCanCapture){ return -1; } - + // If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread. if (mReopenThread != 0) { void *retval = 0; int ret; - + ret = pthread_join(mReopenThread, &retval); if (ret != 0){ Error("Could not join reopen thread."); } - + Info( "Successfully reopened stream." ); mReopenThread = 0; } - AVPacket packet; - uint8_t* directbuffer; - - /* Request a writeable buffer of the target image */ - directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); - if(directbuffer == NULL) { - Error("Failed requesting writeable buffer for the captured image."); - return (-1); - } - int frameComplete = false; - while ( !frameComplete ) - { + while ( !frameComplete ) { int avResult = av_read_frame( mFormatContext, &packet ); - if ( avResult < 0 ) - { + if ( avResult < 0 ) { char errbuf[AV_ERROR_MAX_STRING_SIZE]; av_strerror(avResult, errbuf, AV_ERROR_MAX_STRING_SIZE); if ( - // Check if EOF. - (avResult == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) || - // Check for Connection failure. - (avResult == -110) - ) - { - Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf); + // Check if EOF. + (avResult == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) || + // Check for Connection failure. + (avResult == -110) + ) { + Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf ); ReopenFfmpeg(); } Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, avResult, errbuf ); return( -1 ); } - Debug( 5, "Got packet from stream %d", packet.stream_index ); - if ( packet.stream_index == mVideoStreamId ) - { -#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) - if ( avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ) < 0 ) -#else - if ( avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ) < 0 ) -#endif + Debug( 5, "Got packet from stream %d dts (%d) pts(%d)", packet.stream_index, packet.pts, packet.dts ); + // What about audio stream? Maybe someday we could do sound detection... + if ( packet.stream_index == mVideoStreamId ) { + int ret = zm_avcodec_decode_video( mVideoCodecContext, mRawFrame, &frameComplete, &packet ); + if ( ret < 0 ) Fatal( "Unable to decode frame at frame %d", frameCount ); Debug( 4, "Decoded video packet at frame %d", frameCount ); - if ( frameComplete ) - { - Debug( 3, "Got frame %d", frameCount ); + if ( frameComplete ) { + Debug( 4, "Got frame %d", frameCount ); + + uint8_t* directbuffer; + + /* Request a writeable buffer of the target image */ + directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); + if(directbuffer == NULL) { + Error("Failed requesting writeable buffer for the captured image."); + return (-1); + } + #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(mFrame->data, mFrame->linesize, - directbuffer, imagePixFormat, width, height, 1); + directbuffer, imagePixFormat, width, height, 1); #else avpicture_fill( (AVPicture *)mFrame, directbuffer, - imagePixFormat, width, height); + imagePixFormat, width, height); #endif - + #if HAVE_LIBSWSCALE - if(mConvertContext == NULL) { - mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); + if(mConvertContext == NULL) { + mConvertContext = sws_getContext(mVideoCodecContext->width, + mVideoCodecContext->height, + mVideoCodecContext->pix_fmt, + width, height, imagePixFormat, + SWS_BICUBIC, NULL, NULL, NULL); - if(mConvertContext == NULL) - Fatal( "Unable to create conversion context for %s", mPath.c_str() ); - } - - if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) - Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); + if(mConvertContext == NULL) + Fatal( "Unable to create conversion context for %s", mPath.c_str() ); + } + + if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) + Fatal("Unable to convert raw format %u to target format %u at frame %d", mVideoCodecContext->pix_fmt, imagePixFormat, frameCount); #else // HAVE_LIBSWSCALE - Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" ); + Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" ); #endif // HAVE_LIBSWSCALE - - frameCount++; - } - } -#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) - av_packet_unref( &packet); -#else - av_free_packet( &packet ); -#endif - } - return (0); -} -int FfmpegCamera::PostCapture() -{ + frameCount++; + } // end if frameComplete + } else { + Debug( 4, "Different stream_index %d", packet.stream_index ); + } // end if packet.stream_index == mVideoStreamId + zm_av_packet_unref( &packet ); + } // end while ! frameComplete + return (0); +} // FfmpegCamera::Capture + +int FfmpegCamera::PostCapture() { // Nothing to do here return( 0 ); } @@ -226,6 +229,8 @@ int FfmpegCamera::OpenFfmpeg() { Debug ( 2, "OpenFfmpeg called." ); + int ret; + mOpenStart = time(NULL); mIsOpening = true; @@ -236,37 +241,24 @@ int FfmpegCamera::OpenFfmpeg() { #else // Handle options AVDictionary *opts = 0; - StringVector opVect = split(Options(), ","); - + ret = av_dict_parse_string(&opts, Options().c_str(), "=", ",", 0); + if (ret < 0) { + Warning("Could not parse ffmpeg input options list '%s'\n", Options().c_str()); + } + // Set transport method as specified by method field, rtpUni is default - if ( Method() == "rtpMulti" ) - opVect.push_back("rtsp_transport=udp_multicast"); - else if ( Method() == "rtpRtsp" ) - opVect.push_back("rtsp_transport=tcp"); - else if ( Method() == "rtpRtspHttp" ) - opVect.push_back("rtsp_transport=http"); - - Debug(2, "Number of Options: %d",opVect.size()); - for (size_t i=0; i 1) { - parts[0] = trimSpaces(parts[0]); - parts[1] = trimSpaces(parts[1]); - if ( av_dict_set(&opts, parts[0].c_str(), parts[1].c_str(), 0) == 0 ) { - Debug(2, "set option %d '%s' to '%s'", i, parts[0].c_str(), parts[1].c_str()); - } - else - { - Warning( "Error trying to set option %d '%s' to '%s'", i, parts[0].c_str(), parts[1].c_str() ); - } - - } - else - { - Warning( "Unable to parse ffmpeg option %d '%s', expecting key=value", i, opVect[i].c_str() ); - } - } + if (Method() == "rtpMulti") { + ret = av_dict_set(&opts, "rtsp_transport", "udp_multicast", 0); + } else if (Method() == "rtpRtsp") { + ret = av_dict_set(&opts, "rtsp_transport", "tcp", 0); + } else if (Method() == "rtpRtspHttp") { + ret = av_dict_set(&opts, "rtsp_transport", "http", 0); + } + + if (ret < 0) { + Warning("Could not set rtsp_transport method '%s'\n", Method().c_str()); + } + Debug ( 1, "Calling avformat_open_input" ); mFormatContext = avformat_alloc_context( ); @@ -289,70 +281,111 @@ int FfmpegCamera::OpenFfmpeg() { mIsOpening = false; Debug ( 1, "Opened input" ); + Info( "Stream open %s", mPath.c_str() ); + + //FIXME can speed up initial analysis but need sensible parameters... + //mFormatContext->probesize = 32; + //mFormatContext->max_analyze_duration = 32; // Locate stream info from avformat_open_input #if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0) Debug ( 1, "Calling av_find_stream_info" ); if ( av_find_stream_info( mFormatContext ) < 0 ) #else - Debug ( 1, "Calling avformat_find_stream_info" ); + Debug ( 1, "Calling avformat_find_stream_info" ); if ( avformat_find_stream_info( mFormatContext, 0 ) < 0 ) #endif Fatal( "Unable to find stream info from %s due to: %s", mPath.c_str(), strerror(errno) ); + startTime = av_gettime();//FIXME here or after find_Stream_info Debug ( 1, "Got stream info" ); // Find first video stream present + // The one we want Might not be the first mVideoStreamId = -1; - for (unsigned int i=0; i < mFormatContext->nb_streams; i++ ) - { + mAudioStreamId = -1; + for (unsigned int i=0; i < mFormatContext->nb_streams; i++ ) { #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) + if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) { #else - if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) + if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) { #endif - { - mVideoStreamId = i; - break; + if ( mVideoStreamId == -1 ) { + mVideoStreamId = i; + // if we break, then we won't find the audio stream + continue; + } else { + Debug(2, "Have another video stream." ); + } } - } +#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) + if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO ) { +#else + if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) { +#endif + if ( mAudioStreamId == -1 ) { + mAudioStreamId = i; + } else { + Debug(2, "Have another audio stream." ); + } + } + } // end foreach stream if ( mVideoStreamId == -1 ) Fatal( "Unable to locate video stream in %s", mPath.c_str() ); + if ( mAudioStreamId == -1 ) + Debug( 3, "Unable to locate audio stream in %s", mPath.c_str() ); - Debug ( 1, "Found video stream" ); + Debug ( 3, "Found video stream at index %d", mVideoStreamId ); + Debug ( 3, "Found audio stream at index %d", mAudioStreamId ); - mCodecContext = mFormatContext->streams[mVideoStreamId]->codec; + mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec; + // STolen from ispy + //this fixes issues with rtsp streams!! woot. + //mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG2_CHUNKS | CODEC_FLAG_LOW_DELAY; // Enable faster H264 decode. + mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY; // Try and get the codec from the codec context - if ( (mCodec = avcodec_find_decoder( mCodecContext->codec_id )) == NULL ) - Fatal( "Can't find codec for video stream from %s", mPath.c_str() ); - - Debug ( 1, "Found decoder" ); - + if ((mVideoCodec = avcodec_find_decoder(mVideoCodecContext->codec_id)) == NULL) { + Fatal("Can't find codec for video stream from %s", mPath.c_str()); + } else { + Debug(1, "Video Found decoder"); + zm_dump_stream_format(mFormatContext, mVideoStreamId, 0, 0); // Open the codec #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) Debug ( 1, "Calling avcodec_open" ); - if ( avcodec_open( mCodecContext, mCodec ) < 0 ) + if (avcodec_open(mVideoCodecContext, mVideoCodec) < 0) #else - Debug ( 1, "Calling avcodec_open2" ); - if ( avcodec_open2( mCodecContext, mCodec, 0 ) < 0 ) + Debug ( 1, "Calling avcodec_open2" ); + if (avcodec_open2(mVideoCodecContext, mVideoCodec, 0) < 0) #endif Fatal( "Unable to open codec for video stream from %s", mPath.c_str() ); + } + + if ( mAudioStreamId >= 0 ) { + mAudioCodecContext = mFormatContext->streams[mAudioStreamId]->codec; + if ((mAudioCodec = avcodec_find_decoder(mAudioCodecContext->codec_id)) == NULL) { + Debug(1, "Can't find codec for audio stream from %s", mPath.c_str()); + } else { + Debug(1, "Audio Found decoder"); + zm_dump_stream_format(mFormatContext, mAudioStreamId, 0, 0); + // Open the codec +#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) + Debug ( 1, "Calling avcodec_open" ); + if (avcodec_open(mAudioCodecContext, mAudioCodec) < 0) +#else + Debug ( 1, "Calling avcodec_open2" ); + if (avcodec_open2(mAudioCodecContext, mAudioCodec, 0) < 0) +#endif + Fatal( "Unable to open codec for video stream from %s", mPath.c_str() ); + } + } Debug ( 1, "Opened codec" ); // Allocate space for the native video frame -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - mRawFrame = av_frame_alloc(); -#else - mRawFrame = avcodec_alloc_frame(); -#endif + mRawFrame = zm_av_frame_alloc(); // Allocate space for the converted video frame -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - mFrame = av_frame_alloc(); -#else - mFrame = avcodec_alloc_frame(); -#endif + mFrame = zm_av_frame_alloc(); if(mRawFrame == NULL || mFrame == NULL) Fatal( "Unable to allocate frame for %s", mPath.c_str() ); @@ -370,25 +403,37 @@ int FfmpegCamera::OpenFfmpeg() { } Debug ( 1, "Validated imagesize" ); - + #if HAVE_LIBSWSCALE Debug ( 1, "Calling sws_isSupportedInput" ); - if(!sws_isSupportedInput(mCodecContext->pix_fmt)) { - Fatal("swscale does not support the codec format: %c%c%c%c",(mCodecContext->pix_fmt)&0xff,((mCodecContext->pix_fmt>>8)&0xff),((mCodecContext->pix_fmt>>16)&0xff),((mCodecContext->pix_fmt>>24)&0xff)); + if (!sws_isSupportedInput(mVideoCodecContext->pix_fmt)) { + Fatal("swscale does not support the codec format: %c%c%c%c", (mVideoCodecContext->pix_fmt)&0xff, ((mVideoCodecContext->pix_fmt >> 8)&0xff), ((mVideoCodecContext->pix_fmt >> 16)&0xff), ((mVideoCodecContext->pix_fmt >> 24)&0xff)); } - + if(!sws_isSupportedOutput(imagePixFormat)) { Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff)); } - + + mConvertContext = sws_getContext(mVideoCodecContext->width, + mVideoCodecContext->height, + mVideoCodecContext->pix_fmt, + width, height, + imagePixFormat, SWS_BICUBIC, NULL, + NULL, NULL); + if ( mConvertContext == NULL ) + Fatal( "Unable to create conversion context for %s", mPath.c_str() ); #else // HAVE_LIBSWSCALE Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" ); #endif // HAVE_LIBSWSCALE + if ( (unsigned int)mVideoCodecContext->width != width || (unsigned int)mVideoCodecContext->height != height ) { + Warning( "Monitor dimensions are %dx%d but camera is sending %dx%d", width, height, mVideoCodecContext->width, mVideoCodecContext->height ); + } + mCanCapture = true; return 0; -} +} // int FfmpegCamera::OpenFfmpeg() int FfmpegCamera::ReopenFfmpeg() { @@ -409,29 +454,26 @@ int FfmpegCamera::CloseFfmpeg(){ mCanCapture = false; -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) av_frame_free( &mFrame ); av_frame_free( &mRawFrame ); -#else - av_freep( &mFrame ); - av_freep( &mRawFrame ); -#endif - + #if HAVE_LIBSWSCALE - if ( mConvertContext ) - { + if ( mConvertContext ) { sws_freeContext( mConvertContext ); mConvertContext = NULL; } #endif - if ( mCodecContext ) - { - avcodec_close( mCodecContext ); - mCodecContext = NULL; // Freed by av_close_input_file + if (mVideoCodecContext) { + avcodec_close(mVideoCodecContext); + mVideoCodecContext = NULL; // Freed by av_close_input_file } - if ( mFormatContext ) - { + if (mAudioCodecContext) { + avcodec_close(mAudioCodecContext); + mAudioCodecContext = NULL; // Freed by av_close_input_file + } + + if ( mFormatContext ) { #if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) av_close_input_file( mFormatContext ); #else @@ -443,8 +485,7 @@ int FfmpegCamera::CloseFfmpeg(){ return 0; } -int FfmpegCamera::FfmpegInterruptCallback(void *ctx) -{ +int FfmpegCamera::FfmpegInterruptCallback(void *ctx) { FfmpegCamera* camera = reinterpret_cast(ctx); if (camera->mIsOpening){ int now = time(NULL); @@ -480,4 +521,282 @@ void *FfmpegCamera::ReopenFfmpegThreadCallback(void *ctx){ } } +//Function to handle capture and store +int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event_file ) { + if ( ! mCanCapture ) { + return -1; + } + int ret; + static char errbuf[AV_ERROR_MAX_STRING_SIZE]; + + // If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread. + if ( mReopenThread != 0 ) { + void *retval = 0; + + ret = pthread_join(mReopenThread, &retval); + if (ret != 0){ + Error("Could not join reopen thread."); + } + + Info( "Successfully reopened stream." ); + mReopenThread = 0; + } + + if ( mVideoCodecContext->codec_id != AV_CODEC_ID_H264 ) { + Error( "Input stream is not h264. The stored event file may not be viewable in browser." ); + } + + int frameComplete = false; + while ( ! frameComplete ) { + av_init_packet( &packet ); + + ret = av_read_frame( mFormatContext, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + if ( + // Check if EOF. + (ret == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) || + // Check for Connection failure. + (ret == -110) + ) { + Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf); + ReopenFfmpeg(); + } + + Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret, errbuf ); + return( -1 ); + } + + int key_frame = packet.flags & AV_PKT_FLAG_KEY; + + Debug( 4, "Got packet from stream %d packet pts (%d) dts(%d), key?(%d)", + packet.stream_index, packet.pts, packet.dts, + key_frame + ); + + //Video recording + if ( recording.tv_sec ) { + // The directory we are recording to is no longer tied to the current event. + // Need to re-init the videostore with the correct directory and start recording again + // for efficiency's sake, we should test for keyframe before we test for directory change... + if ( videoStore && key_frame && (strcmp(oldDirectory, event_file) != 0 ) ) { + // don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?... + // if we store our key frame location with the event will that be enough? + Info("Re-starting video storage module"); + + // I don't know if this is important or not... but I figure we might as well write this last packet out to the store before closing it. + // Also don't know how much it matters for audio. + if ( packet.stream_index == mVideoStreamId ) { + //Write the packet to our video store + int ret = videoStore->writeVideoFramePacket( &packet ); + if ( ret < 0 ) { //Less than zero and we skipped a frame + Warning("Error writing last packet to videostore."); + } + } // end if video + + delete videoStore; + videoStore = NULL; + } // end if end of recording + + if ( ( ! videoStore ) && key_frame && ( packet.stream_index == mVideoStreamId ) ) { + //Instantiate the video storage module + + if (record_audio) { + if (mAudioStreamId == -1) { + Debug(3, "Record Audio on but no audio stream found"); + videoStore = new VideoStore((const char *) event_file, "mp4", + mFormatContext->streams[mVideoStreamId], + NULL, + startTime, + this->getMonitor()); + + } else { + Debug(3, "Video module initiated with audio stream"); + videoStore = new VideoStore((const char *) event_file, "mp4", + mFormatContext->streams[mVideoStreamId], + mFormatContext->streams[mAudioStreamId], + startTime, + this->getMonitor()); + } + } else { + Debug(3, "Record_audio is false so exclude audio stream"); + videoStore = new VideoStore((const char *) event_file, "mp4", + mFormatContext->streams[mVideoStreamId], + NULL, + startTime, + this->getMonitor()); + } // end if record_audio + strcpy(oldDirectory, event_file); + + // Need to write out all the frames from the last keyframe? + // No... need to write out all frames from when the event began. Due to PreEventFrames, this could be more than since the last keyframe. + unsigned int packet_count = 0; + ZMPacket *queued_packet; + + // Clear all packets that predate the moment when the recording began + packetqueue.clear_unwanted_packets( &recording, mVideoStreamId ); + + while ( ( queued_packet = packetqueue.popPacket() ) ) { + AVPacket *avp = queued_packet->av_packet(); + + packet_count += 1; + //Write the packet to our video store + Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue.size() ); + if ( avp->stream_index == mVideoStreamId ) { + ret = videoStore->writeVideoFramePacket( avp ); + } else if ( avp->stream_index == mAudioStreamId ) { + ret = videoStore->writeAudioFramePacket( avp ); + } else { + Warning("Unknown stream id in queued packet (%d)", avp->stream_index ); + ret = -1; + } + if ( ret < 0 ) { + //Less than zero and we skipped a frame + } + delete queued_packet; + } // end while packets in the packetqueue + Debug(2, "Wrote %d queued packets", packet_count ); + } // end if ! was recording + + } else { + // Not recording + if ( videoStore ) { + Info("Deleting videoStore instance"); + delete videoStore; + videoStore = NULL; + } + + // Buffer video packets, since we are not recording. + // All audio packets are keyframes, so only if it's a video keyframe + if ( packet.stream_index == mVideoStreamId ) { + if ( key_frame ) { + Debug(3, "Clearing queue"); + packetqueue.clearQueue( monitor->GetPreEventCount(), mVideoStreamId ); + } +#if 0 +// Not sure this is valid. While a camera will PROBABLY always have an increasing pts... it doesn't have to. +// Also, I think there are integer wrap-around issues. + +else if ( packet.pts && video_last_pts > packet.pts ) { + Warning( "Clearing queue due to out of order pts packet.pts(%d) < video_last_pts(%d)"); + packetqueue.clearQueue(); + } +#endif + } + + // The following lines should ensure that the queue always begins with a video keyframe + if ( packet.stream_index == mAudioStreamId ) { +//Debug(2, "Have audio packet, reocrd_audio is (%d) and packetqueue.size is (%d)", record_audio, packetqueue.size() ); + if ( record_audio && packetqueue.size() ) { + // if it's audio, and we are doing audio, and there is already something in the queue + packetqueue.queuePacket( &packet ); + } + } else if ( packet.stream_index == mVideoStreamId ) { + if ( key_frame || packetqueue.size() ) // it's a keyframe or we already have something in the queue + packetqueue.queuePacket( &packet ); + } + } // end if recording or not + + if ( packet.stream_index == mVideoStreamId ) { + if ( videoStore ) { + //Write the packet to our video store + int ret = videoStore->writeVideoFramePacket( &packet ); + if ( ret < 0 ) { //Less than zero and we skipped a frame + zm_av_packet_unref( &packet ); + return 0; + } + } + Debug(4, "about to decode video" ); + +#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0) + ret = avcodec_send_packet( mVideoCodecContext, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + ret = avcodec_receive_frame( mVideoCodecContext, mRawFrame ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + frameComplete = 1; +# else + ret = zm_avcodec_decode_video( mVideoCodecContext, mRawFrame, &frameComplete, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to decode frame at frame %d: %s, continuing", frameCount, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } +#endif + + Debug( 4, "Decoded video packet at frame %d", frameCount ); + + if ( frameComplete ) { + Debug( 4, "Got frame %d", frameCount ); + + uint8_t* directbuffer; + + /* Request a writeable buffer of the target image */ + directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); + if ( directbuffer == NULL ) { + Error("Failed requesting writeable buffer for the captured image."); + zm_av_packet_unref( &packet ); + return (-1); + } +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + av_image_fill_arrays(mFrame->data, mFrame->linesize, directbuffer, imagePixFormat, width, height, 1); +#else + avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height); +#endif + + + if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, + 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) { + Fatal("Unable to convert raw format %u to target format %u at frame %d", + mVideoCodecContext->pix_fmt, imagePixFormat, frameCount); + } + + frameCount++; + } else { + Debug( 3, "Not framecomplete after av_read_frame" ); + } // end if frameComplete + } else if ( packet.stream_index == mAudioStreamId ) { //FIXME best way to copy all other streams + if ( videoStore ) { + if ( record_audio ) { + Debug(3, "Recording audio packet streamindex(%d) packetstreamindex(%d)", mAudioStreamId, packet.stream_index ); + //Write the packet to our video store + //FIXME no relevance of last key frame + int ret = videoStore->writeAudioFramePacket( &packet ); + if ( ret < 0 ) {//Less than zero and we skipped a frame + Warning("Failure to write audio packet."); + zm_av_packet_unref( &packet ); + return 0; + } + } else { + Debug(4, "Not doing recording of audio packet" ); + } + } else { + Debug(4, "Have audio packet, but not recording atm" ); + } + } else { +#if LIBAVUTIL_VERSION_CHECK(56, 23, 0, 23, 0) + Debug( 3, "Some other stream index %d, %s", packet.stream_index, av_get_media_type_string( mFormatContext->streams[packet.stream_index]->codecpar->codec_type) ); +#else + Debug( 3, "Some other stream index %d", packet.stream_index ); +#endif + } + //if ( videoStore ) { + + // the packet contents are ref counted... when queuing, we allocate another packet and reference it with that one, so we should always need to unref here, which should not affect the queued version. + zm_av_packet_unref( &packet ); + //} + } // end while ! frameComplete + return (frameCount); +} // end FfmpegCamera::CaptureAndRecord + #endif // HAVE_LIBAVFORMAT diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index a8ed2a6a6..d472909db 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_FFMPEG_CAMERA_H @@ -25,59 +25,82 @@ #include "zm_buffer.h" //#include "zm_utils.h" #include "zm_ffmpeg.h" +#include "zm_videostore.h" +#include "zm_packetqueue.h" // // Class representing 'ffmpeg' cameras, i.e. those which are // accessed using ffmpeg multimedia framework // -class FfmpegCamera : public Camera -{ -protected: - std::string mPath; - std::string mMethod; - std::string mOptions; +class FfmpegCamera : public Camera { + protected: + std::string mPath; + std::string mMethod; + std::string mOptions; - int frameCount; + int frameCount; #if HAVE_LIBAVFORMAT - AVFormatContext *mFormatContext; - int mVideoStreamId; - AVCodecContext *mCodecContext; - AVCodec *mCodec; - AVFrame *mRawFrame; - AVFrame *mFrame; - _AVPIXELFORMAT imagePixFormat; + AVFormatContext *mFormatContext; + int mVideoStreamId; + int mAudioStreamId; + AVCodecContext *mVideoCodecContext; + AVCodecContext *mAudioCodecContext; + AVCodec *mVideoCodec; + AVCodec *mAudioCodec; + AVFrame *mRawFrame; + AVFrame *mFrame; + _AVPIXELFORMAT imagePixFormat; - int OpenFfmpeg(); - int ReopenFfmpeg(); - int CloseFfmpeg(); - static int FfmpegInterruptCallback(void *ctx); - static void* ReopenFfmpegThreadCallback(void *ctx); - bool mIsOpening; - bool mCanCapture; - int mOpenStart; - pthread_t mReopenThread; + // Need to keep track of these because apparently the stream can start with values for pts/dts and then subsequent packets start at zero. + int64_t audio_last_pts; + int64_t audio_last_dts; + int64_t video_last_pts; + int64_t video_last_dts; + + // Used to store the incoming packet, it will get copied when queued. + // We only ever need one at a time, so instead of constantly allocating + // and freeing this structure, we will just make it a member of the object. + AVPacket packet; + + int OpenFfmpeg(); + int ReopenFfmpeg(); + int CloseFfmpeg(); + static int FfmpegInterruptCallback(void *ctx); + static void* ReopenFfmpegThreadCallback(void *ctx); + bool mIsOpening; + bool mCanCapture; + int mOpenStart; + pthread_t mReopenThread; #endif // HAVE_LIBAVFORMAT + VideoStore *videoStore; + char oldDirectory[4096]; + unsigned int old_event_id; + zm_packetqueue packetqueue; + #if HAVE_LIBSWSCALE - struct SwsContext *mConvertContext; + struct SwsContext *mConvertContext; #endif -public: - FfmpegCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); - ~FfmpegCamera(); + int64_t startTime; - const std::string &Path() const { return( mPath ); } - const std::string &Options() const { return( mOptions ); } - const std::string &Method() const { return( mMethod ); } + public: + FfmpegCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + ~FfmpegCamera(); - void Initialise(); - void Terminate(); + const std::string &Path() const { return( mPath ); } + const std::string &Options() const { return( mOptions ); } + const std::string &Method() const { return( mMethod ); } - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int PostCapture(); + void Initialise(); + void Terminate(); + + int PrimeCapture(); + int PreCapture(); + int Capture( Image &image ); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); + int PostCapture(); }; #endif // ZM_FFMPEG_CAMERA_H diff --git a/src/zm_file_camera.cpp b/src/zm_file_camera.cpp index 4292cea48..b77628963 100644 --- a/src/zm_file_camera.cpp +++ b/src/zm_file_camera.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include @@ -34,7 +34,7 @@ #include "zm.h" #include "zm_file_camera.h" -FileCamera::FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : Camera( p_id, FILE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ) +FileCamera::FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : Camera( p_id, FILE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ) { strncpy( path, p_path, sizeof(path) ); if ( capture ) @@ -87,5 +87,5 @@ int FileCamera::Capture( Image &image ) int FileCamera::PostCapture() { - return( 0 ); + return( 0 ); } diff --git a/src/zm_file_camera.h b/src/zm_file_camera.h index 19e5971b8..6ad911755 100644 --- a/src/zm_file_camera.h +++ b/src/zm_file_camera.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_FILE_CAMERA_H @@ -23,6 +23,7 @@ #include "zm_camera.h" #include "zm_buffer.h" #include "zm_regexp.h" +#include "zm_packetqueue.h" #include @@ -36,7 +37,7 @@ protected: char path[PATH_MAX]; public: - FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~FileCamera(); const char *Path() const { return( path ); } @@ -46,6 +47,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; }; #endif // ZM_FILE_CAMERA_H diff --git a/src/zm_image.cpp b/src/zm_image.cpp index d76384dc3..c6e54d20a 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" #include "zm_font.h" @@ -26,6 +26,18 @@ #include #include +static unsigned char y_table_global[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 41, 43, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 55, 57, 58, 59, 60, 61, 62, 64, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 76, 78, 79, 80, 81, 82, 83, 85, 86, 87, 88, 89, 90, 91, 93, 94, 95, 96, 97, 98, 100, 101, 102, 103, 104, 105, 107, 108, 109, 110, 111, 112, 114, 115, 116, 117, 118, 119, 121, 122, 123, 124, 125, 126, 128, 129, 130, 131, 132, 133, 135, 136, 137, 138, 139, 140, 142, 143, 144, 145, 146, 147, 149, 150, 151, 152, 153, 154, 156, 157, 158, 159, 160, 161, 163, 164, 165, 166, 167, 168, 170, 171, 172, 173, 174, 175, 176, 178, 179, 180, 181, 182, 183, 185, 186, 187, 188, 189, 190, 192, 193, 194, 195, 196, 197, 199, 200, 201, 202, 203, 204, 206, 207, 208, 209, 210, 211, 213, 214, 215, 216, 217, 218, 220, 221, 222, 223, 224, 225, 227, 228, 229, 230, 231, 232, 234, 235, 236, 237, 238, 239, 241, 242, 243, 244, 245, 246, 248, 249, 250, 251, 252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; + +static signed char uv_table_global[] = {-127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -125, -124, -123, -122, -121, -120, -119, -117, -116, -115, -114, -113, -112, -111, -109, -108, -107, -106, -105, -104, -103, -102, -100, -99, -98, -97, -96, -95, -94, -92, -91, -90, -89, -88, -87, -86, -85, -83, -82, -81, -80, -79, -78, -77, -75, -74, -73, -72, -71, -70, -69, -68, -66, -65, -64, -63, -62, -61, -60, -58, -57, -56, -55, -54, -53, -52, -51, -49, -48, -47, -46, -45, -44, -43, -41, -40, -39, -38, -37, -36, -35, -34, -32, -31, -30, -29, -28, -27, -26, -24, -23, -22, -21, -20, -19, -18, -17, -15, -14, -13, -12, -11, -10, -9, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32, 34, 35, 36, 37, 38, 39, 40, 41, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 68, 69, 70, 71, 72, 73, 74, 75, 77, 78, 79, 80, 81, 82, 83, 85, 86, 87, 88, 89, 90, 91, 92, 94, 95, 96, 97, 98, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 111, 112, 113, 114, 115, 116, 117, 119, 120, 121, 122, 123, 124, 125, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127}; + +static short r_v_table_global[] = {-179, -178, -176, -175, -173, -172, -171, -169, -168, -166, -165, -164, -162, -161, -159, -158, -157, -155, -154, -152, -151, -150, -148, -147, -145, -144, -143, -141, -140, -138, -137, -135, -134, -133, -131, -130, -128, -127, -126, -124, -123, -121, -120, -119, -117, -116, -114, -113, -112, -110, -109, -107, -106, -105, -103, -102, -100, -99, -98, -96, -95, -93, -92, -91, -89, -88, -86, -85, -84, -82, -81, -79, -78, -77, -75, -74, -72, -71, -70, -68, -67, -65, -64, -63, -61, -60, -58, -57, -56, -54, -53, -51, -50, -49, -47, -46, -44, -43, -42, -40, -39, -37, -36, -35, -33, -32, -30, -29, -28, -26, -25, -23, -22, -21, -19, -18, -16, -15, -14, -12, -11, -9, -8, -7, -5, -4, -2, -1, 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 14, 15, 16, 18, 19, 21, 22, 23, 25, 26, 28, 29, 30, 32, 33, 35, 36, 37, 39, 40, 42, 43, 44, 46, 47, 49, 50, 51, 53, 54, 56, 57, 58, 60, 61, 63, 64, 65, 67, 68, 70, 71, 72, 74, 75, 77, 78, 79, 81, 82, 84, 85, 86, 88, 89, 91, 92, 93, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 112, 113, 114, 116, 117, 119, 120, 121, 123, 124, 126, 127, 128, 130, 131, 133, 134, 135, 137, 138, 140, 141, 143, 144, 145, 147, 148, 150, 151, 152, 154, 155, 157, 158, 159, 161, 162, 164, 165, 166, 168, 169, 171, 172, 173, 175, 176}; + +static short g_u_table_global[] = {-44, -43, -43, -43, -42, -42, -41, -41, -41, -40, -40, -40, -39, -39, -39, -38, -38, -38, -37, -37, -37, -36, -36, -36, -35, -35, -35, -34, -34, -34, -33, -33, -33, -32, -32, -31, -31, -31, -30, -30, -30, -29, -29, -29, -28, -28, -28, -27, -27, -27, -26, -26, -26, -25, -25, -25, -24, -24, -24, -23, -23, -23, -22, -22, -22, -21, -21, -20, -20, -20, -19, -19, -19, -18, -18, -18, -17, -17, -17, -16, -16, -16, -15, -15, -15, -14, -14, -14, -13, -13, -13, -12, -12, -12, -11, -11, -11, -10, -10, -9, -9, -9, -8, -8, -8, -7, -7, -7, -6, -6, -6, -5, -5, -5, -4, -4, -4, -3, -3, -3, -2, -2, -2, -1, -1, -1, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 22, 22, 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 43, 43}; + +static short g_v_table_global[] = {-91, -90, -89, -89, -88, -87, -87, -86, -85, -84, -84, -83, -82, -82, -81, -80, -79, -79, -78, -77, -77, -76, -75, -74, -74, -73, -72, -72, -71, -70, -69, -69, -68, -67, -67, -66, -65, -64, -64, -63, -62, -62, -61, -60, -59, -59, -58, -57, -57, -56, -55, -54, -54, -53, -52, -52, -51, -50, -49, -49, -48, -47, -47, -46, -45, -44, -44, -43, -42, -42, -41, -40, -39, -39, -38, -37, -37, -36, -35, -34, -34, -33, -32, -32, -31, -30, -29, -29, -28, -27, -27, -26, -25, -24, -24, -23, -22, -22, -21, -20, -19, -19, -18, -17, -17, -16, -15, -14, -14, -13, -12, -12, -11, -10, -9, -9, -8, -7, -7, -6, -5, -4, -4, -3, -2, -2, -1, 0, 0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 9, 10, 11, 12, 12, 13, 14, 14, 15, 16, 17, 17, 18, 19, 19, 20, 21, 22, 22, 23, 24, 24, 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 39, 40, 41, 42, 42, 43, 44, 44, 45, 46, 47, 47, 48, 49, 49, 50, 51, 52, 52, 53, 54, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 62, 62, 63, 64, 64, 65, 66, 67, 67, 68, 69, 69, 70, 71, 72, 72, 73, 74, 74, 75, 76, 77, 77, 78, 79, 79, 80, 81, 82, 82, 83, 84, 84, 85, 86, 87, 87, 88, 89, 89}; + +static short b_u_table_global[] = {-226, -225, -223, -221, -219, -217, -216, -214, -212, -210, -209, -207, -205, -203, -202, -200, -198, -196, -194, -193, -191, -189, -187, -186, -184, -182, -180, -178, -177, -175, -173, -171, -170, -168, -166, -164, -163, -161, -159, -157, -155, -154, -152, -150, -148, -147, -145, -143, -141, -139, -138, -136, -134, -132, -131, -129, -127, -125, -124, -122, -120, -118, -116, -115, -113, -111, -109, -108, -106, -104, -102, -101, -99, -97, -95, -93, -92, -90, -88, -86, -85, -83, -81, -79, -77, -76, -74, -72, -70, -69, -67, -65, -63, -62, -60, -58, -56, -54, -53, -51, -49, -47, -46, -44, -42, -40, -38, -37, -35, -33, -31, -30, -28, -26, -24, -23, -21, -19, -17, -15, -14, -12, -10, -8, -7, -5, -3, -1, 0, 1, 3, 5, 7, 8, 10, 12, 14, 15, 17, 19, 21, 23, 24, 26, 28, 30, 31, 33, 35, 37, 38, 40, 42, 44, 46, 47, 49, 51, 53, 54, 56, 58, 60, 62, 63, 65, 67, 69, 70, 72, 74, 76, 77, 79, 81, 83, 85, 86, 88, 90, 92, 93, 95, 97, 99, 101, 102, 104, 106, 108, 109, 111, 113, 115, 116, 118, 120, 122, 124, 125, 127, 129, 131, 132, 134, 136, 138, 139, 141, 143, 145, 147, 148, 150, 152, 154, 155, 157, 159, 161, 163, 164, 166, 168, 170, 171, 173, 175, 177, 178, 180, 182, 184, 186, 187, 189, 191, 193, 194, 196, 198, 200, 202, 203, 205, 207, 209, 210, 212, 214, 216, 217, 219, 221, 223}; + bool Image::initialised = false; static unsigned char *y_table; static signed char *uv_table; @@ -33,10 +45,11 @@ static short *r_v_table; static short *g_v_table; static short *g_u_table; static short *b_u_table; -__attribute__((aligned(16))) static const uint8_t movemask[16] = {0,4,8,12,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; -jpeg_compress_struct *Image::jpg_ccinfo[101] = { 0 }; -jpeg_decompress_struct *Image::jpg_dcinfo = 0; +jpeg_compress_struct *Image::writejpg_ccinfo[101] = { 0 }; +jpeg_compress_struct *Image::encodejpg_ccinfo[101] = { 0 }; +jpeg_decompress_struct *Image::readjpg_dcinfo = 0; +jpeg_decompress_struct *Image::decodejpg_dcinfo = 0; struct zm_error_mgr Image::jpg_err; /* Pointer to blend function. */ @@ -61,8 +74,7 @@ static deinterlace_4field_fptr_t fptr_deinterlace_4field_gray8; /* Pointer to image buffer memory copy function */ imgbufcpy_fptr_t fptr_imgbufcpy; -Image::Image() -{ +Image::Image() { if ( !initialised ) Initialise(); width = 0; @@ -78,8 +90,7 @@ Image::Image() text[0] = '\0'; } -Image::Image( const char *filename ) -{ +Image::Image( const char *filename ) { if ( !initialised ) Initialise(); width = 0; @@ -138,24 +149,40 @@ Image::Image( const Image &p_image ) strncpy( text, p_image.text, sizeof(text) ); } -Image::~Image() -{ +Image::~Image() { DumpImgBuffer(); - if ( initialised ) - { - delete[] y_table; - delete[] uv_table; - delete[] r_v_table; - delete[] g_v_table; - delete[] g_u_table; - delete[] b_u_table; +} + +/* Should be called as part of program shutdown to free everything */ +void Image::Deinitialise() { + if ( initialised ) { + /* + delete[] y_table; + delete[] uv_table; + delete[] r_v_table; + delete[] g_v_table; + delete[] g_u_table; + delete[] b_u_table; + */ initialised = false; - } - if ( jpg_dcinfo ) - { - jpeg_destroy_decompress( jpg_dcinfo ); - delete jpg_dcinfo; - jpg_dcinfo = 0; + if ( readjpg_dcinfo ) { + jpeg_destroy_decompress( readjpg_dcinfo ); + delete readjpg_dcinfo; + readjpg_dcinfo = 0; + } + if ( decodejpg_dcinfo ) + { + jpeg_destroy_decompress( decodejpg_dcinfo ); + delete decodejpg_dcinfo; + decodejpg_dcinfo = 0; + } + for ( unsigned int quality=0; quality <= 100; quality += 1 ) { + if ( writejpg_ccinfo[quality] ) { + jpeg_destroy_compress( writejpg_ccinfo[quality] ); + delete writejpg_ccinfo[quality]; + writejpg_ccinfo[quality] = NULL; + } + } // end foreach quality } } @@ -165,33 +192,59 @@ void Image::Initialise() if(config.fast_image_blends) { if(config.cpu_extensions && sseversion >= 20) { fptr_blend = &sse2_fastblend; /* SSE2 fast blend */ - Debug(2,"Blend: Using SSE2 fast blend function"); + Debug(4,"Blend: Using SSE2 fast blend function"); + } else if(config.cpu_extensions && neonversion >= 1) { +#if defined(__aarch64__) + fptr_blend = &neon64_armv8_fastblend; /* ARM Neon (AArch64) fast blend */ + Debug(4,"Blend: Using ARM Neon (AArch64) fast blend function"); +#elif defined(__arm__) + fptr_blend = &neon32_armv7_fastblend; /* ARM Neon (AArch32) fast blend */ + Debug(4,"Blend: Using ARM Neon (AArch32) fast blend function"); +#else + Panic("Bug: Non ARM platform but neon present"); +#endif } else { fptr_blend = &std_fastblend; /* standard fast blend */ - Debug(2,"Blend: Using fast blend function"); + Debug(4,"Blend: Using fast blend function"); } } else { fptr_blend = &std_blend; - Debug(2,"Blend: Using standard blend function"); + Debug(4,"Blend: Using standard blend function"); } - __attribute__((aligned(16))) uint8_t blend1[16] = {142,255,159,91,88,227,0,52,37,80,152,97,104,252,90,82}; - __attribute__((aligned(16))) uint8_t blend2[16] = {129,56,136,96,119,149,94,29,96,176,1,144,230,203,111,172}; - __attribute__((aligned(16))) uint8_t blendres[16]; - __attribute__((aligned(16))) uint8_t blendexp[16] = {141,231,157,92,91,217,11,49,45,92,133,103,119,246,92,93}; /* Expected results for 12.5% blend */ - - (*fptr_blend)(blend1,blend2,blendres,16,12.5); - + __attribute__((aligned(64))) uint8_t blend1[128] = { + 86,58,54,63,149,62,209,34,148,46,186,176,9,236,193,254,113,146,228,220,123,164,92,98,9,72,67,156,63,118,96,167, + 48,224,106,176,201,245,223,219,198,50,100,31,68,77,33,76,166,90,254,128,191,82,84,32,3,171,147,248,14,196,141,179, + 79,237,121,11,132,37,194,225,45,171,169,167,56,64,193,85,147,33,97,221,94,97,90,44,191,248,65,8,17,240,167,207, + 224,23,71,74,81,1,46,110,227,94,163,170,55,155,52,147,224,154,237,35,255,26,229,11,223,242,118,155,82,37,189,2 + }; + __attribute__((aligned(64))) uint8_t blend2[128] = { + 92,188,203,118,121,231,252,218,126,88,80,72,123,16,91,131,109,0,57,56,95,204,74,8,137,94,6,69,18,146,229,194, + 146,230,13,146,95,48,185,65,162,47,152,172,184,111,245,143,247,105,49,42,89,37,145,255,221,200,103,80,98,39,14,227, + 227,46,46,59,248,7,83,20,157,79,36,161,237,55,77,175,232,200,38,170,198,239,89,19,82,88,130,120,203,184,141,117, + 228,140,150,107,103,195,74,130,42,11,150,70,176,204,198,188,38,252,174,104,128,106,31,17,141,231,62,104,179,29,143,130 + }; + __attribute__((aligned(64))) uint8_t blendexp[128] = { + 86,73,71,69,145,82,214,56,145,51,173,163,22,209,180,239,112,128,207,200,119,168,89,87,24,74,59,145,57,121,111,170, + 59,224,94,172,188,221,218,200,193,49,106,47,81,81,58,84,175,91,229,117,178,76,91,58,29,174,141,227,24,177,125,184, + 96,214,112,16,145,33,180,200,58,159,153,166,77,62,179,95,157,53,89,214,106,114,89,41,177,228,72,21,39,233,163,196, + 224,37,80,77,83,24,49,112,204,84,161,158,69,160,69,151,201,165,229,43,239,35,205,11,213,240,111,148,93,36,183,17 + }; + __attribute__((aligned(64))) uint8_t blendres[128]; + + /* Run the blend function */ + (*fptr_blend)(blend1,blend2,blendres,128,12.0); + /* Compare results with expected results */ - for(int i=0;i<16;i++) { + for(int i=0;i<128;i++) { if(abs(blendexp[i] - blendres[i]) > 3) { - Panic("Blend function failed self-test: Results differ from the expected results"); + Panic("Blend function failed self-test: Results differ from the expected results. Column %u Expected %u Got %u",i,blendexp[i],blendres[i]); } } - + fptr_delta8_rgb = &std_delta8_rgb; fptr_delta8_bgr = &std_delta8_bgr; - + /* Assign the delta functions */ if(config.cpu_extensions) { if(sseversion >= 35) { @@ -201,24 +254,34 @@ void Image::Initialise() fptr_delta8_argb = &ssse3_delta8_argb; fptr_delta8_abgr = &ssse3_delta8_abgr; fptr_delta8_gray8 = &sse2_delta8_gray8; - Debug(2,"Delta: Using SSSE3 delta functions"); + Debug(4,"Delta: Using SSSE3 delta functions"); } else if(sseversion >= 20) { /* SSE2 available */ fptr_delta8_rgba = &sse2_delta8_rgba; fptr_delta8_bgra = &sse2_delta8_bgra; fptr_delta8_argb = &sse2_delta8_argb; fptr_delta8_abgr = &sse2_delta8_abgr; - /* - ** On some systems, the 4 SSE2 algorithms above might be a little slower than - ** the standard algorithms, especially on early Pentium 4 processors. - ** In that case, comment out the 4 lines above and uncomment the 4 lines below - */ - // fptr_delta8_rgba = &std_delta8_rgba; - // fptr_delta8_bgra = &std_delta8_bgra; - // fptr_delta8_argb = &std_delta8_argb; - // fptr_delta8_abgr = &std_delta8_abgr; fptr_delta8_gray8 = &sse2_delta8_gray8; - Debug(2,"Delta: Using SSE2 delta functions"); + Debug(4,"Delta: Using SSE2 delta functions"); + } else if(neonversion >= 1) { + /* ARM Neon available */ +#if defined(__aarch64__) + fptr_delta8_rgba = &neon64_armv8_delta8_rgba; + fptr_delta8_bgra = &neon64_armv8_delta8_bgra; + fptr_delta8_argb = &neon64_armv8_delta8_argb; + fptr_delta8_abgr = &neon64_armv8_delta8_abgr; + fptr_delta8_gray8 = &neon64_armv8_delta8_gray8; + Debug(4,"Delta: Using ARM Neon (AArch64) delta functions"); +#elif defined(__arm__) + fptr_delta8_rgba = &neon32_armv7_delta8_rgba; + fptr_delta8_bgra = &neon32_armv7_delta8_bgra; + fptr_delta8_argb = &neon32_armv7_delta8_argb; + fptr_delta8_abgr = &neon32_armv7_delta8_abgr; + fptr_delta8_gray8 = &neon32_armv7_delta8_gray8; + Debug(4,"Delta: Using ARM Neon (AArch32) delta functions"); +#else + Panic("Bug: Non ARM platform but neon present"); +#endif } else { /* No suitable SSE version available */ fptr_delta8_rgba = &std_delta8_rgba; @@ -226,7 +289,7 @@ void Image::Initialise() fptr_delta8_argb = &std_delta8_argb; fptr_delta8_abgr = &std_delta8_abgr; fptr_delta8_gray8 = &std_delta8_gray8; - Debug(2,"Delta: Using standard delta functions"); + Debug(4,"Delta: Using standard delta functions"); } } else { /* CPU extensions disabled */ @@ -235,94 +298,146 @@ void Image::Initialise() fptr_delta8_argb = &std_delta8_argb; fptr_delta8_abgr = &std_delta8_abgr; fptr_delta8_gray8 = &std_delta8_gray8; - Debug(2,"Delta: CPU extensions disabled, using standard delta functions"); + Debug(4,"Delta: CPU extensions disabled, using standard delta functions"); } - - /* Use SSSE3 deinterlace functions? */ - if(config.cpu_extensions && sseversion >= 35) { - fptr_deinterlace_4field_rgba = &ssse3_deinterlace_4field_rgba; - fptr_deinterlace_4field_bgra = &ssse3_deinterlace_4field_bgra; - fptr_deinterlace_4field_argb = &ssse3_deinterlace_4field_argb; - fptr_deinterlace_4field_abgr = &ssse3_deinterlace_4field_abgr; - fptr_deinterlace_4field_gray8 = &ssse3_deinterlace_4field_gray8; - Debug(2,"Deinterlace: Using SSSE3 delta functions"); - } else { - fptr_deinterlace_4field_rgba = &std_deinterlace_4field_rgba; - fptr_deinterlace_4field_bgra = &std_deinterlace_4field_bgra; - fptr_deinterlace_4field_argb = &std_deinterlace_4field_argb; - fptr_deinterlace_4field_abgr = &std_deinterlace_4field_abgr; - fptr_deinterlace_4field_gray8 = &std_deinterlace_4field_gray8; - Debug(2,"Deinterlace: Using standard delta functions"); + + __attribute__((aligned(64))) uint8_t delta8_1[128] = { + 221,22,234,254,8,140,15,28,166,13,203,56,92,250,79,225,19,59,241,145,253,33,87,204,97,168,229,180,3,108,205,177, + 41,108,65,149,4,87,16,240,56,50,135,64,153,3,219,214,239,55,169,180,167,45,243,56,191,119,145,250,102,145,73,32, + 207,213,189,167,147,83,217,30,113,51,142,125,219,97,60,5,135,195,95,133,21,197,150,82,134,93,198,97,97,49,117,24, + 242,253,242,5,190,71,182,1,0,69,25,181,139,84,242,79,150,158,29,215,98,100,245,16,86,165,18,98,46,100,139,19 + }; + __attribute__((aligned(64))) uint8_t delta8_2[128] = { + 236,22,153,161,50,141,15,130,89,251,33,5,140,201,225,194,138,76,248,89,25,26,29,93,250,251,48,157,41,126,140,152, + 170,177,134,14,234,99,3,105,217,76,38,233,89,30,93,48,234,40,202,80,184,4,250,71,183,249,76,78,184,148,185,120, + 137,214,238,57,50,93,29,60,99,207,40,15,43,28,177,118,60,231,90,47,198,251,250,241,212,114,249,17,95,161,216,218, + 51,178,137,161,213,108,35,72,65,24,5,176,110,15,0,2,137,58,0,133,197,1,122,169,175,33,223,138,37,114,52,186 + }; + __attribute__((aligned(64))) uint8_t delta8_gray8_exp[128] = { + 15,0,81,93,42,1,0,102,77,238,170,51,48,49,146,31,119,17,7,56,228,7,58,111,153,83,181,23,38,18,65,25, + 129,69,69,135,230,12,13,135,161,26,97,169,64,27,126,166,5,15,33,100,17,41,7,15,8,130,69,172,82,3,112,88, + 70,1,49,110,97,10,188,30,14,156,102,110,176,69,117,113,75,36,5,86,177,54,100,159,78,21,51,80,2,112,99,194, + 191,75,105,156,23,37,147,71,65,45,20,5,29,69,242,77,13,100,29,82,99,99,123,153,89,132,205,40,9,14,87,167 + }; + __attribute__((aligned(64))) uint8_t delta8_rgba_exp[32] = { + 13,11,189,60,41,68,112,28,84,66,68,48,14,30,91,36,24,54,113,101,41,90,39,82,107,47,46,80,69,102,130,21 + }; + __attribute__((aligned(64))) uint8_t delta8_gray8_res[128]; + __attribute__((aligned(64))) uint8_t delta8_rgba_res[32]; + + /* Run the delta8 grayscale function */ + (*fptr_delta8_gray8)(delta8_1,delta8_2,delta8_gray8_res,128); + + /* Compare results with expected results */ + for(int i=0;i<128;i++) { + if(abs(delta8_gray8_exp[i] - delta8_gray8_res[i]) > 7) { + Panic("Delta grayscale function failed self-test: Results differ from the expected results. Column %u Expected %u Got %u",i,delta8_gray8_exp[i],delta8_gray8_res[i]); + } } - + + /* Run the delta8 RGBA function */ + (*fptr_delta8_rgba)(delta8_1,delta8_2,delta8_rgba_res,32); + + /* Compare results with expected results */ + for(int i=0;i<32;i++) { + if(abs(delta8_rgba_exp[i] - delta8_rgba_res[i]) > 7) { + Panic("Delta RGBA function failed self-test: Results differ from the expected results. Column %u Expected %u Got %u",i,delta8_rgba_exp[i],delta8_rgba_res[i]); + } + } + + /* + SSSE3 deinterlacing functions were removed because they were usually equal + or slower than the standard code (compiled with -O2 or better) + The function is too complicated to be vectorized efficiently on SSSE3 + */ + fptr_deinterlace_4field_rgba = &std_deinterlace_4field_rgba; + fptr_deinterlace_4field_bgra = &std_deinterlace_4field_bgra; + fptr_deinterlace_4field_argb = &std_deinterlace_4field_argb; + fptr_deinterlace_4field_abgr = &std_deinterlace_4field_abgr; + fptr_deinterlace_4field_gray8 = &std_deinterlace_4field_gray8; + Debug(4,"Deinterlace: Using standard functions"); + +#if defined(__i386__) && !defined(__x86_64__) /* Use SSE2 aligned memory copy? */ if(config.cpu_extensions && sseversion >= 20) { fptr_imgbufcpy = &sse2_aligned_memcpy; - Debug(2,"Image buffer copy: Using SSE2 aligned memcpy"); + Debug(4,"Image buffer copy: Using SSE2 aligned memcpy"); } else { fptr_imgbufcpy = &memcpy; - Debug(2,"Image buffer copy: Using standard memcpy"); + Debug(4,"Image buffer copy: Using standard memcpy"); } - +#else + fptr_imgbufcpy = &memcpy; + Debug(4,"Image buffer copy: Using standard memcpy"); +#endif + /* Code below relocated from zm_local_camera */ Debug( 3, "Setting up static colour tables" ); - - y_table = new unsigned char[256]; - for ( int i = 0; i <= 255; i++ ) - { - unsigned char c = i; - if ( c <= 16 ) - y_table[c] = 0; - else if ( c >= 235 ) - y_table[c] = 255; - else - y_table[c] = (255*(c-16))/219; - } - uv_table = new signed char[256]; - for ( int i = 0; i <= 255; i++ ) - { - unsigned char c = i; - if ( c <= 16 ) - uv_table[c] = -127; - else if ( c >= 240 ) - uv_table[c] = 127; - else - uv_table[c] = (127*(c-128))/112; - } + y_table = y_table_global; + uv_table = uv_table_global; + r_v_table = r_v_table_global; + g_v_table = g_v_table_global; + g_u_table = g_u_table_global; + b_u_table = b_u_table_global; + /* + y_table = new unsigned char[256]; + for ( int i = 0; i <= 255; i++ ) + { + unsigned char c = i; + if ( c <= 16 ) + y_table[c] = 0; + else if ( c >= 235 ) + y_table[c] = 255; + else + y_table[c] = (255*(c-16))/219; + } + + uv_table = new signed char[256]; + for ( int i = 0; i <= 255; i++ ) + { + unsigned char c = i; + if ( c <= 16 ) + uv_table[c] = -127; + else if ( c >= 240 ) + uv_table[c] = 127; + else + uv_table[c] = (127*(c-128))/112; + } + + r_v_table = new short[255]; + g_v_table = new short[255]; + g_u_table = new short[255]; + b_u_table = new short[255]; + for ( int i = 0; i < 255; i++ ) + { + r_v_table[i] = (1402*(i-128))/1000; + g_u_table[i] = (344*(i-128))/1000; + g_v_table[i] = (714*(i-128))/1000; + b_u_table[i] = (1772*(i-128))/1000; + } + */ - r_v_table = new short[255]; - g_v_table = new short[255]; - g_u_table = new short[255]; - b_u_table = new short[255]; - for ( int i = 0; i < 255; i++ ) - { - r_v_table[i] = (1402*(i-128))/1000; - g_u_table[i] = (344*(i-128))/1000; - g_v_table[i] = (714*(i-128))/1000; - b_u_table[i] = (1772*(i-128))/1000; - } - initialised = true; } /* Requests a writeable buffer to the image. This is safer than buffer() because this way we can guarantee that a buffer of required size exists */ uint8_t* Image::WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder) { unsigned int newsize; - + if(p_colours != ZM_COLOUR_GRAY8 && p_colours != ZM_COLOUR_RGB24 && p_colours != ZM_COLOUR_RGB32) { Error("WriteBuffer called with unexpected colours: %d",p_colours); return NULL; } - + if(!p_height || !p_width) { - Error("WriteBuffer called with invaid width or height: %d %d",p_width,p_height); + Error("WriteBuffer called with invalid width or height: %d %d",p_width,p_height); return NULL; } - + if(p_width != width || p_height != height || p_colours != colours || p_subpixelorder != subpixelorder) { newsize = (p_width * p_height) * p_colours; - + if(buffer == NULL) { AllocImgBuffer(newsize); } else { @@ -337,7 +452,7 @@ uint8_t* Image::WriteBuffer(const unsigned int p_width, const unsigned int p_hei } } } - + width = p_width; height = p_height; colours = p_colours; @@ -345,9 +460,9 @@ uint8_t* Image::WriteBuffer(const unsigned int p_width, const unsigned int p_hei pixels = height*width; size = newsize; } - + return buffer; - + } /* Assign an existing buffer to the image instead of copying from a source buffer. The goal is to reduce the amount of memory copying and increase efficiency and buffer reusing. */ @@ -368,12 +483,12 @@ void Image::AssignDirect( const unsigned int p_width, const unsigned int p_heigh } unsigned int new_buffer_size = ((p_width*p_height)*p_colours); - + if(buffer_size < new_buffer_size) { Error("Attempt to directly assign buffer from an undersized buffer of size: %zu, needed %dx%d*%d colours = %zu",buffer_size, p_width, p_height, p_colours, new_buffer_size ); return; } - + if(holdbuffer && buffer) { if(new_buffer_size > allocation) { Error("Held buffer is undersized for assigned buffer"); @@ -385,55 +500,55 @@ void Image::AssignDirect( const unsigned int p_width, const unsigned int p_heigh subpixelorder = p_subpixelorder; pixels = height*width; size = new_buffer_size; // was pixels*colours, but we already calculated it above as new_buffer_size - + /* Copy into the held buffer */ if(new_buffer != buffer) (*fptr_imgbufcpy)(buffer, new_buffer, size); - + /* Free the new buffer */ DumpBuffer(new_buffer, p_buffertype); } } else { /* Free an existing buffer if any */ DumpImgBuffer(); - + width = p_width; height = p_height; colours = p_colours; subpixelorder = p_subpixelorder; pixels = height*width; size = new_buffer_size; // was pixels*colours, but we already calculated it above as new_buffer_size - + allocation = buffer_size; buffertype = p_buffertype; buffer = new_buffer; } - + } void Image::Assign(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const uint8_t* new_buffer, const size_t buffer_size) { unsigned int new_size = (p_width * p_height) * p_colours; - + if(new_buffer == NULL) { Error("Attempt to assign buffer from a NULL pointer"); return; } - + if(buffer_size < new_size) { Error("Attempt to assign buffer from an undersized buffer of size: %zu",buffer_size); return; } - + if(!p_height || !p_width) { Error("Attempt to assign buffer with invalid width or height: %d %d",p_width,p_height); return; } - + if(p_colours != ZM_COLOUR_GRAY8 && p_colours != ZM_COLOUR_RGB24 && p_colours != ZM_COLOUR_RGB32) { Error("Attempt to assign buffer with unexpected colours per pixel: %d",p_colours); return; } - + if ( !buffer || p_width != width || p_height != height || p_colours != colours || p_subpixelorder != subpixelorder) { if (holdbuffer && buffer) { @@ -447,7 +562,7 @@ void Image::Assign(const unsigned int p_width, const unsigned int p_height, cons AllocImgBuffer(new_size); } } - + width = p_width; height = p_height; pixels = width*height; @@ -455,25 +570,25 @@ void Image::Assign(const unsigned int p_width, const unsigned int p_height, cons subpixelorder = p_subpixelorder; size = new_size; } - + if(new_buffer != buffer) (*fptr_imgbufcpy)(buffer, new_buffer, size); - + } void Image::Assign( const Image &image ) { unsigned int new_size = (image.width * image.height) * image.colours; - + if(image.buffer == NULL) { Error("Attempt to assign image with an empty buffer"); return; } - + if(image.colours != ZM_COLOUR_GRAY8 && image.colours != ZM_COLOUR_RGB24 && image.colours != ZM_COLOUR_RGB32) { Error("Attempt to assign image with unexpected colours per pixel: %d",image.colours); return; } - + if ( !buffer || image.width != width || image.height != height || image.colours != colours || image.subpixelorder != subpixelorder) { if (holdbuffer && buffer) { @@ -487,7 +602,7 @@ void Image::Assign( const Image &image ) { AllocImgBuffer(new_size); } } - + width = image.width; height = image.height; pixels = width*height; @@ -495,7 +610,7 @@ void Image::Assign( const Image &image ) { subpixelorder = image.subpixelorder; size = new_size; } - + if(image.buffer != buffer) (*fptr_imgbufcpy)(buffer, image.buffer, size); } @@ -506,14 +621,14 @@ Image *Image::HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p { Panic( "Attempt to highlight image edges when colours = %d", colours ); } - + /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour,p_subpixelorder); - + /* Create a new image of the target format */ Image *high_image = new Image( width, height, p_colours, p_subpixelorder ); uint8_t* high_buff = high_image->WriteBuffer(width, height, p_colours, p_subpixelorder); - + /* Set image to all black */ high_image->Clear(); @@ -521,7 +636,7 @@ Image *Image::HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p unsigned int lo_y = limits?limits->Lo().Y():0; unsigned int hi_x = limits?limits->Hi().X():width-1; unsigned int hi_y = limits?limits->Hi().Y():height-1; - + if ( p_colours == ZM_COLOUR_GRAY8 ) { for ( unsigned int y = lo_y; y <= hi_y; y++ ) @@ -593,7 +708,7 @@ Image *Image::HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p } } } - + return( high_image ); } @@ -653,11 +768,11 @@ bool Image::WriteRaw( const char *filename ) const bool Image::ReadJpeg( const char *filename, unsigned int p_colours, unsigned int p_subpixelorder) { unsigned int new_width, new_height, new_colours, new_subpixelorder; - struct jpeg_decompress_struct *cinfo = jpg_dcinfo; + struct jpeg_decompress_struct *cinfo = readjpg_dcinfo; if ( !cinfo ) { - cinfo = jpg_dcinfo = new jpeg_decompress_struct; + cinfo = readjpg_dcinfo = new jpeg_decompress_struct; cinfo->err = jpeg_std_error( &jpg_err.pub ); jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.emit_message = zm_jpeg_emit_message; @@ -689,7 +804,7 @@ bool Image::ReadJpeg( const char *filename, unsigned int p_colours, unsigned int fclose( infile ); return( false ); } - + /* Check if the image has at least one huffman table defined. If not, use the standard ones */ /* This is required for the MJPEG capture palette of USB devices */ if(cinfo->dc_huff_tbl_ptrs[0] == NULL) { @@ -703,67 +818,67 @@ bool Image::ReadJpeg( const char *filename, unsigned int p_colours, unsigned int { Debug(9,"Image dimensions differ. Old: %ux%u New: %ux%u",width,height,new_width,new_height); } - + switch(p_colours) { case ZM_COLOUR_GRAY8: - { - cinfo->out_color_space = JCS_GRAYSCALE; - new_colours = ZM_COLOUR_GRAY8; - new_subpixelorder = ZM_SUBPIX_ORDER_NONE; - break; - } + { + cinfo->out_color_space = JCS_GRAYSCALE; + new_colours = ZM_COLOUR_GRAY8; + new_subpixelorder = ZM_SUBPIX_ORDER_NONE; + break; + } case ZM_COLOUR_RGB32: - { + { #ifdef JCS_EXTENSIONS - new_colours = ZM_COLOUR_RGB32; - if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - cinfo->out_color_space = JCS_EXT_BGRX; - new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - cinfo->out_color_space = JCS_EXT_XRGB; - new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - cinfo->out_color_space = JCS_EXT_XBGR; - new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; - } else { - /* Assume RGBA */ - cinfo->out_color_space = JCS_EXT_RGBX; - new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } - break; + new_colours = ZM_COLOUR_RGB32; + if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + cinfo->out_color_space = JCS_EXT_BGRX; + new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + cinfo->out_color_space = JCS_EXT_XRGB; + new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + cinfo->out_color_space = JCS_EXT_XBGR; + new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; + } else { + /* Assume RGBA */ + cinfo->out_color_space = JCS_EXT_RGBX; + new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } + break; #else - Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); + Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); #endif - } + } case ZM_COLOUR_RGB24: default: - { - new_colours = ZM_COLOUR_RGB24; - if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { + { + new_colours = ZM_COLOUR_RGB24; + if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_BGR; - new_subpixelorder = ZM_SUBPIX_ORDER_BGR; + cinfo->out_color_space = JCS_EXT_BGR; + new_subpixelorder = ZM_SUBPIX_ORDER_BGR; #else - Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); - cinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); + cinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; #endif - } else { - /* Assume RGB */ -/* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_RGB; +cinfo->out_color_space = JCS_EXT_RGB; #else - cinfo->out_color_space = JCS_RGB; +cinfo->out_color_space = JCS_RGB; #endif -*/ - cinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; - } - break; - } + */ + cinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + } + break; + } } - + if(WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == NULL) { Error("Failed requesting writeable buffer for reading JPEG image."); jpeg_abort_decompress( cinfo ); @@ -814,11 +929,11 @@ bool Image::WriteJpeg( const char *filename, int quality_override, struct timeva } int quality = quality_override?quality_override:config.jpeg_file_quality; - struct jpeg_compress_struct *cinfo = jpg_ccinfo[quality]; + struct jpeg_compress_struct *cinfo = writejpg_ccinfo[quality]; if ( !cinfo ) { - cinfo = jpg_ccinfo[quality] = new jpeg_compress_struct; + cinfo = writejpg_ccinfo[quality] = new jpeg_compress_struct; cinfo->err = jpeg_std_error( &jpg_err.pub ); jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.emit_message = zm_jpeg_emit_message; @@ -835,97 +950,97 @@ bool Image::WriteJpeg( const char *filename, int quality_override, struct timeva cinfo->image_width = width; /* image width and height, in pixels */ cinfo->image_height = height; - + switch(colours) { case ZM_COLOUR_GRAY8: - { - cinfo->input_components = 1; - cinfo->in_color_space = JCS_GRAYSCALE; - break; - } + { + cinfo->input_components = 1; + cinfo->in_color_space = JCS_GRAYSCALE; + break; + } case ZM_COLOUR_RGB32: - { + { #ifdef JCS_EXTENSIONS - cinfo->input_components = 4; - if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - cinfo->in_color_space = JCS_EXT_BGRX; - } else if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - cinfo->in_color_space = JCS_EXT_XRGB; - } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - cinfo->in_color_space = JCS_EXT_XBGR; - } else { - /* Assume RGBA */ - cinfo->in_color_space = JCS_EXT_RGBX; - } + cinfo->input_components = 4; + if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + cinfo->in_color_space = JCS_EXT_BGRX; + } else if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + cinfo->in_color_space = JCS_EXT_XRGB; + } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + cinfo->in_color_space = JCS_EXT_XBGR; + } else { + /* Assume RGBA */ + cinfo->in_color_space = JCS_EXT_RGBX; + } #else - Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); - jpeg_abort_compress( cinfo ); - fclose(outfile); - return(false); + Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); + jpeg_abort_compress( cinfo ); + fclose(outfile); + return(false); #endif - break; - } + break; + } case ZM_COLOUR_RGB24: default: - { - cinfo->input_components = 3; - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + { + cinfo->input_components = 3; + if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { #ifdef JCS_EXTENSIONS - cinfo->in_color_space = JCS_EXT_BGR; + cinfo->in_color_space = JCS_EXT_BGR; #else - Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source"); - jpeg_abort_compress( cinfo ); - fclose(outfile); - return(false); + Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source"); + jpeg_abort_compress( cinfo ); + fclose(outfile); + return(false); #endif - } else { - /* Assume RGB */ -/* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_RGB; +cinfo->out_color_space = JCS_EXT_RGB; #else - cinfo->out_color_space = JCS_RGB; +cinfo->out_color_space = JCS_RGB; #endif -*/ - cinfo->in_color_space = JCS_RGB; - } - break; - } + */ + cinfo->in_color_space = JCS_RGB; + } + break; + } } - + jpeg_set_defaults( cinfo ); jpeg_set_quality( cinfo, quality, FALSE ); cinfo->dct_method = JDCT_FASTEST; jpeg_start_compress( cinfo, TRUE ); - if ( config.add_jpeg_comments && text[0] ) - { + if ( config.add_jpeg_comments && text[0] ) { jpeg_write_marker( cinfo, JPEG_COM, (const JOCTET *)text, strlen(text) ); } - // If we have a non-zero time (meaning a parameter was passed in), then form a simple exif segment with that time as DateTimeOriginal and SubsecTimeOriginal - // No timestamp just leave off the exif section. - if(timestamp.tv_sec) - { - #define EXIFTIMES_MS_OFFSET 0x36 // three decimal digits for milliseconds - #define EXIFTIMES_MS_LEN 0x03 - #define EXIFTIMES_OFFSET 0x3E // 19 characters format '2015:07:21 13:14:45' not including quotes - #define EXIFTIMES_LEN 0x13 // = 19 - #define EXIF_CODE 0xE1 + // If we have a non-zero time (meaning a parameter was passed in), then form a simple exif segment with that time as DateTimeOriginal and SubsecTimeOriginal + // No timestamp just leave off the exif section. + if(timestamp.tv_sec) + { +#define EXIFTIMES_MS_OFFSET 0x36 // three decimal digits for milliseconds +#define EXIFTIMES_MS_LEN 0x03 +#define EXIFTIMES_OFFSET 0x3E // 19 characters format '2015:07:21 13:14:45' not including quotes +#define EXIFTIMES_LEN 0x13 // = 19 +#define EXIF_CODE 0xE1 - char timebuf[64], msbuf[64]; - strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime(&(timestamp.tv_sec))); - snprintf(msbuf, sizeof msbuf, "%06d",(int)(timestamp.tv_usec)); // we only use milliseconds because that's all defined in exif, but this is the whole microseconds because we have it - unsigned char exiftimes[82] = { - 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x69, 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x03, 0x90, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x91, 0x92, - 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00 }; - memcpy(&exiftimes[EXIFTIMES_OFFSET], timebuf,EXIFTIMES_LEN); - memcpy(&exiftimes[EXIFTIMES_MS_OFFSET], msbuf ,EXIFTIMES_MS_LEN); - jpeg_write_marker (cinfo, EXIF_CODE, (const JOCTET *)exiftimes, sizeof(exiftimes) ); - } + // This is a lot of stuff to allocate on the stack. Recommend char *timebuf[64]; + char timebuf[64], msbuf[64]; + strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime(&(timestamp.tv_sec))); + snprintf(msbuf, sizeof msbuf, "%06d",(int)(timestamp.tv_usec)); // we only use milliseconds because that's all defined in exif, but this is the whole microseconds because we have it + unsigned char exiftimes[82] = { + 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x69, 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x03, 0x90, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x91, 0x92, + 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00 }; + memcpy(&exiftimes[EXIFTIMES_OFFSET], timebuf,EXIFTIMES_LEN); + memcpy(&exiftimes[EXIFTIMES_MS_OFFSET], msbuf, EXIFTIMES_MS_LEN); + jpeg_write_marker( cinfo, EXIF_CODE, (const JOCTET *)exiftimes, sizeof(exiftimes) ); + } JSAMPROW row_pointer; /* pointer to a single row */ int row_stride = cinfo->image_width * colours; /* physical row width in buffer */ @@ -945,11 +1060,11 @@ bool Image::WriteJpeg( const char *filename, int quality_override, struct timeva bool Image::DecodeJpeg( const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder) { unsigned int new_width, new_height, new_colours, new_subpixelorder; - struct jpeg_decompress_struct *cinfo = jpg_dcinfo; + struct jpeg_decompress_struct *cinfo = decodejpg_dcinfo; if ( !cinfo ) { - cinfo = jpg_dcinfo = new jpeg_decompress_struct; + cinfo = decodejpg_dcinfo = new jpeg_decompress_struct; cinfo->err = jpeg_std_error( &jpg_err.pub ); jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.emit_message = zm_jpeg_emit_message; @@ -972,7 +1087,7 @@ bool Image::DecodeJpeg( const JOCTET *inbuffer, int inbuffer_size, unsigned int jpeg_abort_decompress( cinfo ); return( false ); } - + /* Check if the image has at least one huffman table defined. If not, use the standard ones */ /* This is required for the MJPEG capture palette of USB devices */ if(cinfo->dc_huff_tbl_ptrs[0] == NULL) { @@ -986,67 +1101,67 @@ bool Image::DecodeJpeg( const JOCTET *inbuffer, int inbuffer_size, unsigned int { Debug(9,"Image dimensions differ. Old: %ux%u New: %ux%u",width,height,new_width,new_height); } - + switch(p_colours) { case ZM_COLOUR_GRAY8: - { - cinfo->out_color_space = JCS_GRAYSCALE; - new_colours = ZM_COLOUR_GRAY8; - new_subpixelorder = ZM_SUBPIX_ORDER_NONE; - break; - } + { + cinfo->out_color_space = JCS_GRAYSCALE; + new_colours = ZM_COLOUR_GRAY8; + new_subpixelorder = ZM_SUBPIX_ORDER_NONE; + break; + } case ZM_COLOUR_RGB32: - { + { #ifdef JCS_EXTENSIONS - new_colours = ZM_COLOUR_RGB32; - if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - cinfo->out_color_space = JCS_EXT_BGRX; - new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - cinfo->out_color_space = JCS_EXT_XRGB; - new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - cinfo->out_color_space = JCS_EXT_XBGR; - new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; - } else { - /* Assume RGBA */ - cinfo->out_color_space = JCS_EXT_RGBX; - new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; - } - break; + new_colours = ZM_COLOUR_RGB32; + if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + cinfo->out_color_space = JCS_EXT_BGRX; + new_subpixelorder = ZM_SUBPIX_ORDER_BGRA; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + cinfo->out_color_space = JCS_EXT_XRGB; + new_subpixelorder = ZM_SUBPIX_ORDER_ARGB; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + cinfo->out_color_space = JCS_EXT_XBGR; + new_subpixelorder = ZM_SUBPIX_ORDER_ABGR; + } else { + /* Assume RGBA */ + cinfo->out_color_space = JCS_EXT_RGBX; + new_subpixelorder = ZM_SUBPIX_ORDER_RGBA; + } + break; #else - Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); + Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead."); #endif - } + } case ZM_COLOUR_RGB24: default: - { - new_colours = ZM_COLOUR_RGB24; - if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { + { + new_colours = ZM_COLOUR_RGB24; + if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_BGR; - new_subpixelorder = ZM_SUBPIX_ORDER_BGR; + cinfo->out_color_space = JCS_EXT_BGR; + new_subpixelorder = ZM_SUBPIX_ORDER_BGR; #else - Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); - cinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead."); + cinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; #endif - } else { - /* Assume RGB */ -/* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_RGB; +cinfo->out_color_space = JCS_EXT_RGB; #else - cinfo->out_color_space = JCS_RGB; +cinfo->out_color_space = JCS_RGB; #endif -*/ - cinfo->out_color_space = JCS_RGB; - new_subpixelorder = ZM_SUBPIX_ORDER_RGB; - } - break; - } + */ + cinfo->out_color_space = JCS_RGB; + new_subpixelorder = ZM_SUBPIX_ORDER_RGB; + } + break; + } } - + if(WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == NULL) { Error("Failed requesting writeable buffer for reading JPEG image."); jpeg_abort_decompress( cinfo ); @@ -1079,11 +1194,11 @@ bool Image::EncodeJpeg( JOCTET *outbuffer, int *outbuffer_size, int quality_over int quality = quality_override?quality_override:config.jpeg_stream_quality; - struct jpeg_compress_struct *cinfo = jpg_ccinfo[quality]; + struct jpeg_compress_struct *cinfo = encodejpg_ccinfo[quality]; if ( !cinfo ) { - cinfo = jpg_ccinfo[quality] = new jpeg_compress_struct; + cinfo = encodejpg_ccinfo[quality] = new jpeg_compress_struct; cinfo->err = jpeg_std_error( &jpg_err.pub ); jpg_err.pub.error_exit = zm_jpeg_error_exit; jpg_err.pub.emit_message = zm_jpeg_emit_message; @@ -1097,59 +1212,59 @@ bool Image::EncodeJpeg( JOCTET *outbuffer, int *outbuffer_size, int quality_over switch(colours) { case ZM_COLOUR_GRAY8: - { - cinfo->input_components = 1; - cinfo->in_color_space = JCS_GRAYSCALE; - break; - } + { + cinfo->input_components = 1; + cinfo->in_color_space = JCS_GRAYSCALE; + break; + } case ZM_COLOUR_RGB32: - { + { #ifdef JCS_EXTENSIONS - cinfo->input_components = 4; - if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - cinfo->in_color_space = JCS_EXT_BGRX; - } else if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - cinfo->in_color_space = JCS_EXT_XRGB; - } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - cinfo->in_color_space = JCS_EXT_XBGR; - } else { - /* Assume RGBA */ - cinfo->in_color_space = JCS_EXT_RGBX; - } + cinfo->input_components = 4; + if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + cinfo->in_color_space = JCS_EXT_BGRX; + } else if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + cinfo->in_color_space = JCS_EXT_XRGB; + } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + cinfo->in_color_space = JCS_EXT_XBGR; + } else { + /* Assume RGBA */ + cinfo->in_color_space = JCS_EXT_RGBX; + } #else - Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); - jpeg_abort_compress( cinfo ); - return(false); + Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source"); + jpeg_abort_compress( cinfo ); + return(false); #endif - break; - } + break; + } case ZM_COLOUR_RGB24: default: - { - cinfo->input_components = 3; - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + { + cinfo->input_components = 3; + if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { #ifdef JCS_EXTENSIONS - cinfo->in_color_space = JCS_EXT_BGR; + cinfo->in_color_space = JCS_EXT_BGR; #else - Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source"); - jpeg_abort_compress( cinfo ); - return(false); + Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source"); + jpeg_abort_compress( cinfo ); + return(false); #endif - } else { - /* Assume RGB */ -/* + } else { + /* Assume RGB */ + /* #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_RGB; +cinfo->out_color_space = JCS_EXT_RGB; #else - cinfo->out_color_space = JCS_RGB; +cinfo->out_color_space = JCS_RGB; #endif -*/ - cinfo->in_color_space = JCS_RGB; - } - break; - } + */ + cinfo->in_color_space = JCS_RGB; + } + break; + } } - + jpeg_set_defaults( cinfo ); jpeg_set_quality( cinfo, quality, FALSE ); cinfo->dct_method = JDCT_FASTEST; @@ -1222,7 +1337,7 @@ bool Image::Crop( unsigned int lo_x, unsigned int lo_y, unsigned int hi_x, unsig unsigned int new_size = new_width*new_height*colours; uint8_t *new_buffer = AllocBuffer(new_size); - + unsigned int new_stride = new_width*colours; for ( unsigned int y = lo_y, ny = 0; y <= hi_y; y++, ny++ ) { @@ -1249,17 +1364,17 @@ void Image::Overlay( const Image &image ) { Panic( "Attempt to overlay different sized images, expected %dx%d, got %dx%d", width, height, image.width, image.height ); } - + if( colours == image.colours && subpixelorder != image.subpixelorder ) { Warning("Attempt to overlay images of same format but with different subpixel order."); } - + /* Grayscale ontop of grayscale - complete */ if ( colours == ZM_COLOUR_GRAY8 && image.colours == ZM_COLOUR_GRAY8 ) { const uint8_t* const max_ptr = buffer+size; const uint8_t* psrc = image.buffer; uint8_t* pdest = buffer; - + while( pdest < max_ptr ) { if ( *psrc ) @@ -1269,15 +1384,15 @@ void Image::Overlay( const Image &image ) pdest++; psrc++; } - - /* RGB24 ontop of grayscale - convert to same format first - complete */ + + /* RGB24 ontop of grayscale - convert to same format first - complete */ } else if ( colours == ZM_COLOUR_GRAY8 && image.colours == ZM_COLOUR_RGB24 ) { Colourise(image.colours, image.subpixelorder); - + const uint8_t* const max_ptr = buffer+size; const uint8_t* psrc = image.buffer; uint8_t* pdest = buffer; - + while( pdest < max_ptr ) { if ( RED_PTR_RGBA(psrc) || GREEN_PTR_RGBA(psrc) || BLUE_PTR_RGBA(psrc) ) @@ -1289,15 +1404,15 @@ void Image::Overlay( const Image &image ) pdest += 3; psrc += 3; } - - /* RGB32 ontop of grayscale - convert to same format first - complete */ + + /* RGB32 ontop of grayscale - convert to same format first - complete */ } else if( colours == ZM_COLOUR_GRAY8 && image.colours == ZM_COLOUR_RGB32 ) { Colourise(image.colours, image.subpixelorder); - + const Rgb* const max_ptr = (Rgb*)(buffer+size); const Rgb* prsrc = (Rgb*)image.buffer; Rgb* prdest = (Rgb*)buffer; - + if(subpixelorder == ZM_SUBPIX_ORDER_RGBA || subpixelorder == ZM_SUBPIX_ORDER_BGRA) { /* RGB\BGR\RGBA\BGRA subpixel order - Alpha byte is last */ while (prdest < max_ptr) { @@ -1319,13 +1434,13 @@ void Image::Overlay( const Image &image ) prsrc++; } } - - /* Grayscale ontop of RGB24 - complete */ + + /* Grayscale ontop of RGB24 - complete */ } else if ( colours == ZM_COLOUR_RGB24 && image.colours == ZM_COLOUR_GRAY8 ) { const uint8_t* const max_ptr = buffer+size; const uint8_t* psrc = image.buffer; uint8_t* pdest = buffer; - + while( pdest < max_ptr ) { if ( *psrc ) @@ -1335,13 +1450,13 @@ void Image::Overlay( const Image &image ) pdest += 3; psrc++; } - - /* RGB24 ontop of RGB24 - not complete. need to take care of different subpixel orders */ + + /* RGB24 ontop of RGB24 - not complete. need to take care of different subpixel orders */ } else if ( colours == ZM_COLOUR_RGB24 && image.colours == ZM_COLOUR_RGB24 ) { const uint8_t* const max_ptr = buffer+size; const uint8_t* psrc = image.buffer; uint8_t* pdest = buffer; - + while( pdest < max_ptr ) { if ( RED_PTR_RGBA(psrc) || GREEN_PTR_RGBA(psrc) || BLUE_PTR_RGBA(psrc) ) @@ -1353,17 +1468,17 @@ void Image::Overlay( const Image &image ) pdest += 3; psrc += 3; } - - /* RGB32 ontop of RGB24 - TO BE DONE */ + + /* RGB32 ontop of RGB24 - TO BE DONE */ } else if ( colours == ZM_COLOUR_RGB24 && image.colours == ZM_COLOUR_RGB32 ) { Error("Overlay of RGB32 ontop of RGB24 is not supported."); - - /* Grayscale ontop of RGB32 - complete */ + + /* Grayscale ontop of RGB32 - complete */ } else if ( colours == ZM_COLOUR_RGB32 && image.colours == ZM_COLOUR_GRAY8 ) { const Rgb* const max_ptr = (Rgb*)(buffer+size); Rgb* prdest = (Rgb*)buffer; const uint8_t* psrc = image.buffer; - + if(subpixelorder == ZM_SUBPIX_ORDER_RGBA || subpixelorder == ZM_SUBPIX_ORDER_BGRA) { /* RGBA\BGRA subpixel order - Alpha byte is last */ while (prdest < max_ptr) { @@ -1385,17 +1500,17 @@ void Image::Overlay( const Image &image ) psrc++; } } - - /* RGB24 ontop of RGB32 - TO BE DONE */ + + /* RGB24 ontop of RGB32 - TO BE DONE */ } else if ( colours == ZM_COLOUR_RGB32 && image.colours == ZM_COLOUR_RGB24 ) { Error("Overlay of RGB24 ontop of RGB32 is not supported."); - - /* RGB32 ontop of RGB32 - not complete. need to take care of different subpixel orders */ + + /* RGB32 ontop of RGB32 - not complete. need to take care of different subpixel orders */ } else if ( colours == ZM_COLOUR_RGB32 && image.colours == ZM_COLOUR_RGB32 ) { const Rgb* const max_ptr = (Rgb*)(buffer+size); Rgb* prdest = (Rgb*)buffer; const Rgb* prsrc = (Rgb*)image.buffer; - + if(image.subpixelorder == ZM_SUBPIX_ORDER_RGBA || image.subpixelorder == ZM_SUBPIX_ORDER_BGRA) { /* RGB\BGR\RGBA\BGRA subpixel order - Alpha byte is last */ while (prdest < max_ptr) { @@ -1418,7 +1533,7 @@ void Image::Overlay( const Image &image ) } } } - + } /* RGB32 compatible: complete */ @@ -1483,7 +1598,7 @@ void Image::Overlay( const Image &image, unsigned int x, unsigned int y ) } else { Error("Overlay called with unexpected colours: %d", colours); } - + } void Image::Blend( const Image &image, int transparency ) @@ -1494,33 +1609,33 @@ void Image::Blend( const Image &image, int transparency ) unsigned long milpixels; #endif uint8_t* new_buffer; - + if ( !(width == image.width && height == image.height && colours == image.colours && subpixelorder == image.subpixelorder) ) { Panic( "Attempt to blend different sized images, expected %dx%dx%d %d, got %dx%dx%d %d", width, height, colours, subpixelorder, image.width, image.height, image.colours, image.subpixelorder ); } - + if(transparency <= 0) return; - + new_buffer = AllocBuffer(size); - + #ifdef ZM_IMAGE_PROFILING clock_gettime(CLOCK_THREAD_CPUTIME_ID,&start); #endif - + /* Do the blending */ (*fptr_blend)(buffer, image.buffer, new_buffer, size, transparency); - + #ifdef ZM_IMAGE_PROFILING clock_gettime(CLOCK_THREAD_CPUTIME_ID,&end); timespec_diff(&start,&end,&diff); - + executetime = (1000000000ull * diff.tv_sec) + diff.tv_nsec; milpixels = (unsigned long)((long double)size)/((((long double)executetime)/1000)); Debug(5, "Blend: %u colours blended in %llu nanoseconds, %lu million colours/s\n",size,executetime,milpixels); #endif - + AssignDirect( width, height, colours, subpixelorder, new_buffer, size, ZM_BUFTYPE_ZM); } @@ -1612,6 +1727,8 @@ Image *Image::Highlight( unsigned int n_images, Image *images[], const Rgb thres unsigned int size = result->size; for ( unsigned int c = 0; c < colours; c++ ) { + unsigned int ref_colour_rgb = RGB_VAL(ref_colour,c); + for ( unsigned int i = 0; i < size; i++ ) { unsigned int count = 0; @@ -1620,11 +1737,9 @@ Image *Image::Highlight( unsigned int n_images, Image *images[], const Rgb thres { uint8_t *psrc = images[j]->buffer+c; -#ifndef SOLARIS - if ( (unsigned)abs((*psrc)-RGB_VAL(ref_colour,c)) >= RGB_VAL(threshold,c) ) -#else - if ( (unsigned)std::abs((*psrc)-RGB_VAL(ref_colour,c)) >= RGB_VAL(threshold,c) ) -#endif + unsigned int diff = ((*psrc)-ref_colour_rgb) > 0 ? (*psrc)-ref_colour_rgb : ref_colour_rgb - (*psrc); + + if (diff >= RGB_VAL(threshold,c)) { count++; } @@ -1645,63 +1760,63 @@ void Image::Delta( const Image &image, Image* targetimage) const unsigned long long executetime; unsigned long milpixels; #endif - + if ( !(width == image.width && height == image.height && colours == image.colours && subpixelorder == image.subpixelorder) ) { Panic( "Attempt to get delta of different sized images, expected %dx%dx%d %d, got %dx%dx%d %d", width, height, colours, subpixelorder, image.width, image.height, image.colours, image.subpixelorder); } - + uint8_t *pdiff = targetimage->WriteBuffer(width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE); - + if(pdiff == NULL) { Panic("Failed requesting writeable buffer for storing the delta image"); } - + #ifdef ZM_IMAGE_PROFILING clock_gettime(CLOCK_THREAD_CPUTIME_ID,&start); #endif - + switch(colours) { case ZM_COLOUR_RGB24: - { - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { - /* BGR subpixel order */ - (*fptr_delta8_bgr)(buffer, image.buffer, pdiff, pixels); - } else { - /* Assume RGB subpixel order */ - (*fptr_delta8_rgb)(buffer, image.buffer, pdiff, pixels); - } - break; - } + { + if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + /* BGR subpixel order */ + (*fptr_delta8_bgr)(buffer, image.buffer, pdiff, pixels); + } else { + /* Assume RGB subpixel order */ + (*fptr_delta8_rgb)(buffer, image.buffer, pdiff, pixels); + } + break; + } case ZM_COLOUR_RGB32: - { - if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - /* ARGB subpixel order */ - (*fptr_delta8_argb)(buffer, image.buffer, pdiff, pixels); - } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - /* ABGR subpixel order */ - (*fptr_delta8_abgr)(buffer, image.buffer, pdiff, pixels); - } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - /* BGRA subpixel order */ - (*fptr_delta8_bgra)(buffer, image.buffer, pdiff, pixels); - } else { - /* Assume RGBA subpixel order */ - (*fptr_delta8_rgba)(buffer, image.buffer, pdiff, pixels); - } - break; - } + { + if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + /* ARGB subpixel order */ + (*fptr_delta8_argb)(buffer, image.buffer, pdiff, pixels); + } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + /* ABGR subpixel order */ + (*fptr_delta8_abgr)(buffer, image.buffer, pdiff, pixels); + } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + /* BGRA subpixel order */ + (*fptr_delta8_bgra)(buffer, image.buffer, pdiff, pixels); + } else { + /* Assume RGBA subpixel order */ + (*fptr_delta8_rgba)(buffer, image.buffer, pdiff, pixels); + } + break; + } case ZM_COLOUR_GRAY8: - (*fptr_delta8_gray8)(buffer, image.buffer, pdiff, pixels); - break; + (*fptr_delta8_gray8)(buffer, image.buffer, pdiff, pixels); + break; default: - Panic("Delta called with unexpected colours: %d",colours); - break; + Panic("Delta called with unexpected colours: %d",colours); + break; } - + #ifdef ZM_IMAGE_PROFILING clock_gettime(CLOCK_THREAD_CPUTIME_ID,&end); timespec_diff(&start,&end,&diff); - + executetime = (1000000000ull * diff.tv_sec) + diff.tv_nsec; milpixels = (unsigned long)((long double)pixels)/((((long double)executetime)/1000)); Debug(5, "Delta: %u delta pixels generated in %llu nanoseconds, %lu million pixels/s\n",pixels,executetime,milpixels); @@ -1730,7 +1845,7 @@ const Coord Image::centreCoord( const char *text ) const line = text+index; line_no++; } - int x = (width - (max_line_len * CHAR_WIDTH) ) / 2; + int x = (width - (max_line_len * ZM_CHAR_WIDTH) ) / 2; int y = (height - (line_no * LINE_HEIGHT) ) / 2; return( Coord( x, y ) ); } @@ -1772,7 +1887,7 @@ void Image::MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour } } else if ( colours == ZM_COLOUR_RGB32 ) - { + { for ( unsigned int x = 0; x < width; x++, ptr += colours ) { Rgb *temp_ptr = (Rgb*)ptr; @@ -1780,10 +1895,10 @@ void Image::MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour *temp_ptr = pixel_rgb_col; i++; } - } else { - Panic("MaskPrivacy called with unexpected colours: %d", colours); - return; - } + } else { + Panic("MaskPrivacy called with unexpected colours: %d", colours); + return; + } } } @@ -1805,7 +1920,7 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int const uint8_t fg_bw_col = fg_colour & 0xff; const Rgb fg_rgb_col = rgb_convert(fg_colour,subpixelorder); const bool fg_trans = (fg_colour == RGB_TRANSPARENT); - + const uint8_t bg_r_col = RED_VAL_RGBA(bg_colour); const uint8_t bg_g_col = GREEN_VAL_RGBA(bg_colour); const uint8_t bg_b_col = BLUE_VAL_RGBA(bg_colour); @@ -1820,7 +1935,7 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int while ( (index < text_len) && (line_len = strcspn( line, "\n" )) ) { - unsigned int line_width = line_len * CHAR_WIDTH * size; + unsigned int line_width = line_len * ZM_CHAR_WIDTH * size; unsigned int lo_line_x = coord.X(); unsigned int lo_line_y = coord.Y() + (line_no * LINE_HEIGHT * size); @@ -1851,17 +1966,17 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int if ( colours == ZM_COLOUR_GRAY8 ) { unsigned char *ptr = &buffer[(lo_line_y*width)+lo_line_x]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (CHAR_HEIGHT * size); y++, r++, ptr += width ) + for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += width ) { unsigned char *temp_ptr = ptr; for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { int f; if (size == 2) - f = bigfontdata[(line[c] * CHAR_HEIGHT * size) + r]; + f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; else - f = fontdata[(line[c] * CHAR_HEIGHT) + r]; - for ( unsigned int i = 0; i < (CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) + f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; + for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) { if ( f & (zm_text_bitmask >> i) ) { @@ -1881,17 +1996,17 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int unsigned int wc = width * colours; unsigned char *ptr = &buffer[((lo_line_y*width)+lo_line_x)*colours]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (CHAR_HEIGHT * size); y++, r++, ptr += wc ) + for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += wc ) { unsigned char *temp_ptr = ptr; for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { int f; if (size == 2) - f = bigfontdata[(line[c] * CHAR_HEIGHT * size) + r]; + f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; else - f = fontdata[(line[c] * CHAR_HEIGHT) + r]; - for ( unsigned int i = 0; i < (CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr += colours ) + f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; + for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr += colours ) { if ( f & (zm_text_bitmask >> i) ) { @@ -1913,42 +2028,42 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int } } else if ( colours == ZM_COLOUR_RGB32 ) - { + { unsigned int wc = width * colours; uint8_t *ptr = &buffer[((lo_line_y*width)+lo_line_x)<<2]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (CHAR_HEIGHT * size); y++, r++, ptr += wc ) + for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += wc ) { Rgb* temp_ptr = (Rgb*)ptr; for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { int f; if (size == 2) - f = bigfontdata[(line[c] * CHAR_HEIGHT * size) + r]; + f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; else - f = fontdata[(line[c] * CHAR_HEIGHT) + r]; - for ( unsigned int i = 0; i < (CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) + f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; + for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) { if ( f & (zm_text_bitmask >> i) ) { if ( !fg_trans ) { - *temp_ptr = fg_rgb_col; + *temp_ptr = fg_rgb_col; } } else if ( !bg_trans ) { - *temp_ptr = bg_rgb_col; + *temp_ptr = bg_rgb_col; } } } } - - } else { - Panic("Annotate called with unexpected colours: %d",colours); - return; - } - + + } else { + Panic("Annotate called with unexpected colours: %d",colours); + return; + } + index += line_len; while ( text[index] == '\n' ) { @@ -1959,18 +2074,14 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int } } -void Image::Timestamp( const char *label, const time_t when, const Coord &coord, const int size ) -{ +void Image::Timestamp( const char *label, const time_t when, const Coord &coord, const int size ) { char time_text[64]; strftime( time_text, sizeof(time_text), "%y/%m/%d %H:%M:%S", localtime( &when ) ); char text[64]; - if ( label ) - { + if ( label ) { snprintf( text, sizeof(text), "%s - %s", label, time_text ); Annotate( text, coord, size ); - } - else - { + } else { Annotate( time_text, coord, size ); } } @@ -1979,21 +2090,21 @@ void Image::Timestamp( const char *label, const time_t when, const Coord &coord, void Image::Colourise(const unsigned int p_reqcolours, const unsigned int p_reqsubpixelorder) { Debug(9, "Colourise: Req colours: %u Req subpixel order: %u Current colours: %u Current subpixel order: %u",p_reqcolours,p_reqsubpixelorder,colours,subpixelorder); - + if ( colours != ZM_COLOUR_GRAY8) { Warning("Target image is already colourised, colours: %u",colours); return; } - + if ( p_reqcolours == ZM_COLOUR_RGB32 ) { /* RGB32 */ Rgb* new_buffer = (Rgb*)AllocBuffer(pixels*sizeof(Rgb)); - + const uint8_t *psrc = buffer; Rgb* pdest = new_buffer; Rgb subpixel; Rgb newpixel; - + if ( p_reqsubpixelorder == ZM_SUBPIX_ORDER_ABGR || p_reqsubpixelorder == ZM_SUBPIX_ORDER_ARGB) { /* ARGB\ABGR subpixel order. alpha byte is first (mem+0), so we need to shift the pixel left in the end */ for(unsigned int i=0;i= 35) { + /* Use SSSE3 functions */ switch(subpixelorder) { case ZM_SUBPIX_ORDER_BGRA: - std_convert_bgra_gray8(buffer,buffer,pixels); - break; + ssse3_convert_bgra_gray8(buffer,buffer,pixels); + break; case ZM_SUBPIX_ORDER_ARGB: - std_convert_argb_gray8(buffer,buffer,pixels); - break; + ssse3_convert_argb_gray8(buffer,buffer,pixels); + break; case ZM_SUBPIX_ORDER_ABGR: - std_convert_abgr_gray8(buffer,buffer,pixels); - break; + ssse3_convert_abgr_gray8(buffer,buffer,pixels); + break; case ZM_SUBPIX_ORDER_RGBA: default: - std_convert_rgba_gray8(buffer,buffer,pixels); - break; + ssse3_convert_rgba_gray8(buffer,buffer,pixels); + break; } } else { - /* Assume RGB24 */ - switch(subpixelorder) { - case ZM_SUBPIX_ORDER_BGR: - std_convert_bgr_gray8(buffer,buffer,pixels); - break; - case ZM_SUBPIX_ORDER_RGB: - default: - std_convert_rgb_gray8(buffer,buffer,pixels); - break; - } - + /* Use standard functions */ + if ( colours == ZM_COLOUR_RGB32 ) + { + switch(subpixelorder) { + case ZM_SUBPIX_ORDER_BGRA: + std_convert_bgra_gray8(buffer,buffer,pixels); + break; + case ZM_SUBPIX_ORDER_ARGB: + std_convert_argb_gray8(buffer,buffer,pixels); + break; + case ZM_SUBPIX_ORDER_ABGR: + std_convert_abgr_gray8(buffer,buffer,pixels); + break; + case ZM_SUBPIX_ORDER_RGBA: + default: + std_convert_rgba_gray8(buffer,buffer,pixels); + break; + } + } else { + /* Assume RGB24 */ + switch(subpixelorder) { + case ZM_SUBPIX_ORDER_BGR: + std_convert_bgr_gray8(buffer,buffer,pixels); + break; + case ZM_SUBPIX_ORDER_RGB: + default: + std_convert_rgb_gray8(buffer,buffer,pixels); + break; + } + } } } @@ -2082,10 +2212,10 @@ void Image::Fill( Rgb colour, const Box *limits ) { Panic( "Attempt to fill image with unexpected colours %d", colours ); } - + /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour,subpixelorder); - + unsigned int lo_x = limits?limits->Lo().X():0; unsigned int lo_y = limits?limits->Lo().Y():0; unsigned int hi_x = limits?limits->Hi().X():width-1; @@ -2119,7 +2249,7 @@ void Image::Fill( Rgb colour, const Box *limits ) for ( unsigned int y = lo_y; y <= (unsigned int)hi_y; y++ ) { Rgb *p = (Rgb*)&buffer[((y*width)+lo_x)<<2]; - + for ( unsigned int x = lo_x; x <= (unsigned int)hi_x; x++, p++) { /* Fast, copies the entire pixel in a single pass */ @@ -2135,12 +2265,12 @@ void Image::Fill( Rgb colour, int density, const Box *limits ) /* Allow the faster version to be used if density is not used (density=1) */ if(density <= 1) return Fill(colour,limits); - + if ( !(colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB24 || colours == ZM_COLOUR_RGB32 ) ) { Panic( "Attempt to fill image with unexpected colours %d", colours ); } - + /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour,subpixelorder); @@ -2189,7 +2319,7 @@ void Image::Fill( Rgb colour, int density, const Box *limits ) } } } - + } /* RGB32 compatible: complete */ @@ -2199,10 +2329,10 @@ void Image::Outline( Rgb colour, const Polygon &polygon ) { Panic( "Attempt to outline image with unexpected colours %d", colours ); } - + /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour,subpixelorder); - + int n_coords = polygon.getNumCoords(); for ( int j = 0, i = n_coords-1; j < n_coords; i = j++ ) { @@ -2296,7 +2426,7 @@ void Image::Outline( Rgb colour, const Polygon &polygon ) *(Rgb*)(buffer+(((int(round(y))*width)+x)<<2)) = colour; } } - + } } } @@ -2308,7 +2438,7 @@ void Image::Fill( Rgb colour, int density, const Polygon &polygon ) { Panic( "Attempt to fill image with unexpected colours %d", colours ); } - + /* Convert the colour's RGBA subpixel order into the image's subpixel order */ colour = rgb_convert(colour,subpixelorder); @@ -2454,7 +2584,7 @@ void Image::Fill( Rgb colour, const Polygon &polygon ) /* RGB32 compatible: complete */ void Image::Rotate( int angle ) { - + angle %= 360; if ( !angle ) @@ -2465,7 +2595,7 @@ void Image::Rotate( int angle ) { return; } - + unsigned int new_height = height; unsigned int new_width = width; uint8_t* rotate_buffer = AllocBuffer(size); @@ -2473,157 +2603,157 @@ void Image::Rotate( int angle ) switch( angle ) { case 90 : - { - new_height = width; - new_width = height; + { + new_height = width; + new_width = height; - unsigned int line_bytes = new_width*colours; - unsigned char *s_ptr = buffer; + unsigned int line_bytes = new_width*colours; + unsigned char *s_ptr = buffer; - if ( colours == ZM_COLOUR_GRAY8 ) - { - unsigned char *d_ptr; - for ( unsigned int i = new_width; i > 0; i-- ) + if ( colours == ZM_COLOUR_GRAY8 ) { - d_ptr = rotate_buffer+(i-1); - for ( unsigned int j = new_height; j > 0; j-- ) + unsigned char *d_ptr; + for ( unsigned int i = new_width; i > 0; i-- ) { - *d_ptr = *s_ptr++; - d_ptr += line_bytes; + d_ptr = rotate_buffer+(i-1); + for ( unsigned int j = new_height; j > 0; j-- ) + { + *d_ptr = *s_ptr++; + d_ptr += line_bytes; + } } } - } - else if ( colours == ZM_COLOUR_RGB32 ) - { - Rgb* s_rptr = (Rgb*)s_ptr; - Rgb* d_rptr; - for ( unsigned int i = new_width; i > 0; i-- ) + else if ( colours == ZM_COLOUR_RGB32 ) { - d_rptr = (Rgb*)(rotate_buffer+((i-1)<<2)); - for ( unsigned int j = new_height; j > 0; j-- ) + Rgb* s_rptr = (Rgb*)s_ptr; + Rgb* d_rptr; + for ( unsigned int i = new_width; i > 0; i-- ) { - *d_rptr = *s_rptr++; - d_rptr += new_width; + d_rptr = (Rgb*)(rotate_buffer+((i-1)<<2)); + for ( unsigned int j = new_height; j > 0; j-- ) + { + *d_rptr = *s_rptr++; + d_rptr += new_width; + } } } - } - else /* Assume RGB24 */ - { - unsigned char *d_ptr; - for ( unsigned int i = new_width; i > 0; i-- ) + else /* Assume RGB24 */ { - d_ptr = rotate_buffer+((i-1)*3); - for ( unsigned int j = new_height; j > 0; j-- ) + unsigned char *d_ptr; + for ( unsigned int i = new_width; i > 0; i-- ) { - *d_ptr = *s_ptr++; - *(d_ptr+1) = *s_ptr++; - *(d_ptr+2) = *s_ptr++; - d_ptr += line_bytes; + d_ptr = rotate_buffer+((i-1)*3); + for ( unsigned int j = new_height; j > 0; j-- ) + { + *d_ptr = *s_ptr++; + *(d_ptr+1) = *s_ptr++; + *(d_ptr+2) = *s_ptr++; + d_ptr += line_bytes; + } } } + break; } - break; - } case 180 : - { - unsigned char *s_ptr = buffer+size; - unsigned char *d_ptr = rotate_buffer; + { + unsigned char *s_ptr = buffer+size; + unsigned char *d_ptr = rotate_buffer; - if ( colours == ZM_COLOUR_GRAY8 ) - { - while( s_ptr > buffer ) + if ( colours == ZM_COLOUR_GRAY8 ) { - s_ptr--; - *d_ptr++ = *s_ptr; - } - } - else if ( colours == ZM_COLOUR_RGB32 ) - { - Rgb* s_rptr = (Rgb*)s_ptr; - Rgb* d_rptr = (Rgb*)d_ptr; - while( s_rptr > (Rgb*)buffer ) - { - s_rptr--; - *d_rptr++ = *s_rptr; - } - } - else /* Assume RGB24 */ - { - while( s_ptr > buffer ) - { - s_ptr -= 3; - *d_ptr++ = *s_ptr; - *d_ptr++ = *(s_ptr+1); - *d_ptr++ = *(s_ptr+2); - } - } - break; - } - case 270 : - { - new_height = width; - new_width = height; - - unsigned int line_bytes = new_width*colours; - unsigned char *s_ptr = buffer+size; - - if ( colours == ZM_COLOUR_GRAY8 ) - { - unsigned char *d_ptr; - for ( unsigned int i = new_width; i > 0; i-- ) - { - d_ptr = rotate_buffer+(i-1); - for ( unsigned int j = new_height; j > 0; j-- ) + while( s_ptr > buffer ) { s_ptr--; - *d_ptr = *s_ptr; - d_ptr += line_bytes; + *d_ptr++ = *s_ptr; } } - } - else if ( colours == ZM_COLOUR_RGB32 ) - { - Rgb* s_rptr = (Rgb*)s_ptr; - Rgb* d_rptr; - for ( unsigned int i = new_width; i > 0; i-- ) + else if ( colours == ZM_COLOUR_RGB32 ) { - d_rptr = (Rgb*)(rotate_buffer+((i-1)<<2)); - for ( unsigned int j = new_height; j > 0; j-- ) + Rgb* s_rptr = (Rgb*)s_ptr; + Rgb* d_rptr = (Rgb*)d_ptr; + while( s_rptr > (Rgb*)buffer ) { s_rptr--; - *d_rptr = *s_rptr; - d_rptr += new_width; + *d_rptr++ = *s_rptr; } } - } - else /* Assume RGB24 */ - { - unsigned char *d_ptr; - for ( unsigned int i = new_width; i > 0; i-- ) + else /* Assume RGB24 */ { - d_ptr = rotate_buffer+((i-1)*3); - for ( unsigned int j = new_height; j > 0; j-- ) + while( s_ptr > buffer ) { - *(d_ptr+2) = *(--s_ptr); - *(d_ptr+1) = *(--s_ptr); - *d_ptr = *(--s_ptr); - d_ptr += line_bytes; + s_ptr -= 3; + *d_ptr++ = *s_ptr; + *d_ptr++ = *(s_ptr+1); + *d_ptr++ = *(s_ptr+2); } } + break; + } + case 270 : + { + new_height = width; + new_width = height; + + unsigned int line_bytes = new_width*colours; + unsigned char *s_ptr = buffer+size; + + if ( colours == ZM_COLOUR_GRAY8 ) + { + unsigned char *d_ptr; + for ( unsigned int i = new_width; i > 0; i-- ) + { + d_ptr = rotate_buffer+(i-1); + for ( unsigned int j = new_height; j > 0; j-- ) + { + s_ptr--; + *d_ptr = *s_ptr; + d_ptr += line_bytes; + } + } + } + else if ( colours == ZM_COLOUR_RGB32 ) + { + Rgb* s_rptr = (Rgb*)s_ptr; + Rgb* d_rptr; + for ( unsigned int i = new_width; i > 0; i-- ) + { + d_rptr = (Rgb*)(rotate_buffer+((i-1)<<2)); + for ( unsigned int j = new_height; j > 0; j-- ) + { + s_rptr--; + *d_rptr = *s_rptr; + d_rptr += new_width; + } + } + } + else /* Assume RGB24 */ + { + unsigned char *d_ptr; + for ( unsigned int i = new_width; i > 0; i-- ) + { + d_ptr = rotate_buffer+((i-1)*3); + for ( unsigned int j = new_height; j > 0; j-- ) + { + *(d_ptr+2) = *(--s_ptr); + *(d_ptr+1) = *(--s_ptr); + *d_ptr = *(--s_ptr); + d_ptr += line_bytes; + } + } + } + break; } - break; - } } - + AssignDirect( new_width, new_height, colours, subpixelorder, rotate_buffer, size, ZM_BUFTYPE_ZM); - + } /* RGB32 compatible: complete */ void Image::Flip( bool leftright ) { uint8_t* flip_buffer = AllocBuffer(size); - + unsigned int line_bytes = width*colours; unsigned int line_bytes2 = 2*line_bytes; if ( leftright ) @@ -2688,9 +2818,9 @@ void Image::Flip( bool leftright ) d_ptr += line_bytes; } } - + AssignDirect( width, height, colours, subpixelorder, flip_buffer, size, ZM_BUFTYPE_ZM); - + } void Image::Scale( unsigned int factor ) @@ -2707,11 +2837,11 @@ void Image::Scale( unsigned int factor ) unsigned int new_width = (width*factor)/ZM_SCALE_BASE; unsigned int new_height = (height*factor)/ZM_SCALE_BASE; - + size_t scale_buffer_size = (new_width+1) * (new_height+1) * colours; - + uint8_t* scale_buffer = AllocBuffer(scale_buffer_size); - + if ( factor > ZM_SCALE_BASE ) { unsigned char *pd = scale_buffer; @@ -2778,7 +2908,7 @@ void Image::Scale( unsigned int factor ) { w_count += factor; w_index = w_count/ZM_SCALE_BASE; - + if ( w_index > last_w_index ) { for ( unsigned int c = 0; c < colours; c++ ) @@ -2798,15 +2928,15 @@ void Image::Scale( unsigned int factor ) new_width = last_w_index; new_height = last_h_index; } - + AssignDirect( new_width, new_height, colours, subpixelorder, scale_buffer, scale_buffer_size, ZM_BUFTYPE_ZM); - + } void Image::Deinterlace_Discard() { /* Simple deinterlacing. Copy the even lines into the odd lines */ - + if ( colours == ZM_COLOUR_GRAY8 ) { const uint8_t *psrc; @@ -2850,16 +2980,16 @@ void Image::Deinterlace_Discard() } else { Error("Deinterlace called with unexpected colours: %d", colours); } - + } void Image::Deinterlace_Linear() { /* Simple deinterlacing. The odd lines are average of the line above and line below */ - + const uint8_t *pbelow, *pabove; uint8_t *pcurrent; - + if ( colours == ZM_COLOUR_GRAY8 ) { for (unsigned int y = 1; y < (unsigned int)(height-1); y += 2) @@ -2926,15 +3056,15 @@ void Image::Deinterlace_Linear() } else { Error("Deinterlace called with unexpected colours: %d", colours); } - + } void Image::Deinterlace_Blend() { /* Simple deinterlacing. Blend the fields together. 50% blend */ - + uint8_t *pabove, *pcurrent; - + if ( colours == ZM_COLOUR_GRAY8 ) { for (unsigned int y = 1; y < (unsigned int)height; y += 2) @@ -2983,7 +3113,7 @@ void Image::Deinterlace_Blend() } else { Error("Deinterlace called with unexpected colours: %d", colours); } - + } void Image::Deinterlace_Blend_CustomRatio(int divider) @@ -2993,14 +3123,14 @@ void Image::Deinterlace_Blend_CustomRatio(int divider) /* 2 = 25% blending */ /* 3 = 12.% blending */ /* 4 = 6.25% blending */ - + uint8_t *pabove, *pcurrent; uint8_t subpix1, subpix2; - + if ( divider < 1 || divider > 4 ) { Error("Deinterlace called with invalid blend ratio"); } - + if ( colours == ZM_COLOUR_GRAY8 ) { for (unsigned int y = 1; y < (unsigned int)height; y += 2) @@ -3065,7 +3195,7 @@ void Image::Deinterlace_Blend_CustomRatio(int divider) } else { Error("Deinterlace called with unexpected colours: %d", colours); } - + } @@ -3075,44 +3205,44 @@ void Image::Deinterlace_4Field(const Image* next_image, unsigned int threshold) { Panic( "Attempt to deinterlace different sized images, expected %dx%dx%d %d, got %dx%dx%d %d", width, height, colours, subpixelorder, next_image->width, next_image->height, next_image->colours, next_image->subpixelorder); } - + switch(colours) { case ZM_COLOUR_RGB24: - { - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { - /* BGR subpixel order */ - std_deinterlace_4field_bgr(buffer, next_image->buffer, threshold, width, height); - } else { - /* Assume RGB subpixel order */ - std_deinterlace_4field_rgb(buffer, next_image->buffer, threshold, width, height); - } - break; - } + { + if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + /* BGR subpixel order */ + std_deinterlace_4field_bgr(buffer, next_image->buffer, threshold, width, height); + } else { + /* Assume RGB subpixel order */ + std_deinterlace_4field_rgb(buffer, next_image->buffer, threshold, width, height); + } + break; + } case ZM_COLOUR_RGB32: - { - if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - /* ARGB subpixel order */ - (*fptr_deinterlace_4field_argb)(buffer, next_image->buffer, threshold, width, height); - } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - /* ABGR subpixel order */ - (*fptr_deinterlace_4field_abgr)(buffer, next_image->buffer, threshold, width, height); - } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - /* BGRA subpixel order */ - (*fptr_deinterlace_4field_bgra)(buffer, next_image->buffer, threshold, width, height); - } else { - /* Assume RGBA subpixel order */ - (*fptr_deinterlace_4field_rgba)(buffer, next_image->buffer, threshold, width, height); - } - break; - } + { + if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + /* ARGB subpixel order */ + (*fptr_deinterlace_4field_argb)(buffer, next_image->buffer, threshold, width, height); + } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + /* ABGR subpixel order */ + (*fptr_deinterlace_4field_abgr)(buffer, next_image->buffer, threshold, width, height); + } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + /* BGRA subpixel order */ + (*fptr_deinterlace_4field_bgra)(buffer, next_image->buffer, threshold, width, height); + } else { + /* Assume RGBA subpixel order */ + (*fptr_deinterlace_4field_rgba)(buffer, next_image->buffer, threshold, width, height); + } + break; + } case ZM_COLOUR_GRAY8: - (*fptr_deinterlace_4field_gray8)(buffer, next_image->buffer, threshold, width, height); - break; + (*fptr_deinterlace_4field_gray8)(buffer, next_image->buffer, threshold, width, height); + break; default: - Panic("Deinterlace_4Field called with unexpected colours: %d",colours); - break; + Panic("Deinterlace_4Field called with unexpected colours: %d",colours); + break; } - + } @@ -3127,7 +3257,7 @@ void sse2_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, u static uint32_t divider = 0; static uint32_t clearmask = 0; static double current_blendpercent = 0.0; - + if(current_blendpercent != blendpercent) { /* Attempt to match the blending percent to one of the possible values */ if(blendpercent < 2.34375) { @@ -3159,29 +3289,29 @@ void sse2_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, u } __asm__ __volatile__( - "movd %4, %%xmm3\n\t" - "movd %5, %%xmm4\n\t" - "pshufd $0x0, %%xmm3, %%xmm3\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x10, %2\n\t" - "sse2_fastblend_iter:\n\t" - "movdqa (%0,%3),%%xmm0\n\t" - "movdqa %%xmm0,%%xmm2\n\t" - "movdqa (%1,%3),%%xmm1\n\t" - "psrlq %%xmm4,%%xmm0\n\t" - "psrlq %%xmm4,%%xmm1\n\t" - "pand %%xmm3,%%xmm1\n\t" - "pand %%xmm3,%%xmm0\n\t" - "psubb %%xmm0,%%xmm1\n\t" - "paddb %%xmm2,%%xmm1\n\t" - "movntdq %%xmm1,(%2,%3)\n\t" - "sub $0x10, %3\n\t" - "jnz sse2_fastblend_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (clearmask), "m" (divider) - : "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory" - ); + "movd %4, %%xmm3\n\t" + "movd %5, %%xmm4\n\t" + "pshufd $0x0, %%xmm3, %%xmm3\n\t" + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x10, %2\n\t" + "sse2_fastblend_iter:\n\t" + "movdqa (%0,%3),%%xmm0\n\t" + "movdqa %%xmm0,%%xmm2\n\t" + "movdqa (%1,%3),%%xmm1\n\t" + "psrlq %%xmm4,%%xmm0\n\t" + "psrlq %%xmm4,%%xmm1\n\t" + "pand %%xmm3,%%xmm1\n\t" + "pand %%xmm3,%%xmm0\n\t" + "psubb %%xmm0,%%xmm1\n\t" + "paddb %%xmm2,%%xmm1\n\t" + "movntdq %%xmm1,(%2,%3)\n\t" + "sub $0x10, %3\n\t" + "jnz sse2_fastblend_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (clearmask), "m" (divider) + : "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif @@ -3191,7 +3321,7 @@ __attribute__((noinline)) void std_fastblend(const uint8_t* col1, const uint8_t* static int divider = 0; static double current_blendpercent = 0.0; const uint8_t* const max_ptr = result + count; - + if(current_blendpercent != blendpercent) { /* Attempt to match the blending percent to one of the possible values */ if(blendpercent < 2.34375) { @@ -3215,7 +3345,7 @@ __attribute__((noinline)) void std_fastblend(const uint8_t* col1, const uint8_t* } current_blendpercent = blendpercent; } - + while(result < max_ptr) { result[0] = ((col2[0] - col1[0])>>divider) + col1[0]; @@ -3234,21 +3364,190 @@ __attribute__((noinline)) void std_fastblend(const uint8_t* col1, const uint8_t* result[13] = ((col2[13] - col1[13])>>divider) + col1[13]; result[14] = ((col2[14] - col1[14])>>divider) + col1[14]; result[15] = ((col2[15] - col1[15])>>divider) + col1[15]; - + col1 += 16; col2 += 16; result += 16; } } +/* FastBlend Neon for AArch32 */ +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +__attribute__((noinline,__target__("fpu=neon"))) +#endif +void neon32_armv7_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) + static int8_t divider = 0; + static double current_blendpercent = 0.0; + + if(current_blendpercent != blendpercent) { + /* Attempt to match the blending percent to one of the possible values */ + if(blendpercent < 2.34375) { + // 1.5625% blending + divider = 6; + } else if(blendpercent >= 2.34375 && blendpercent < 4.6875) { + // 3.125% blending + divider = 5; + } else if(blendpercent >= 4.6875 && blendpercent < 9.375) { + // 6.25% blending + divider = 4; + } else if(blendpercent >= 9.375 && blendpercent < 18.75) { + // 12.5% blending + divider = 3; + } else if(blendpercent >= 18.75 && blendpercent < 37.5) { + // 25% blending + divider = 2; + } else if(blendpercent >= 37.5) { + // 50% blending + divider = 1; + } + // We only have instruction to shift left by a variable, going negative shifts right :) + divider *= -1; + current_blendpercent = blendpercent; + } + + /* Q0(D0,D1) = col1+0 */ + /* Q1(D2,D3) = col1+16 */ + /* Q2(D4,D5) = col1+32 */ + /* Q3(D6,D7) = col1+48 */ + /* Q4(D8,D9) = col2+0 */ + /* Q5(D10,D11) = col2+16 */ + /* Q6(D12,D13) = col2+32 */ + /* Q7(D14,D15) = col2+48 */ + /* Q8(D16,D17) = col1tmp+0 */ + /* Q9(D18,D19) = col1tmp+16 */ + /* Q10(D20,D21) = col1tmp+32 */ + /* Q11(D22,D23) = col1tmp+48 */ + /* Q12(D24,D25) = divider */ + + __asm__ __volatile__ ( + "mov r12, %4\n\t" + "vdup.8 q12, r12\n\t" + "neon32_armv7_fastblend_iter:\n\t" + "vldm %0!, {q0,q1,q2,q3}\n\t" + "vldm %1!, {q4,q5,q6,q7}\n\t" + "pld [%0, #256]\n\t" + "pld [%1, #256]\n\t" + "vrshl.u8 q8, q0, q12\n\t" + "vrshl.u8 q9, q1, q12\n\t" + "vrshl.u8 q10, q2, q12\n\t" + "vrshl.u8 q11, q3, q12\n\t" + "vrshl.u8 q4, q4, q12\n\t" + "vrshl.u8 q5, q5, q12\n\t" + "vrshl.u8 q6, q6, q12\n\t" + "vrshl.u8 q7, q7, q12\n\t" + "vsub.i8 q4, q4, q8\n\t" + "vsub.i8 q5, q5, q9\n\t" + "vsub.i8 q6, q6, q10\n\t" + "vsub.i8 q7, q7, q11\n\t" + "vadd.i8 q4, q4, q0\n\t" + "vadd.i8 q5, q5, q1\n\t" + "vadd.i8 q6, q6, q2\n\t" + "vadd.i8 q7, q7, q3\n\t" + "vstm %2!, {q4,q5,q6,q7}\n\t" + "subs %3, %3, #64\n\t" + "bne neon32_armv7_fastblend_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (divider) + : "%r12", "%q0", "%q1", "%q2", "%q3", "%q4", "%q5", "%q6", "%q7", "%q8", "%q9", "%q10", "%q11", "%q12", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + +__attribute__((noinline)) void neon64_armv8_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { +#if (defined(__aarch64__) && !defined(ZM_STRIP_NEON)) + static int8_t divider = 0; + static double current_blendpercent = 0.0; + + if(current_blendpercent != blendpercent) { + /* Attempt to match the blending percent to one of the possible values */ + if(blendpercent < 2.34375) { + // 1.5625% blending + divider = 6; + } else if(blendpercent >= 2.34375 && blendpercent < 4.6875) { + // 3.125% blending + divider = 5; + } else if(blendpercent >= 4.6875 && blendpercent < 9.375) { + // 6.25% blending + divider = 4; + } else if(blendpercent >= 9.375 && blendpercent < 18.75) { + // 12.5% blending + divider = 3; + } else if(blendpercent >= 18.75 && blendpercent < 37.5) { + // 25% blending + divider = 2; + } else if(blendpercent >= 37.5) { + // 50% blending + divider = 1; + } + // We only have instruction to shift left by a variable, going negative shifts right :) + divider *= -1; + current_blendpercent = blendpercent; + } + + /* V16 = col1+0 */ + /* V17 = col1+16 */ + /* V18 = col1+32 */ + /* V19 = col1+48 */ + /* V20 = col2+0 */ + /* V21 = col2+16 */ + /* V22 = col2+32 */ + /* V23 = col2+48 */ + /* V24 = col1tmp+0 */ + /* V25 = col1tmp+16 */ + /* V26 = col1tmp+32 */ + /* V27 = col1tmp+48 */ + /* V28 = divider */ + + __asm__ __volatile__ ( + "mov x12, %4\n\t" + "dup v28.16b, w12\n\t" + "neon64_armv8_fastblend_iter:\n\t" + "ldp q16, q17, [%0], #32\n\t" + "ldp q18, q19, [%0], #32\n\t" + "ldp q20, q21, [%1], #32\n\t" + "ldp q22, q23, [%1], #32\n\t" + "prfm pldl1keep, [%0, #256]\n\t" + "prfm pldl1keep, [%1, #256]\n\t" + "urshl v24.16b, v16.16b, v28.16b\n\t" + "urshl v25.16b, v17.16b, v28.16b\n\t" + "urshl v26.16b, v18.16b, v28.16b\n\t" + "urshl v27.16b, v19.16b, v28.16b\n\t" + "urshl v20.16b, v20.16b, v28.16b\n\t" + "urshl v21.16b, v21.16b, v28.16b\n\t" + "urshl v22.16b, v22.16b, v28.16b\n\t" + "urshl v23.16b, v23.16b, v28.16b\n\t" + "sub v20.16b, v20.16b, v24.16b\n\t" + "sub v21.16b, v21.16b, v25.16b\n\t" + "sub v22.16b, v22.16b, v26.16b\n\t" + "sub v23.16b, v23.16b, v27.16b\n\t" + "add v20.16b, v20.16b, v16.16b\n\t" + "add v21.16b, v21.16b, v17.16b\n\t" + "add v22.16b, v22.16b, v18.16b\n\t" + "add v23.16b, v23.16b, v19.16b\n\t" + "stp q20, q21, [%2], #32\n\t" + "stp q22, q23, [%2], #32\n\t" + "subs %3, %3, #64\n\t" + "bne neon64_armv8_fastblend_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (divider) + : "%x12", "%v16", "%v17", "%v18", "%v19", "%v20", "%v21", "%v22", "%v23", "%v24", "%v25", "%v26", "%v27", "%v28", "cc", "memory" +); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + __attribute__((noinline)) void std_blend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { double divide = blendpercent / 100.0; double opacity = 1.0 - divide; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { *result++ = (*col1++ * opacity) + (*col2++ * divide); - + } } @@ -3258,7 +3557,7 @@ __attribute__((noinline)) void std_blend(const uint8_t* col1, const uint8_t* col __attribute__((noinline)) void std_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { /* Loop unrolling is used to work on 16 bytes (16 grayscale pixels) at a time */ const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { result[0] = abs(col1[0] - col2[0]); result[1] = abs(col1[1] - col2[1]); @@ -3276,7 +3575,7 @@ __attribute__((noinline)) void std_delta8_gray8(const uint8_t* col1, const uint8 result[13] = abs(col1[13] - col2[13]); result[14] = abs(col1[14] - col2[14]); result[15] = abs(col1[15] - col2[15]); - + col1 += 16; col2 += 16; result += 16; @@ -3288,7 +3587,7 @@ __attribute__((noinline)) void std_delta8_rgb(const uint8_t* col1, const uint8_t /* Loop unrolling is used to work on 12 bytes (4 rgb24 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { r = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); @@ -3306,7 +3605,7 @@ __attribute__((noinline)) void std_delta8_rgb(const uint8_t* col1, const uint8_t g = abs(col1[10] - col2[10]); b = abs(col1[11] - col2[11]); result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 12; col2 += 12; result += 4; @@ -3318,7 +3617,7 @@ __attribute__((noinline)) void std_delta8_bgr(const uint8_t* col1, const uint8_t /* Loop unrolling is used to work on 12 bytes (4 rgb24 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { b = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); @@ -3336,7 +3635,7 @@ __attribute__((noinline)) void std_delta8_bgr(const uint8_t* col1, const uint8_t g = abs(col1[10] - col2[10]); r = abs(col1[11] - col2[11]); result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 12; col2 += 12; result += 4; @@ -3348,7 +3647,7 @@ __attribute__((noinline)) void std_delta8_rgba(const uint8_t* col1, const uint8_ /* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { r = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); @@ -3366,7 +3665,7 @@ __attribute__((noinline)) void std_delta8_rgba(const uint8_t* col1, const uint8_ g = abs(col1[13] - col2[13]); b = abs(col1[14] - col2[14]); result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; col2 += 16; result += 4; @@ -3378,7 +3677,7 @@ __attribute__((noinline)) void std_delta8_bgra(const uint8_t* col1, const uint8_ /* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { b = abs(col1[0] - col2[0]); g = abs(col1[1] - col2[1]); @@ -3396,7 +3695,7 @@ __attribute__((noinline)) void std_delta8_bgra(const uint8_t* col1, const uint8_ g = abs(col1[13] - col2[13]); r = abs(col1[14] - col2[14]); result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; col2 += 16; result += 4; @@ -3408,7 +3707,7 @@ __attribute__((noinline)) void std_delta8_argb(const uint8_t* col1, const uint8_ /* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { r = abs(col1[1] - col2[1]); g = abs(col1[2] - col2[2]); @@ -3426,7 +3725,7 @@ __attribute__((noinline)) void std_delta8_argb(const uint8_t* col1, const uint8_ g = abs(col1[14] - col2[14]); b = abs(col1[15] - col2[15]); result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; col2 += 16; result += 4; @@ -3438,7 +3737,7 @@ __attribute__((noinline)) void std_delta8_abgr(const uint8_t* col1, const uint8_ /* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */ int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { b = abs(col1[1] - col2[1]); g = abs(col1[2] - col2[2]); @@ -3456,13 +3755,242 @@ __attribute__((noinline)) void std_delta8_abgr(const uint8_t* col1, const uint8_ g = abs(col1[14] - col2[14]); r = abs(col1[15] - col2[15]); result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; col2 += 16; result += 4; } } +/* Grayscale Neon for AArch32 */ +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +__attribute__((noinline,__target__("fpu=neon"))) +#endif +void neon32_armv7_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) + + /* Q0(D0,D1) = col1+0 */ + /* Q1(D2,D3) = col1+16 */ + /* Q2(D4,D5) = col1+32 */ + /* Q3(D6,D7) = col1+48 */ + /* Q4(D8,D9) = col2+0 */ + /* Q5(D10,D11) = col2+16 */ + /* Q6(D12,D13) = col2+32 */ + /* Q7(D14,D15) = col2+48 */ + + __asm__ __volatile__ ( + "neon32_armv7_delta8_gray8_iter:\n\t" + "vldm %0!, {q0,q1,q2,q3}\n\t" + "vldm %1!, {q4,q5,q6,q7}\n\t" + "pld [%0, #512]\n\t" + "pld [%1, #512]\n\t" + "vabd.u8 q0, q0, q4\n\t" + "vabd.u8 q1, q1, q5\n\t" + "vabd.u8 q2, q2, q6\n\t" + "vabd.u8 q3, q3, q7\n\t" + "vstm %2!, {q0,q1,q2,q3}\n\t" + "subs %3, %3, #64\n\t" + "bne neon32_armv7_delta8_gray8_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%q0", "%q1", "%q2", "%q3", "%q4", "%q5", "%q6", "%q7", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + +/* Grayscale Neon for AArch64 */ +__attribute__((noinline)) void neon64_armv8_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { +#if (defined(__aarch64__) && !defined(ZM_STRIP_NEON)) + + /* V16 = col1+0 */ + /* V17 = col1+16 */ + /* V18 = col1+32 */ + /* V19 = col1+48 */ + /* V20 = col2+0 */ + /* V21 = col2+16 */ + /* V22 = col2+32 */ + /* V23 = col2+48 */ + + __asm__ __volatile__ ( + "neon64_armv8_delta8_gray8_iter:\n\t" + "ldp q16, q17, [%0], #32\n\t" + "ldp q18, q19, [%0], #32\n\t" + "ldp q20, q21, [%1], #32\n\t" + "ldp q22, q23, [%1], #32\n\t" + "prfm pldl1keep, [%0, #512]\n\t" + "prfm pldl1keep, [%1, #512]\n\t" + "uabd v16.16b, v16.16b, v20.16b\n\t" + "uabd v17.16b, v17.16b, v21.16b\n\t" + "uabd v18.16b, v18.16b, v22.16b\n\t" + "uabd v19.16b, v19.16b, v23.16b\n\t" + "stp q16, q17, [%2], #32\n\t" + "stp q18, q19, [%2], #32\n\t" + "subs %3, %3, #64\n\t" + "bne neon64_armv8_delta8_gray8_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%v16", "%v17", "%v18", "%v19", "%v20", "%v21", "%v22", "%v23", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + +/* RGB32 Neon for AArch32 */ +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +__attribute__((noinline,__target__("fpu=neon"))) +#endif +void neon32_armv7_delta8_rgb32(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, uint32_t multiplier) { +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) + + /* Q0(D0,D1) = col1+0 */ + /* Q1(D2,D3) = col1+16 */ + /* Q2(D4,D5) = col1+32 */ + /* Q3(D6,D7) = col1+48 */ + /* Q4(D8,D9) = col2+0 */ + /* Q5(D10,D11) = col2+16 */ + /* Q6(D12,D13) = col2+32 */ + /* Q7(D14,D15) = col2+48 */ + /* Q8(D16,D17) = multiplier */ + + __asm__ __volatile__ ( + "mov r12, %4\n\t" + "vdup.32 q8, r12\n\t" + "neon32_armv7_delta8_rgb32_iter:\n\t" + "vldm %0!, {q0,q1,q2,q3}\n\t" + "vldm %1!, {q4,q5,q6,q7}\n\t" + "pld [%0, #256]\n\t" + "pld [%1, #256]\n\t" + "vabd.u8 q0, q0, q4\n\t" + "vabd.u8 q1, q1, q5\n\t" + "vabd.u8 q2, q2, q6\n\t" + "vabd.u8 q3, q3, q7\n\t" + "vrshr.u8 q0, q0, #3\n\t" + "vrshr.u8 q1, q1, #3\n\t" + "vrshr.u8 q2, q2, #3\n\t" + "vrshr.u8 q3, q3, #3\n\t" + "vmul.i8 q0, q0, q8\n\t" + "vmul.i8 q1, q1, q8\n\t" + "vmul.i8 q2, q2, q8\n\t" + "vmul.i8 q3, q3, q8\n\t" + "vpadd.i8 d0, d0, d1\n\t" + "vpadd.i8 d2, d2, d3\n\t" + "vpadd.i8 d4, d4, d5\n\t" + "vpadd.i8 d6, d6, d7\n\t" + "vpadd.i8 d0, d0, d0\n\t" + "vpadd.i8 d1, d2, d2\n\t" + "vpadd.i8 d2, d4, d4\n\t" + "vpadd.i8 d3, d6, d6\n\t" + "vst4.32 {d0[0],d1[0],d2[0],d3[0]}, [%2]!\n\t" + "subs %3, %3, #16\n\t" + "bne neon32_armv7_delta8_rgb32_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (multiplier) + : "%r12", "%q0", "%q1", "%q2", "%q3", "%q4", "%q5", "%q6", "%q7", "%q8", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + +/* RGB32 Neon for AArch64 */ +__attribute__((noinline)) void neon64_armv8_delta8_rgb32(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, uint32_t multiplier) { +#if (defined(__aarch64__) && !defined(ZM_STRIP_NEON)) + + /* V16 = col1+0 */ + /* V17 = col1+16 */ + /* V18 = col1+32 */ + /* V19 = col1+48 */ + /* V20 = col2+0 */ + /* V21 = col2+16 */ + /* V22 = col2+32 */ + /* V23 = col2+48 */ + /* V24 = multiplier */ + + __asm__ __volatile__ ( + "mov x12, %4\n\t" + "dup v24.4s, w12\n\t" + "neon64_armv8_delta8_rgb32_iter:\n\t" + "ldp q16, q17, [%0], #32\n\t" + "ldp q18, q19, [%0], #32\n\t" + "ldp q20, q21, [%1], #32\n\t" + "ldp q22, q23, [%1], #32\n\t" + "prfm pldl1keep, [%0, #256]\n\t" + "prfm pldl1keep, [%1, #256]\n\t" + "uabd v16.16b, v16.16b, v20.16b\n\t" + "uabd v17.16b, v17.16b, v21.16b\n\t" + "uabd v18.16b, v18.16b, v22.16b\n\t" + "uabd v19.16b, v19.16b, v23.16b\n\t" + "urshr v16.16b, v16.16b, #3\n\t" + "urshr v17.16b, v17.16b, #3\n\t" + "urshr v18.16b, v18.16b, #3\n\t" + "urshr v19.16b, v19.16b, #3\n\t" + "mul v16.16b, v16.16b, v24.16b\n\t" + "mul v17.16b, v17.16b, v24.16b\n\t" + "mul v18.16b, v18.16b, v24.16b\n\t" + "mul v19.16b, v19.16b, v24.16b\n\t" + "addp v16.16b, v16.16b, v16.16b\n\t" + "addp v17.16b, v17.16b, v17.16b\n\t" + "addp v18.16b, v18.16b, v18.16b\n\t" + "addp v19.16b, v19.16b, v19.16b\n\t" + "addp v16.16b, v16.16b, v16.16b\n\t" + "addp v17.16b, v17.16b, v17.16b\n\t" + "addp v18.16b, v18.16b, v18.16b\n\t" + "addp v19.16b, v19.16b, v19.16b\n\t" + "st4 {v16.s, v17.s, v18.s, v19.s}[0], [%2], #16\n\t" + "subs %3, %3, #16\n\t" + "bne neon64_armv8_delta8_rgb32_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (multiplier) + : "%x12", "%v16", "%v17", "%v18", "%v19", "%v20", "%v21", "%v22", "%v23", "%v24", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + +/* RGB32: RGBA Neon for AArch32 */ +void neon32_armv7_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x00010502); +} + +/* RGB32: BGRA Neon for AArch32 */ +void neon32_armv7_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x00020501); +} + +/* RGB32: ARGB Neon for AArch32 */ +void neon32_armv7_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x01050200); +} + +/* RGB32: ABGR Neon for AArch32 */ +void neon32_armv7_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x02050100); +} + +/* RGB32: RGBA Neon for AArch64 */ +void neon64_armv8_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon64_armv8_delta8_rgb32(col1, col2, result, count, 0x00010502); +} + +/* RGB32: BGRA Neon for AArch64 */ +void neon64_armv8_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon64_armv8_delta8_rgb32(col1, col2, result, count, 0x00020501); +} + +/* RGB32: ARGB Neon for AArch64 */ +void neon64_armv8_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon64_armv8_delta8_rgb32(col1, col2, result, count, 0x01050200); +} + +/* RGB32: ABGR Neon for AArch64 */ +void neon64_armv8_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon64_armv8_delta8_rgb32(col1, col2, result, count, 0x02050100); +} + /* Grayscale SSE2 */ #if defined(__i386__) || defined(__x86_64__) __attribute__((noinline,__target__("sse2"))) @@ -3471,24 +3999,24 @@ void sse2_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) __asm__ __volatile__ ( - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x10, %2\n\t" - "sse2_delta8_gray8_iter:\n\t" - "movdqa (%0,%3), %%xmm1\n\t" - "movdqa (%1,%3), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm3\n\t" - "movdqa %%xmm2, %%xmm4\n\t" - "pmaxub %%xmm1, %%xmm2\n\t" - "pminub %%xmm3, %%xmm4\n\t" - "psubb %%xmm4, %%xmm2\n\t" - "movntdq %%xmm2, (%2,%3)\n\t" - "sub $0x10, %3\n\t" - "jnz sse2_delta8_gray8_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count) - : "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory" - ); + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x10, %2\n\t" + "sse2_delta8_gray8_iter:\n\t" + "movdqa (%0,%3), %%xmm1\n\t" + "movdqa (%1,%3), %%xmm2\n\t" + "movdqa %%xmm1, %%xmm3\n\t" + "movdqa %%xmm2, %%xmm4\n\t" + "pmaxub %%xmm1, %%xmm2\n\t" + "pminub %%xmm3, %%xmm4\n\t" + "psubb %%xmm4, %%xmm2\n\t" + "movntdq %%xmm2, (%2,%3)\n\t" + "sub $0x10, %3\n\t" + "jnz sse2_delta8_gray8_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif @@ -3500,53 +4028,53 @@ __attribute__((noinline,__target__("sse2"))) #endif void sse2_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - + __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "sse2_delta8_rgba_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" - "movdqa %%xmm2, %%xmm6\n\t" - "pmaxub %%xmm1, %%xmm2\n\t" - "pminub %%xmm5, %%xmm6\n\t" - "psubb %%xmm6, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm3\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "packssdw %%xmm1, %%xmm1\n\t" - "packuswb %%xmm1, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz sse2_delta8_rgba_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" - ); + "mov $0x1F1F1F1F, %%eax\n\t" + "movd %%eax, %%xmm4\n\t" + "pshufd $0x0, %%xmm4, %%xmm4\n\t" + "mov $0xff, %%eax\n\t" + "movd %%eax, %%xmm0\n\t" + "pshufd $0x0, %%xmm0, %%xmm0\n\t" + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x4, %2\n\t" + "sse2_delta8_rgba_iter:\n\t" + "movdqa (%0,%3,4), %%xmm1\n\t" + "movdqa (%1,%3,4), %%xmm2\n\t" + "psrlq $0x3, %%xmm1\n\t" + "psrlq $0x3, %%xmm2\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pand %%xmm4, %%xmm2\n\t" + "movdqa %%xmm1, %%xmm5\n\t" + "movdqa %%xmm2, %%xmm6\n\t" + "pmaxub %%xmm1, %%xmm2\n\t" + "pminub %%xmm5, %%xmm6\n\t" + "psubb %%xmm6, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm3\n\t" + "psrld $0x8, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm1\n\t" + "pslld $0x2, %%xmm2\n\t" + "paddd %%xmm1, %%xmm2\n\t" + "movdqa %%xmm3, %%xmm1\n\t" + "pand %%xmm0, %%xmm1\n\t" + "paddd %%xmm1, %%xmm1\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "movdqa %%xmm3, %%xmm2\n\t" + "psrld $0x10, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "packssdw %%xmm1, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" + "movd %%xmm1, %%eax\n\t" + "movnti %%eax, (%2,%3)\n\t" + "sub $0x4, %3\n\t" + "jnz sse2_delta8_rgba_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif @@ -3558,53 +4086,53 @@ __attribute__((noinline,__target__("sse2"))) #endif void sse2_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - + __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "sse2_delta8_bgra_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" - "movdqa %%xmm2, %%xmm6\n\t" - "pmaxub %%xmm1, %%xmm2\n\t" - "pminub %%xmm5, %%xmm6\n\t" - "psubb %%xmm6, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm3\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "packssdw %%xmm1, %%xmm1\n\t" - "packuswb %%xmm1, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz sse2_delta8_bgra_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" - ); + "mov $0x1F1F1F1F, %%eax\n\t" + "movd %%eax, %%xmm4\n\t" + "pshufd $0x0, %%xmm4, %%xmm4\n\t" + "mov $0xff, %%eax\n\t" + "movd %%eax, %%xmm0\n\t" + "pshufd $0x0, %%xmm0, %%xmm0\n\t" + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x4, %2\n\t" + "sse2_delta8_bgra_iter:\n\t" + "movdqa (%0,%3,4), %%xmm1\n\t" + "movdqa (%1,%3,4), %%xmm2\n\t" + "psrlq $0x3, %%xmm1\n\t" + "psrlq $0x3, %%xmm2\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pand %%xmm4, %%xmm2\n\t" + "movdqa %%xmm1, %%xmm5\n\t" + "movdqa %%xmm2, %%xmm6\n\t" + "pmaxub %%xmm1, %%xmm2\n\t" + "pminub %%xmm5, %%xmm6\n\t" + "psubb %%xmm6, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm3\n\t" + "psrld $0x8, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm1\n\t" + "pslld $0x2, %%xmm2\n\t" + "paddd %%xmm1, %%xmm2\n\t" + "movdqa %%xmm3, %%xmm1\n\t" + "pand %%xmm0, %%xmm1\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "movdqa %%xmm3, %%xmm2\n\t" + "psrld $0x10, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "paddd %%xmm2, %%xmm2\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "packssdw %%xmm1, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" + "movd %%xmm1, %%eax\n\t" + "movnti %%eax, (%2,%3)\n\t" + "sub $0x4, %3\n\t" + "jnz sse2_delta8_bgra_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif @@ -3616,54 +4144,54 @@ __attribute__((noinline,__target__("sse2"))) #endif void sse2_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - + __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "sse2_delta8_argb_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" - "movdqa %%xmm2, %%xmm6\n\t" - "pmaxub %%xmm1, %%xmm2\n\t" - "pminub %%xmm5, %%xmm6\n\t" - "psubb %%xmm6, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm3\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "psrld $0x8, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x18, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "packssdw %%xmm1, %%xmm1\n\t" - "packuswb %%xmm1, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz sse2_delta8_argb_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" - ); + "mov $0x1F1F1F1F, %%eax\n\t" + "movd %%eax, %%xmm4\n\t" + "pshufd $0x0, %%xmm4, %%xmm4\n\t" + "mov $0xff, %%eax\n\t" + "movd %%eax, %%xmm0\n\t" + "pshufd $0x0, %%xmm0, %%xmm0\n\t" + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x4, %2\n\t" + "sse2_delta8_argb_iter:\n\t" + "movdqa (%0,%3,4), %%xmm1\n\t" + "movdqa (%1,%3,4), %%xmm2\n\t" + "psrlq $0x3, %%xmm1\n\t" + "psrlq $0x3, %%xmm2\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pand %%xmm4, %%xmm2\n\t" + "movdqa %%xmm1, %%xmm5\n\t" + "movdqa %%xmm2, %%xmm6\n\t" + "pmaxub %%xmm1, %%xmm2\n\t" + "pminub %%xmm5, %%xmm6\n\t" + "psubb %%xmm6, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm3\n\t" + "psrld $0x10, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm1\n\t" + "pslld $0x2, %%xmm2\n\t" + "paddd %%xmm1, %%xmm2\n\t" + "movdqa %%xmm3, %%xmm1\n\t" + "psrld $0x8, %%xmm1\n\t" + "pand %%xmm0, %%xmm1\n\t" + "paddd %%xmm1, %%xmm1\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "movdqa %%xmm3, %%xmm2\n\t" + "psrld $0x18, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "packssdw %%xmm1, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" + "movd %%xmm1, %%eax\n\t" + "movnti %%eax, (%2,%3)\n\t" + "sub $0x4, %3\n\t" + "jnz sse2_delta8_argb_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif @@ -3675,279 +4203,126 @@ __attribute__((noinline,__target__("sse2"))) #endif void sse2_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - + __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "sse2_delta8_abgr_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" - "movdqa %%xmm2, %%xmm6\n\t" - "pmaxub %%xmm1, %%xmm2\n\t" - "pminub %%xmm5, %%xmm6\n\t" - "psubb %%xmm6, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm3\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "psrld $0x8, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x18, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "packssdw %%xmm1, %%xmm1\n\t" - "packuswb %%xmm1, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz sse2_delta8_abgr_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" - ); + "mov $0x1F1F1F1F, %%eax\n\t" + "movd %%eax, %%xmm4\n\t" + "pshufd $0x0, %%xmm4, %%xmm4\n\t" + "mov $0xff, %%eax\n\t" + "movd %%eax, %%xmm0\n\t" + "pshufd $0x0, %%xmm0, %%xmm0\n\t" + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x4, %2\n\t" + "sse2_delta8_abgr_iter:\n\t" + "movdqa (%0,%3,4), %%xmm1\n\t" + "movdqa (%1,%3,4), %%xmm2\n\t" + "psrlq $0x3, %%xmm1\n\t" + "psrlq $0x3, %%xmm2\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pand %%xmm4, %%xmm2\n\t" + "movdqa %%xmm1, %%xmm5\n\t" + "movdqa %%xmm2, %%xmm6\n\t" + "pmaxub %%xmm1, %%xmm2\n\t" + "pminub %%xmm5, %%xmm6\n\t" + "psubb %%xmm6, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm3\n\t" + "psrld $0x10, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "movdqa %%xmm2, %%xmm1\n\t" + "pslld $0x2, %%xmm2\n\t" + "paddd %%xmm1, %%xmm2\n\t" + "movdqa %%xmm3, %%xmm1\n\t" + "psrld $0x8, %%xmm1\n\t" + "pand %%xmm0, %%xmm1\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "movdqa %%xmm3, %%xmm2\n\t" + "psrld $0x18, %%xmm2\n\t" + "pand %%xmm0, %%xmm2\n\t" + "paddd %%xmm2, %%xmm2\n\t" + "paddd %%xmm2, %%xmm1\n\t" + "packssdw %%xmm1, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" + "movd %%xmm1, %%eax\n\t" + "movnti %%eax, (%2,%3)\n\t" + "sub $0x4, %3\n\t" + "jnz sse2_delta8_abgr_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory" + ); +#else + Panic("SSE function called on a non x86\\x86-64 platform"); +#endif +} + +/* RGB32 SSSE3 */ +#if defined(__i386__) || defined(__x86_64__) +__attribute__((noinline,__target__("ssse3"))) +#endif +void ssse3_delta8_rgb32(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, uint32_t multiplier) { +#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) + + /* XMM0 - zero */ + /* XMM1 - col1 */ + /* XMM2 - col2 */ + /* XMM3 - multiplier */ + /* XMM4 - divide mask */ + + __asm__ __volatile__ ( + "mov $0x1F1F1F1F, %%eax\n\t" + "movd %%eax, %%xmm4\n\t" + "pshufd $0x0, %%xmm4, %%xmm4\n\t" + "mov %4, %%eax\n\t" + "movd %%eax, %%xmm3\n\t" + "pshufd $0x0, %%xmm3, %%xmm3\n\t" + "pxor %%xmm0, %%xmm0\n\t" + "sub $0x10, %0\n\t" + "sub $0x10, %1\n\t" + "sub $0x4, %2\n\t" + "ssse3_delta8_rgb32_iter:\n\t" + "movdqa (%0,%3,4), %%xmm1\n\t" + "movdqa (%1,%3,4), %%xmm2\n\t" + "psrlq $0x3, %%xmm1\n\t" + "psrlq $0x3, %%xmm2\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pand %%xmm4, %%xmm2\n\t" + "psubb %%xmm2, %%xmm1\n\t" + "pabsb %%xmm1, %%xmm1\n\t" + "pmaddubsw %%xmm3, %%xmm1\n\t" + "phaddw %%xmm0, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" + "movd %%xmm1, %%eax\n\t" + "movnti %%eax, (%2,%3)\n\t" + "sub $0x4, %3\n\t" + "jnz ssse3_delta8_rgb32_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "g" (multiplier) + : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif } /* RGB32: RGBA SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif void ssse3_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %4, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "ssse3_delta8_rgba_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz ssse3_delta8_rgba_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif + ssse3_delta8_rgb32(col1, col2, result, count, 0x00010502); } /* RGB32: BGRA SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif void ssse3_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %4, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "ssse3_delta8_bgra_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz ssse3_delta8_bgra_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif + ssse3_delta8_rgb32(col1, col2, result, count, 0x00020501); } /* RGB32: ARGB SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif void ssse3_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %4, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "ssse3_delta8_argb_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "psrld $0x8, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x18, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz ssse3_delta8_argb_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif + ssse3_delta8_rgb32(col1, col2, result, count, 0x01050200); } /* RGB32: ABGR SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif void ssse3_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %4, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "ssse3_delta8_abgr_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "psrld $0x8, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x18, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz ssse3_delta8_abgr_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif + ssse3_delta8_rgb32(col1, col2, result, count, 0x02050100); } @@ -3957,7 +4332,7 @@ void ssse3_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result __attribute__((noinline)) void std_convert_rgb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { unsigned int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { r = col1[0]; g = col1[1]; @@ -3975,7 +4350,7 @@ __attribute__((noinline)) void std_convert_rgb_gray8(const uint8_t* col1, uint8_ g = col1[10]; b = col1[11]; result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 12; result += 4; } @@ -3985,7 +4360,7 @@ __attribute__((noinline)) void std_convert_rgb_gray8(const uint8_t* col1, uint8_ __attribute__((noinline)) void std_convert_bgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { unsigned int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { b = col1[0]; g = col1[1]; @@ -4003,7 +4378,7 @@ __attribute__((noinline)) void std_convert_bgr_gray8(const uint8_t* col1, uint8_ g = col1[10]; r = col1[11]; result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 12; result += 4; } @@ -4013,7 +4388,7 @@ __attribute__((noinline)) void std_convert_bgr_gray8(const uint8_t* col1, uint8_ __attribute__((noinline)) void std_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { unsigned int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { r = col1[0]; g = col1[1]; @@ -4031,7 +4406,7 @@ __attribute__((noinline)) void std_convert_rgba_gray8(const uint8_t* col1, uint8 g = col1[13]; b = col1[14]; result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; result += 4; } @@ -4041,7 +4416,7 @@ __attribute__((noinline)) void std_convert_rgba_gray8(const uint8_t* col1, uint8 __attribute__((noinline)) void std_convert_bgra_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { unsigned int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { b = col1[0]; g = col1[1]; @@ -4059,7 +4434,7 @@ __attribute__((noinline)) void std_convert_bgra_gray8(const uint8_t* col1, uint8 g = col1[13]; r = col1[14]; result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; result += 4; } @@ -4069,7 +4444,7 @@ __attribute__((noinline)) void std_convert_bgra_gray8(const uint8_t* col1, uint8 __attribute__((noinline)) void std_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { unsigned int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { r = col1[1]; g = col1[2]; @@ -4087,7 +4462,7 @@ __attribute__((noinline)) void std_convert_argb_gray8(const uint8_t* col1, uint8 g = col1[14]; b = col1[15]; result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; result += 4; } @@ -4097,7 +4472,7 @@ __attribute__((noinline)) void std_convert_argb_gray8(const uint8_t* col1, uint8 __attribute__((noinline)) void std_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { unsigned int r,g,b; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { b = col1[1]; g = col1[2]; @@ -4115,7 +4490,7 @@ __attribute__((noinline)) void std_convert_abgr_gray8(const uint8_t* col1, uint8 g = col1[14]; r = col1[15]; result[3] = (r + r + b + g + g + g + g + g)>>3; - + col1 += 16; result += 4; } @@ -4125,7 +4500,7 @@ __attribute__((noinline)) void std_convert_abgr_gray8(const uint8_t* col1, uint8 __attribute__((noinline)) void std_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { const uint16_t* yuvbuf = (const uint16_t*)col1; const uint8_t* const max_ptr = result + count; - + while(result < max_ptr) { result[0] = (uint8_t)yuvbuf[0]; result[1] = (uint8_t)yuvbuf[1]; @@ -4143,61 +4518,74 @@ __attribute__((noinline)) void std_convert_yuyv_gray8(const uint8_t* col1, uint8 result[13] = (uint8_t)yuvbuf[13]; result[14] = (uint8_t)yuvbuf[14]; result[15] = (uint8_t)yuvbuf[15]; - + yuvbuf += 16; result += 16; } } -/* RGBA to grayscale SSSE3 */ +/* RGB32 to grayscale SSSE3 */ #if defined(__i386__) || defined(__x86_64__) __attribute__((noinline,__target__("ssse3"))) #endif -void ssse3_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { +void ssse3_convert_rgb32_gray8(const uint8_t* col1, uint8_t* result, unsigned long count, uint32_t multiplier) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) + /* XMM0 - zero */ + /* XMM1 - col1 */ + /* XMM3 - multiplier */ + /* XMM4 - divide mask */ + __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %3, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x4, %1\n\t" - "ssse3_convert_rgba_gray8_iter:\n\t" - "movdqa (%0,%2,4), %%xmm3\n\t" - "psrlq $0x3, %%xmm3\n\t" - "pand %%xmm4, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%1,%2)\n\t" - "sub $0x4, %2\n\t" - "jnz ssse3_convert_rgba_gray8_iter\n\t" - : - : "r" (col1), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); + "mov $0x1F1F1F1F, %%eax\n\t" + "movd %%eax, %%xmm4\n\t" + "pshufd $0x0, %%xmm4, %%xmm4\n\t" + "mov %3, %%eax\n\t" + "movd %%eax, %%xmm3\n\t" + "pshufd $0x0, %%xmm3, %%xmm3\n\t" + "pxor %%xmm0, %%xmm0\n\t" + "sub $0x10, %0\n\t" + "sub $0x4, %1\n\t" + "ssse3_convert_rgb32_gray8_iter:\n\t" + "movdqa (%0,%2,4), %%xmm1\n\t" + "psrlq $0x3, %%xmm1\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pmaddubsw %%xmm3, %%xmm1\n\t" + "phaddw %%xmm0, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" + "movd %%xmm1, %%eax\n\t" + "movnti %%eax, (%1,%2)\n\t" + "sub $0x4, %2\n\t" + "jnz ssse3_convert_rgb32_gray8_iter\n\t" + : + : "r" (col1), "r" (result), "r" (count), "g" (multiplier) + : "%eax", "%xmm0", "%xmm1", "%xmm3", "%xmm4", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif } +/* RGBA to grayscale SSSE3 */ +void ssse3_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { + ssse3_convert_rgb32_gray8(col1, result, count, 0x00010502); +} + +/* BGRA to grayscale SSSE3 */ +void ssse3_convert_bgra_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { + ssse3_convert_rgb32_gray8(col1, result, count, 0x00020501); +} + +/* ARGB to grayscale SSSE3 */ +void ssse3_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { + ssse3_convert_rgb32_gray8(col1, result, count, 0x01050200); +} + +/* ABGR to grayscale SSSE3 */ +void ssse3_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { + ssse3_convert_rgb32_gray8(col1, result, count, 0x02050100); +} + /* Converts a YUYV image into grayscale by extracting the Y channel */ #if defined(__i386__) || defined(__x86_64__) __attribute__((noinline,__target__("ssse3"))) @@ -4205,10 +4593,10 @@ __attribute__((noinline,__target__("ssse3"))) void ssse3_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) unsigned long i = 0; - + __attribute__((aligned(16))) static const uint8_t movemask1[16] = {0,2,4,6,8,10,12,14,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; __attribute__((aligned(16))) static const uint8_t movemask2[16] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0,2,4,6,8,10,12,14}; - + /* XMM0 - General purpose */ /* XMM1 - General purpose */ /* XMM2 - unused */ @@ -4219,28 +4607,28 @@ void ssse3_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned lon /* XMM7 - unused */ __asm__ __volatile__ ( - "movdqa %4, %%xmm3\n\t" - "movdqa %5, %%xmm4\n\t" - "algo_ssse3_convert_yuyv_gray8:\n\t" - "movdqa (%0), %%xmm0\n\t" - "pshufb %%xmm3, %%xmm0\n\t" - "movdqa 0x10(%0), %%xmm1\n\t" - "pshufb %%xmm4, %%xmm1\n\t" - "por %%xmm1, %%xmm0\n\t" - "movntdq %%xmm0, (%1)\n\t" - "add $0x10, %3\n\t" - "add $0x10, %1\n\t" - "add $0x20, %0\n\t" - "cmp %2, %3\n\t" - "jb algo_ssse3_convert_yuyv_gray8\n\t" - : + "movdqa %4, %%xmm3\n\t" + "movdqa %5, %%xmm4\n\t" + "algo_ssse3_convert_yuyv_gray8:\n\t" + "movdqa (%0), %%xmm0\n\t" + "pshufb %%xmm3, %%xmm0\n\t" + "movdqa 0x10(%0), %%xmm1\n\t" + "pshufb %%xmm4, %%xmm1\n\t" + "por %%xmm1, %%xmm0\n\t" + "movntdq %%xmm0, (%1)\n\t" + "add $0x10, %3\n\t" + "add $0x10, %1\n\t" + "add $0x20, %0\n\t" + "cmp %2, %3\n\t" + "jb algo_ssse3_convert_yuyv_gray8\n\t" + : #if (defined(_DEBUG) && !defined(__x86_64__)) /* Use one less register to allow compilation to success on 32bit with omit frame pointer disabled */ - : "r" (col1), "r" (result), "m" (count), "r" (i), "m" (*movemask1), "m" (*movemask2) + : "r" (col1), "r" (result), "m" (count), "r" (i), "m" (*movemask1), "m" (*movemask2) #else - : "r" (col1), "r" (result), "r" (count), "r" (i), "m" (*movemask1), "m" (*movemask2) + : "r" (col1), "r" (result), "r" (count), "r" (i), "m" (*movemask1), "m" (*movemask2) #endif - : "%xmm3", "%xmm4", "cc", "memory" - ); + : "%xmm3", "%xmm4", "cc", "memory" + ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif @@ -4259,11 +4647,11 @@ __attribute__((noinline)) void zm_convert_yuyv_rgb(const uint8_t* col1, uint8_t* r = y1 + r_v_table[v]; g = y1 - (g_u_table[u]+g_v_table[v]); b = y1 + b_u_table[u]; - + result[0] = r<0?0:(r>255?255:r); result[1] = g<0?0:(g>255?255:g); result[2] = b<0?0:(b>255?255:b); - + r = y2 + r_v_table[v]; g = y2 - (g_u_table[u]+g_v_table[v]); b = y2 + b_u_table[u]; @@ -4272,7 +4660,7 @@ __attribute__((noinline)) void zm_convert_yuyv_rgb(const uint8_t* col1, uint8_t* result[4] = g<0?0:(g>255?255:g); result[5] = b<0?0:(b>255?255:b); } - + } /* YUYV to RGBA - modified the one above */ @@ -4288,11 +4676,11 @@ __attribute__((noinline)) void zm_convert_yuyv_rgba(const uint8_t* col1, uint8_t r = y1 + r_v_table[v]; g = y1 - (g_u_table[u]+g_v_table[v]); b = y1 + b_u_table[u]; - + result[0] = r<0?0:(r>255?255:r); result[1] = g<0?0:(g>255?255:g); result[2] = b<0?0:(b>255?255:b); - + r = y2 + r_v_table[v]; g = y2 - (g_u_table[u]+g_v_table[v]); b = y2 + b_u_table[u]; @@ -4301,7 +4689,7 @@ __attribute__((noinline)) void zm_convert_yuyv_rgba(const uint8_t* col1, uint8_t result[5] = g<0?0:(g>255?255:g); result[6] = b<0?0:(b>255?255:b); } - + } /* RGB555 to RGB24 - relocated from zm_local_camera.cpp */ @@ -4388,9 +4776,9 @@ __attribute__((noinline)) void std_deinterlace_4field_gray8(uint8_t* col1, uint8 pabove += width; pnabove += width; pbelow += width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + width; while(pcurrent < max_ptr2) { @@ -4447,29 +4835,29 @@ __attribute__((noinline)) void std_deinterlace_4field_rgb(uint8_t* col1, uint8_t pabove += row_width; pnabove += row_width; pbelow += row_width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + row_width; while(pcurrent < max_ptr2) { - r = abs(pnabove[0] - pabove[0]); - g = abs(pnabove[1] - pabove[1]); - b = abs(pnabove[2] - pabove[2]); - delta1 = (r + r + b + g + g + g + g + g)>>3; - r = abs(pncurrent[0] - pcurrent[0]); - g = abs(pncurrent[1] - pcurrent[1]); - b = abs(pncurrent[2] - pcurrent[2]); - delta2 = (r + r + b + g + g + g + g + g)>>3; - if(((delta1 + delta2) >> 1) >= threshold) { - pcurrent[0] = pabove[0]; - pcurrent[1] = pabove[1]; - pcurrent[2] = pabove[2]; - } - pabove += 3; - pnabove += 3; - pcurrent += 3; - pncurrent += 3; + r = abs(pnabove[0] - pabove[0]); + g = abs(pnabove[1] - pabove[1]); + b = abs(pnabove[2] - pabove[2]); + delta1 = (r + r + b + g + g + g + g + g)>>3; + r = abs(pncurrent[0] - pcurrent[0]); + g = abs(pncurrent[1] - pcurrent[1]); + b = abs(pncurrent[2] - pcurrent[2]); + delta2 = (r + r + b + g + g + g + g + g)>>3; + if(((delta1 + delta2) >> 1) >= threshold) { + pcurrent[0] = pabove[0]; + pcurrent[1] = pabove[1]; + pcurrent[2] = pabove[2]; + } + pabove += 3; + pnabove += 3; + pcurrent += 3; + pncurrent += 3; } } @@ -4516,29 +4904,29 @@ __attribute__((noinline)) void std_deinterlace_4field_bgr(uint8_t* col1, uint8_t pabove += row_width; pnabove += row_width; pbelow += row_width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + row_width; while(pcurrent < max_ptr2) { - b = abs(pnabove[0] - pabove[0]); - g = abs(pnabove[1] - pabove[1]); - r = abs(pnabove[2] - pabove[2]); - delta1 = (r + r + b + g + g + g + g + g)>>3; - b = abs(pncurrent[0] - pcurrent[0]); - g = abs(pncurrent[1] - pcurrent[1]); - r = abs(pncurrent[2] - pcurrent[2]); - delta2 = (r + r + b + g + g + g + g + g)>>3; - if(((delta1 + delta2) >> 1) >= threshold) { - pcurrent[0] = pabove[0]; - pcurrent[1] = pabove[1]; - pcurrent[2] = pabove[2]; - } - pabove += 3; - pnabove += 3; - pcurrent += 3; - pncurrent += 3; + b = abs(pnabove[0] - pabove[0]); + g = abs(pnabove[1] - pabove[1]); + r = abs(pnabove[2] - pabove[2]); + delta1 = (r + r + b + g + g + g + g + g)>>3; + b = abs(pncurrent[0] - pcurrent[0]); + g = abs(pncurrent[1] - pcurrent[1]); + r = abs(pncurrent[2] - pcurrent[2]); + delta2 = (r + r + b + g + g + g + g + g)>>3; + if(((delta1 + delta2) >> 1) >= threshold) { + pcurrent[0] = pabove[0]; + pcurrent[1] = pabove[1]; + pcurrent[2] = pabove[2]; + } + pabove += 3; + pnabove += 3; + pcurrent += 3; + pncurrent += 3; } } @@ -4585,29 +4973,29 @@ __attribute__((noinline)) void std_deinterlace_4field_rgba(uint8_t* col1, uint8_ pabove += row_width; pnabove += row_width; pbelow += row_width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + row_width; while(pcurrent < max_ptr2) { - r = abs(pnabove[0] - pabove[0]); - g = abs(pnabove[1] - pabove[1]); - b = abs(pnabove[2] - pabove[2]); - delta1 = (r + r + b + g + g + g + g + g)>>3; - r = abs(pncurrent[0] - pcurrent[0]); - g = abs(pncurrent[1] - pcurrent[1]); - b = abs(pncurrent[2] - pcurrent[2]); - delta2 = (r + r + b + g + g + g + g + g)>>3; - if(((delta1 + delta2) >> 1) >= threshold) { - pcurrent[0] = pabove[0]; - pcurrent[1] = pabove[1]; - pcurrent[2] = pabove[2]; - } - pabove += 4; - pnabove += 4; - pcurrent += 4; - pncurrent += 4; + r = abs(pnabove[0] - pabove[0]); + g = abs(pnabove[1] - pabove[1]); + b = abs(pnabove[2] - pabove[2]); + delta1 = (r + r + b + g + g + g + g + g)>>3; + r = abs(pncurrent[0] - pcurrent[0]); + g = abs(pncurrent[1] - pcurrent[1]); + b = abs(pncurrent[2] - pcurrent[2]); + delta2 = (r + r + b + g + g + g + g + g)>>3; + if(((delta1 + delta2) >> 1) >= threshold) { + pcurrent[0] = pabove[0]; + pcurrent[1] = pabove[1]; + pcurrent[2] = pabove[2]; + } + pabove += 4; + pnabove += 4; + pcurrent += 4; + pncurrent += 4; } } @@ -4654,29 +5042,29 @@ __attribute__((noinline)) void std_deinterlace_4field_bgra(uint8_t* col1, uint8_ pabove += row_width; pnabove += row_width; pbelow += row_width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + row_width; while(pcurrent < max_ptr2) { - b = abs(pnabove[0] - pabove[0]); - g = abs(pnabove[1] - pabove[1]); - r = abs(pnabove[2] - pabove[2]); - delta1 = (r + r + b + g + g + g + g + g)>>3; - b = abs(pncurrent[0] - pcurrent[0]); - g = abs(pncurrent[1] - pcurrent[1]); - r = abs(pncurrent[2] - pcurrent[2]); - delta2 = (r + r + b + g + g + g + g + g)>>3; - if(((delta1 + delta2) >> 1) >= threshold) { - pcurrent[0] = pabove[0]; - pcurrent[1] = pabove[1]; - pcurrent[2] = pabove[2]; - } - pabove += 4; - pnabove += 4; - pcurrent += 4; - pncurrent += 4; + b = abs(pnabove[0] - pabove[0]); + g = abs(pnabove[1] - pabove[1]); + r = abs(pnabove[2] - pabove[2]); + delta1 = (r + r + b + g + g + g + g + g)>>3; + b = abs(pncurrent[0] - pcurrent[0]); + g = abs(pncurrent[1] - pcurrent[1]); + r = abs(pncurrent[2] - pcurrent[2]); + delta2 = (r + r + b + g + g + g + g + g)>>3; + if(((delta1 + delta2) >> 1) >= threshold) { + pcurrent[0] = pabove[0]; + pcurrent[1] = pabove[1]; + pcurrent[2] = pabove[2]; + } + pabove += 4; + pnabove += 4; + pcurrent += 4; + pncurrent += 4; } } @@ -4723,29 +5111,29 @@ __attribute__((noinline)) void std_deinterlace_4field_argb(uint8_t* col1, uint8_ pabove += row_width; pnabove += row_width; pbelow += row_width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + row_width; while(pcurrent < max_ptr2) { - r = abs(pnabove[1] - pabove[1]); - g = abs(pnabove[2] - pabove[2]); - b = abs(pnabove[3] - pabove[3]); - delta1 = (r + r + b + g + g + g + g + g)>>3; - r = abs(pncurrent[1] - pcurrent[1]); - g = abs(pncurrent[2] - pcurrent[2]); - b = abs(pncurrent[3] - pcurrent[3]); - delta2 = (r + r + b + g + g + g + g + g)>>3; - if(((delta1 + delta2) >> 1) >= threshold) { - pcurrent[1] = pabove[1]; - pcurrent[2] = pabove[2]; - pcurrent[3] = pabove[3]; - } - pabove += 4; - pnabove += 4; - pcurrent += 4; - pncurrent += 4; + r = abs(pnabove[1] - pabove[1]); + g = abs(pnabove[2] - pabove[2]); + b = abs(pnabove[3] - pabove[3]); + delta1 = (r + r + b + g + g + g + g + g)>>3; + r = abs(pncurrent[1] - pcurrent[1]); + g = abs(pncurrent[2] - pcurrent[2]); + b = abs(pncurrent[3] - pcurrent[3]); + delta2 = (r + r + b + g + g + g + g + g)>>3; + if(((delta1 + delta2) >> 1) >= threshold) { + pcurrent[1] = pabove[1]; + pcurrent[2] = pabove[2]; + pcurrent[3] = pabove[3]; + } + pabove += 4; + pnabove += 4; + pcurrent += 4; + pncurrent += 4; } } @@ -4792,896 +5180,28 @@ __attribute__((noinline)) void std_deinterlace_4field_abgr(uint8_t* col1, uint8_ pabove += row_width; pnabove += row_width; pbelow += row_width; - + } - + /* Special case for the last line */ max_ptr2 = pcurrent + row_width; while(pcurrent < max_ptr2) { - b = abs(pnabove[1] - pabove[1]); - g = abs(pnabove[2] - pabove[2]); - r = abs(pnabove[3] - pabove[3]); - delta1 = (r + r + b + g + g + g + g + g)>>3; - b = abs(pncurrent[1] - pcurrent[1]); - g = abs(pncurrent[2] - pcurrent[2]); - r = abs(pncurrent[3] - pcurrent[3]); - delta2 = (r + r + b + g + g + g + g + g)>>3; - if(((delta1 + delta2) >> 1) >= threshold) { - pcurrent[1] = pabove[1]; - pcurrent[2] = pabove[2]; - pcurrent[3] = pabove[3]; - } - pabove += 4; - pnabove += 4; - pcurrent += 4; - pncurrent += 4; + b = abs(pnabove[1] - pabove[1]); + g = abs(pnabove[2] - pabove[2]); + r = abs(pnabove[3] - pabove[3]); + delta1 = (r + r + b + g + g + g + g + g)>>3; + b = abs(pncurrent[1] - pcurrent[1]); + g = abs(pncurrent[2] - pcurrent[2]); + r = abs(pncurrent[3] - pcurrent[3]); + delta2 = (r + r + b + g + g + g + g + g)>>3; + if(((delta1 + delta2) >> 1) >= threshold) { + pcurrent[1] = pabove[1]; + pcurrent[2] = pabove[2]; + pcurrent[3] = pabove[3]; + } + pabove += 4; + pnabove += 4; + pcurrent += 4; + pncurrent += 4; } } - -/* Grayscale SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_gray8(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { - -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - union { - uint32_t int32; - uint8_t int8a[4]; - } threshold_mask; - threshold_mask.int8a[0] = threshold; - threshold_mask.int8a[1] = 0; - threshold_mask.int8a[2] = threshold; - threshold_mask.int8a[3] = 0; - - unsigned long row_width = width; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - /* Load the threshold */ - "mov %5, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_gray8:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "pmaxub %%xmm2, %%xmm1\n\t" - "pminub %%xmm5, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "pmaxub %%xmm2, %%xmm1\n\t" - "pminub %%xmm6, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - "movdqa %%xmm1, %%xmm2\n\t" - - /* Do the comparison on words instead of bytes because we don't have unsigned comparison */ - "punpcklbw %%xmm0, %%xmm1\n\t" // Expand pixels 0-7 into words into xmm1 - "punpckhbw %%xmm0, %%xmm2\n\t" // Expand pixels 8-15 into words into xmm2 - "pcmpgtw %%xmm4, %%xmm1\n\t" // Compare average delta with threshold for pixels 0-7 - "pcmpgtw %%xmm4, %%xmm2\n\t" // Compare average delta with threshold for pixels 8-15 - "packsswb %%xmm2, %%xmm1\n\t" // Pack the comparison results into xmm1 - - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_gray8\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_gray8\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "pmaxub %%xmm2, %%xmm1\n\t" - "pminub %%xmm5, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "pmaxub %%xmm2, %%xmm1\n\t" - "pminub %%xmm6, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - "movdqa %%xmm1, %%xmm2\n\t" - - /* Do the comparison on words instead of bytes because we don't have unsigned comparison */ - "punpcklbw %%xmm0, %%xmm1\n\t" // Expand pixels 0-7 into words into xmm1 - "punpckhbw %%xmm0, %%xmm2\n\t" // Expand pixels 8-15 into words into xmm2 - "pcmpgtw %%xmm4, %%xmm1\n\t" // Compare average delta with threshold for pixels 0-7 - "pcmpgtw %%xmm4, %%xmm2\n\t" // Compare average delta with threshold for pixels 8-15 - "packsswb %%xmm2, %%xmm1\n\t" // Pack the comparison results into xmm1 - - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_mask.int32) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} - -/* RGBA SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_rgba(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - __attribute__((aligned(16))) static const uint8_t movemask2[16] = {1,1,1,1,1,0,0,2,9,9,9,9,9,8,8,10}; - - const uint32_t threshold_val = threshold; - - unsigned long row_width = width*4; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "movdqa %6, %%xmm3\n\t" - "mov %5, %%eax\n\t" -#if defined(__x86_64__) - "movd %%eax, %%xmm8\n\t" - "pshufd $0x0, %%xmm8, %%xmm8\n\t" -#endif - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_rgba:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_rgba\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_rgba\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_val), "m" (*movemask2) -#if defined(__x86_64__) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "cc", "memory" -#else - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" -#endif - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} - -/* BGRA SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_bgra(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - __attribute__((aligned(16))) static const uint8_t movemask2[16] = {1,1,1,1,1,2,2,0,9,9,9,9,9,10,10,8}; - - const uint32_t threshold_val = threshold; - - unsigned long row_width = width*4; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "movdqa %6, %%xmm3\n\t" - "mov %5, %%eax\n\t" -#if defined(__x86_64__) - "movd %%eax, %%xmm8\n\t" - "pshufd $0x0, %%xmm8, %%xmm8\n\t" -#endif - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_bgra:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_bgra\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_bgra\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_val), "m" (*movemask2) -#if defined(__x86_64__) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "cc", "memory" -#else - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" -#endif - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} - -/* ARGB SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_argb(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - __attribute__((aligned(16))) static const uint8_t movemask2[16] = {2,2,2,2,2,1,1,3,10,10,10,10,10,9,9,11}; - - const uint32_t threshold_val = threshold; - - unsigned long row_width = width*4; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "movdqa %6, %%xmm3\n\t" - "mov %5, %%eax\n\t" -#if defined(__x86_64__) - "movd %%eax, %%xmm8\n\t" - "pshufd $0x0, %%xmm8, %%xmm8\n\t" -#endif - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_argb:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_argb\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_argb\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_val), "m" (*movemask2) -#if defined(__x86_64__) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "cc", "memory" -#else - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" -#endif - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} - -/* ABGR SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_abgr(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - __attribute__((aligned(16))) static const uint8_t movemask2[16] = {2,2,2,2,2,3,3,1,10,10,10,10,10,11,11,9}; - - const uint32_t threshold_val = threshold; - - unsigned long row_width = width*4; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "movdqa %6, %%xmm3\n\t" - "mov %5, %%eax\n\t" -#if defined(__x86_64__) - "movd %%eax, %%xmm8\n\t" - "pshufd $0x0, %%xmm8, %%xmm8\n\t" -#endif - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_abgr:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_abgr\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_abgr\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_val), "m" (*movemask2) -#if defined(__x86_64__) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "cc", "memory" -#else - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" -#endif - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} diff --git a/src/zm_image.h b/src/zm_image.h index 016a64a3c..1982d4232 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_IMAGE_H @@ -54,7 +54,7 @@ extern imgbufcpy_fptr_t fptr_imgbufcpy; /* Should be called from Image class functions */ inline static uint8_t* AllocBuffer(size_t p_bufsize) { - uint8_t* buffer = (uint8_t*)zm_mallocaligned(16,p_bufsize); + uint8_t* buffer = (uint8_t*)zm_mallocaligned(64,p_bufsize); if(buffer == NULL) Fatal("Memory allocation failed: %s",strerror(errno)); @@ -122,8 +122,8 @@ protected: } public: - enum { CHAR_HEIGHT=11, CHAR_WIDTH=6 }; - enum { LINE_HEIGHT=CHAR_HEIGHT+0 }; + enum { ZM_CHAR_HEIGHT=11, ZM_CHAR_WIDTH=6 }; + enum { LINE_HEIGHT=ZM_CHAR_HEIGHT+0 }; protected: static bool initialised; @@ -131,11 +131,12 @@ protected: static unsigned char *y_r_table; static unsigned char *y_g_table; static unsigned char *y_b_table; - static jpeg_compress_struct *jpg_ccinfo[101]; - static jpeg_decompress_struct *jpg_dcinfo; + static jpeg_compress_struct *writejpg_ccinfo[101]; + static jpeg_compress_struct *encodejpg_ccinfo[101]; + static jpeg_decompress_struct *readjpg_dcinfo; + static jpeg_decompress_struct *decodejpg_dcinfo; static struct zm_error_mgr jpg_err; -protected: unsigned int width; unsigned int height; unsigned int pixels; @@ -148,8 +149,6 @@ protected: int holdbuffer; /* Hold the buffer instead of replacing it with new one */ char text[1024]; -protected: - static void Initialise(); public: Image(); @@ -157,6 +156,8 @@ public: Image( int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer=0); Image( const Image &p_image ); ~Image(); + static void Initialise(); + static void Deinitialise(); inline unsigned int Width() const { return( width ); } inline unsigned int Height() const { return( height ); } @@ -263,6 +264,8 @@ public: /* Blend functions */ void sse2_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); void std_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); +void neon32_armv7_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); +void neon64_armv8_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); void std_blend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); /* Delta functions */ @@ -273,6 +276,16 @@ void std_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, void std_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void std_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void std_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon64_armv8_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon64_armv8_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon64_armv8_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon64_armv8_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon64_armv8_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void sse2_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void sse2_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void sse2_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); @@ -292,6 +305,9 @@ void std_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long void std_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); void std_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); void ssse3_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); +void ssse3_convert_bgra_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); +void ssse3_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); +void ssse3_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); void ssse3_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); void zm_convert_yuyv_rgb(const uint8_t* col1, uint8_t* result, unsigned long count); void zm_convert_yuyv_rgba(const uint8_t* col1, uint8_t* result, unsigned long count); @@ -308,8 +324,3 @@ void std_deinterlace_4field_rgba(uint8_t* col1, uint8_t* col2, unsigned int thre void std_deinterlace_4field_bgra(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); void std_deinterlace_4field_argb(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); void std_deinterlace_4field_abgr(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_gray8(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_rgba(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_bgra(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_argb(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_abgr(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); diff --git a/src/zm_jpeg.cpp b/src/zm_jpeg.cpp index c47bbe267..ba0085c6b 100644 --- a/src/zm_jpeg.cpp +++ b/src/zm_jpeg.cpp @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "zm_jpeg.h" diff --git a/src/zm_jpeg.h b/src/zm_jpeg.h index b7807d0d3..d0348dd3e 100644 --- a/src/zm_jpeg.h +++ b/src/zm_jpeg.h @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include @@ -29,7 +29,7 @@ extern "C" { -/* Stuff for overriden error handlers */ +/* Stuff for overridden error handlers */ struct zm_error_mgr { struct jpeg_error_mgr pub; diff --git a/src/zm_libvlc_camera.cpp b/src/zm_libvlc_camera.cpp index 26c4701fb..a4135d352 100644 --- a/src/zm_libvlc_camera.cpp +++ b/src/zm_libvlc_camera.cpp @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "zm.h" @@ -27,11 +27,11 @@ void* LibvlcLockBuffer(void* opaque, void** planes) { LibvlcPrivateData* data = (LibvlcPrivateData*)opaque; data->mutex.lock(); - + uint8_t* buffer = data->buffer; data->buffer = data->prevBuffer; data->prevBuffer = buffer; - + *planes = data->buffer; return NULL; } @@ -39,7 +39,7 @@ void* LibvlcLockBuffer(void* opaque, void** planes) void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) { LibvlcPrivateData* data = (LibvlcPrivateData*)opaque; - + bool newFrame = false; for(uint32_t i = 0; i < data->bufferSize; i++) { @@ -50,7 +50,7 @@ void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) } } data->mutex.unlock(); - + time_t now; time(&now); // Return frames slightly faster than 1fps (if time() supports greater than one second resolution) @@ -61,8 +61,8 @@ void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) } } -LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - Camera( p_id, LIBVLC_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), +LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + Camera( p_id, LIBVLC_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), mPath( p_path ), mMethod( p_method ), mOptions( p_options ) @@ -89,7 +89,7 @@ LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::stri } else { Panic("Unexpected colours: %d",colours); } - + if ( capture ) { Initialise(); @@ -143,9 +143,9 @@ void LibvlcCamera::Terminate() int LibvlcCamera::PrimeCapture() { Info("Priming capture from %s", mPath.c_str()); - + StringVector opVect = split(Options(), ","); - + // Set transport method as specified by method field, rtpUni is default if ( Method() == "rtpMulti" ) opVect.push_back("--rtsp-mcast"); @@ -168,11 +168,11 @@ int LibvlcCamera::PrimeCapture() mLibvlcInstance = libvlc_new (opVect.size(), (const char* const*)mOptArgV); if(mLibvlcInstance == NULL) Fatal("Unable to create libvlc instance due to: %s", libvlc_errmsg()); - + mLibvlcMedia = libvlc_media_new_location(mLibvlcInstance, mPath.c_str()); if(mLibvlcMedia == NULL) Fatal("Unable to open input %s due to: %s", mPath.c_str(), libvlc_errmsg()); - + mLibvlcMediaPlayer = libvlc_media_player_new_from_media(mLibvlcMedia); if(mLibvlcMediaPlayer == NULL) Fatal("Unable to create player for %s due to: %s", mPath.c_str(), libvlc_errmsg()); @@ -182,18 +182,18 @@ int LibvlcCamera::PrimeCapture() mLibvlcData.bufferSize = width * height * mBpp; // Libvlc wants 32 byte alignment for images (should in theory do this for all image lines) - mLibvlcData.buffer = (uint8_t*)zm_mallocaligned(32, mLibvlcData.bufferSize); - mLibvlcData.prevBuffer = (uint8_t*)zm_mallocaligned(32, mLibvlcData.bufferSize); + mLibvlcData.buffer = (uint8_t*)zm_mallocaligned(64, mLibvlcData.bufferSize); + mLibvlcData.prevBuffer = (uint8_t*)zm_mallocaligned(64, mLibvlcData.bufferSize); mLibvlcData.newImage.setValueImmediate(false); libvlc_media_player_play(mLibvlcMediaPlayer); - + return(0); } int LibvlcCamera::PreCapture() -{ +{ return(0); } @@ -207,7 +207,21 @@ int LibvlcCamera::Capture( Image &image ) image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); mLibvlcData.newImage.setValueImmediate(false); mLibvlcData.mutex.unlock(); - + + return (0); +} + +// Should not return -1 as cancels capture. Always wait for image if available. +int LibvlcCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory) +{ + while(!mLibvlcData.newImage.getValueImmediate()) + mLibvlcData.newImage.getUpdatedValue(1); + + mLibvlcData.mutex.lock(); + image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); + mLibvlcData.newImage.setValueImmediate(false); + mLibvlcData.mutex.unlock(); + return (0); } diff --git a/src/zm_libvlc_camera.h b/src/zm_libvlc_camera.h index d08b310f5..4221bd0b7 100644 --- a/src/zm_libvlc_camera.h +++ b/src/zm_libvlc_camera.h @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef ZM_LIBVLC_CAMERA_H @@ -57,7 +57,7 @@ protected: libvlc_media_player_t *mLibvlcMediaPlayer; public: - LibvlcCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + LibvlcCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~LibvlcCamera(); const std::string &Path() const { return( mPath ); } @@ -70,6 +70,7 @@ public: int PrimeCapture(); int PreCapture(); int Capture( Image &image ); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); int PostCapture(); }; diff --git a/src/zm_local_camera.cpp b/src/zm_local_camera.cpp index 8df65d8b5..f13733b11 100644 --- a/src/zm_local_camera.cpp +++ b/src/zm_local_camera.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" @@ -55,7 +55,7 @@ static int vidioctl( int fd, int request, void *arg ) static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette ) { _AVPIXELFORMAT pixFormat = AV_PIX_FMT_NONE; - + #if ZM_HAS_V4L2 if ( v4l_version == 2 ) { @@ -114,27 +114,27 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette case V4L2_PIX_FMT_UYVY : pixFormat = AV_PIX_FMT_UYVY422; break; - // These don't seem to have ffmpeg equivalents - // See if you can match any of the ones in the default clause below!? + // These don't seem to have ffmpeg equivalents + // See if you can match any of the ones in the default clause below!? case V4L2_PIX_FMT_RGB332 : case V4L2_PIX_FMT_RGB555X : case V4L2_PIX_FMT_RGB565X : - //case V4L2_PIX_FMT_Y16 : - //case V4L2_PIX_FMT_PAL8 : + //case V4L2_PIX_FMT_Y16 : + //case V4L2_PIX_FMT_PAL8 : case V4L2_PIX_FMT_YVU410 : case V4L2_PIX_FMT_YVU420 : case V4L2_PIX_FMT_Y41P : - //case V4L2_PIX_FMT_YUV555 : - //case V4L2_PIX_FMT_YUV565 : - //case V4L2_PIX_FMT_YUV32 : + //case V4L2_PIX_FMT_YUV555 : + //case V4L2_PIX_FMT_YUV565 : + //case V4L2_PIX_FMT_YUV32 : case V4L2_PIX_FMT_NV12 : case V4L2_PIX_FMT_NV21 : case V4L2_PIX_FMT_YYUV : case V4L2_PIX_FMT_HI240 : case V4L2_PIX_FMT_HM12 : - //case V4L2_PIX_FMT_SBGGR8 : - //case V4L2_PIX_FMT_SGBRG8 : - //case V4L2_PIX_FMT_SBGGR16 : + //case V4L2_PIX_FMT_SBGGR8 : + //case V4L2_PIX_FMT_SGBRG8 : + //case V4L2_PIX_FMT_SBGGR16 : case V4L2_PIX_FMT_DV : case V4L2_PIX_FMT_MPEG : case V4L2_PIX_FMT_WNVA : @@ -142,43 +142,43 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette case V4L2_PIX_FMT_PWC1 : case V4L2_PIX_FMT_PWC2 : case V4L2_PIX_FMT_ET61X251 : - //case V4L2_PIX_FMT_SPCA501 : - //case V4L2_PIX_FMT_SPCA505 : - //case V4L2_PIX_FMT_SPCA508 : - //case V4L2_PIX_FMT_SPCA561 : - //case V4L2_PIX_FMT_PAC207 : - //case V4L2_PIX_FMT_PJPG : - //case V4L2_PIX_FMT_YVYU : + //case V4L2_PIX_FMT_SPCA501 : + //case V4L2_PIX_FMT_SPCA505 : + //case V4L2_PIX_FMT_SPCA508 : + //case V4L2_PIX_FMT_SPCA561 : + //case V4L2_PIX_FMT_PAC207 : + //case V4L2_PIX_FMT_PJPG : + //case V4L2_PIX_FMT_YVYU : default : - { - Fatal( "Can't find swscale format for palette %d", palette ); - break; - // These are all spare and may match some of the above - pixFormat = AV_PIX_FMT_YUVJ420P; - pixFormat = AV_PIX_FMT_YUVJ422P; - pixFormat = AV_PIX_FMT_UYVY422; - pixFormat = AV_PIX_FMT_UYYVYY411; - pixFormat = AV_PIX_FMT_BGR565; - pixFormat = AV_PIX_FMT_BGR555; - pixFormat = AV_PIX_FMT_BGR8; - pixFormat = AV_PIX_FMT_BGR4; - pixFormat = AV_PIX_FMT_BGR4_BYTE; - pixFormat = AV_PIX_FMT_RGB8; - pixFormat = AV_PIX_FMT_RGB4; - pixFormat = AV_PIX_FMT_RGB4_BYTE; - pixFormat = AV_PIX_FMT_NV12; - pixFormat = AV_PIX_FMT_NV21; - pixFormat = AV_PIX_FMT_RGB32_1; - pixFormat = AV_PIX_FMT_BGR32_1; - pixFormat = AV_PIX_FMT_GRAY16BE; - pixFormat = AV_PIX_FMT_GRAY16LE; - pixFormat = AV_PIX_FMT_YUV440P; - pixFormat = AV_PIX_FMT_YUVJ440P; - pixFormat = AV_PIX_FMT_YUVA420P; - //pixFormat = AV_PIX_FMT_VDPAU_H264; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; - } + { + Fatal( "Can't find swscale format for palette %d", palette ); + break; + // These are all spare and may match some of the above + pixFormat = AV_PIX_FMT_YUVJ420P; + pixFormat = AV_PIX_FMT_YUVJ422P; + pixFormat = AV_PIX_FMT_UYVY422; + pixFormat = AV_PIX_FMT_UYYVYY411; + pixFormat = AV_PIX_FMT_BGR565; + pixFormat = AV_PIX_FMT_BGR555; + pixFormat = AV_PIX_FMT_BGR8; + pixFormat = AV_PIX_FMT_BGR4; + pixFormat = AV_PIX_FMT_BGR4_BYTE; + pixFormat = AV_PIX_FMT_RGB8; + pixFormat = AV_PIX_FMT_RGB4; + pixFormat = AV_PIX_FMT_RGB4_BYTE; + pixFormat = AV_PIX_FMT_NV12; + pixFormat = AV_PIX_FMT_NV21; + pixFormat = AV_PIX_FMT_RGB32_1; + pixFormat = AV_PIX_FMT_BGR32_1; + pixFormat = AV_PIX_FMT_GRAY16BE; + pixFormat = AV_PIX_FMT_GRAY16LE; + pixFormat = AV_PIX_FMT_YUV440P; + pixFormat = AV_PIX_FMT_YUVJ440P; + pixFormat = AV_PIX_FMT_YUVA420P; + //pixFormat = AV_PIX_FMT_VDPAU_H264; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; + } } } #endif // ZM_HAS_V4L2 @@ -188,17 +188,17 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette switch( palette ) { case VIDEO_PALETTE_RGB32 : - if(BigEndian) - pixFormat = AV_PIX_FMT_ARGB; - else - pixFormat = AV_PIX_FMT_BGRA; - break; + if(BigEndian) + pixFormat = AV_PIX_FMT_ARGB; + else + pixFormat = AV_PIX_FMT_BGRA; + break; case VIDEO_PALETTE_RGB24 : - if(BigEndian) - pixFormat = AV_PIX_FMT_RGB24; - else - pixFormat = AV_PIX_FMT_BGR24; - break; + if(BigEndian) + pixFormat = AV_PIX_FMT_RGB24; + else + pixFormat = AV_PIX_FMT_BGR24; + break; case VIDEO_PALETTE_GREY : pixFormat = AV_PIX_FMT_GRAY8; break; @@ -219,36 +219,36 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette pixFormat = AV_PIX_FMT_YUV420P; break; default : - { - Fatal( "Can't find swscale format for palette %d", palette ); - break; - // These are all spare and may match some of the above - pixFormat = AV_PIX_FMT_YUVJ420P; - pixFormat = AV_PIX_FMT_YUVJ422P; - pixFormat = AV_PIX_FMT_YUVJ444P; - pixFormat = AV_PIX_FMT_UYVY422; - pixFormat = AV_PIX_FMT_UYYVYY411; - pixFormat = AV_PIX_FMT_BGR565; - pixFormat = AV_PIX_FMT_BGR555; - pixFormat = AV_PIX_FMT_BGR8; - pixFormat = AV_PIX_FMT_BGR4; - pixFormat = AV_PIX_FMT_BGR4_BYTE; - pixFormat = AV_PIX_FMT_RGB8; - pixFormat = AV_PIX_FMT_RGB4; - pixFormat = AV_PIX_FMT_RGB4_BYTE; - pixFormat = AV_PIX_FMT_NV12; - pixFormat = AV_PIX_FMT_NV21; - pixFormat = AV_PIX_FMT_RGB32_1; - pixFormat = AV_PIX_FMT_BGR32_1; - pixFormat = AV_PIX_FMT_GRAY16BE; - pixFormat = AV_PIX_FMT_GRAY16LE; - pixFormat = AV_PIX_FMT_YUV440P; - pixFormat = AV_PIX_FMT_YUVJ440P; - pixFormat = AV_PIX_FMT_YUVA420P; - //pixFormat = AV_PIX_FMT_VDPAU_H264; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; - } + { + Fatal( "Can't find swscale format for palette %d", palette ); + break; + // These are all spare and may match some of the above + pixFormat = AV_PIX_FMT_YUVJ420P; + pixFormat = AV_PIX_FMT_YUVJ422P; + pixFormat = AV_PIX_FMT_YUVJ444P; + pixFormat = AV_PIX_FMT_UYVY422; + pixFormat = AV_PIX_FMT_UYYVYY411; + pixFormat = AV_PIX_FMT_BGR565; + pixFormat = AV_PIX_FMT_BGR555; + pixFormat = AV_PIX_FMT_BGR8; + pixFormat = AV_PIX_FMT_BGR4; + pixFormat = AV_PIX_FMT_BGR4_BYTE; + pixFormat = AV_PIX_FMT_RGB8; + pixFormat = AV_PIX_FMT_RGB4; + pixFormat = AV_PIX_FMT_RGB4_BYTE; + pixFormat = AV_PIX_FMT_NV12; + pixFormat = AV_PIX_FMT_NV21; + pixFormat = AV_PIX_FMT_RGB32_1; + pixFormat = AV_PIX_FMT_BGR32_1; + pixFormat = AV_PIX_FMT_GRAY16BE; + pixFormat = AV_PIX_FMT_GRAY16LE; + pixFormat = AV_PIX_FMT_YUV440P; + pixFormat = AV_PIX_FMT_YUVJ440P; + pixFormat = AV_PIX_FMT_YUVA420P; + //pixFormat = AV_PIX_FMT_VDPAU_H264; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; + } } } #endif // ZM_HAS_V4L1 @@ -286,8 +286,26 @@ AVFrame **LocalCamera::capturePictures = 0; LocalCamera *LocalCamera::last_camera = NULL; -LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, int p_standard, bool p_v4l_multi_buffer, unsigned int p_v4l_captures_per_frame, const std::string &p_method, int p_width, int p_height, int p_colours, int p_palette, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, unsigned int p_extras) : - Camera( p_id, LOCAL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), +LocalCamera::LocalCamera( + int p_id, + const std::string &p_device, + int p_channel, + int p_standard, + bool p_v4l_multi_buffer, + unsigned int p_v4l_captures_per_frame, + const std::string &p_method, + int p_width, + int p_height, + int p_colours, + int p_palette, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio, + unsigned int p_extras) : + Camera( p_id, LOCAL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), device( p_device ), channel( p_channel ), standard( p_standard ), @@ -301,7 +319,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, v4l_version = (p_method=="v4l2"?2:1); v4l_multi_buffer = p_v4l_multi_buffer; v4l_captures_per_frame = p_v4l_captures_per_frame; - + if ( capture ) { if ( device_prime ) @@ -322,9 +340,9 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, // We are the second, or subsequent, input using this channel channel_prime = false; } - + } - + /* The V4L1 API doesn't care about endianness, we need to check the endianness of the machine */ uint32_t checkval = 0xAABBCCDD; if(*(unsigned char*)&checkval == 0xDD) { @@ -337,7 +355,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, Error("Unable to detect the processor's endianness. Assuming little-endian."); BigEndian = 0; } - + #if ZM_HAS_V4L2 if( v4l_version == 2 && palette == 0 ) { /* Use automatic format selection */ @@ -348,27 +366,27 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, palette = V4L2_PIX_FMT_YUYV; } else { if(capture) { - Info("Selected capture palette: %s (%c%c%c%c)", palette_desc, palette&0xff, (palette>>8)&0xff, (palette>>16)&0xff, (palette>>24)&0xff); + Info("Selected capture palette: %s (0x%02hhx%02hhx%02hhx%02hhx)", palette_desc, (palette>>24)&0xff, (palette>>16)&0xff, (palette>>8)&0xff, (palette)&0xff); } } } #endif - + if( capture ) { if ( last_camera ) { if ( (p_method == "v4l2" && v4l_version != 2) || (p_method == "v4l1" && v4l_version != 1) ) Fatal( "Different Video For Linux version used for monitors sharing same device" ); - + if ( standard != last_camera->standard ) Warning( "Different video standards defined for monitors sharing same device, results may be unpredictable or completely wrong" ); - + if ( palette != last_camera->palette ) Warning( "Different video palettes defined for monitors sharing same device, results may be unpredictable or completely wrong" ); - + if ( width != last_camera->width || height != last_camera->height ) Warning( "Different capture sizes defined for monitors sharing same device, results may be unpredictable or completely wrong" ); } - + #if HAVE_LIBSWSCALE /* Get ffmpeg pixel format based on capture palette and endianness */ capturePixFormat = getFfPixFormatFromV4lPalette( v4l_version, palette ); @@ -380,38 +398,39 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, #if ZM_HAS_V4L2 if ( v4l_version == 2 ) { /* Try to find a match for the selected palette and target colourspace */ - + /* RGB32 palette and 32bit target colourspace */ if(palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_RGB32) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_ARGB; - - /* BGR32 palette and 32bit target colourspace */ + + /* BGR32 palette and 32bit target colourspace */ } else if(palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_RGB32) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_BGRA; - - /* RGB24 palette and 24bit target colourspace */ + + /* RGB24 palette and 24bit target colourspace */ } else if(palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_RGB; - - /* BGR24 palette and 24bit target colourspace */ + + /* BGR24 palette and 24bit target colourspace */ } else if(palette == V4L2_PIX_FMT_BGR24 && colours == ZM_COLOUR_RGB24) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_BGR; - - /* Grayscale palette and grayscale target colourspace */ + + /* Grayscale palette and grayscale target colourspace */ } else if(palette == V4L2_PIX_FMT_GREY && colours == ZM_COLOUR_GRAY8) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_NONE; - /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ + /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ } else { if( capture ) #if HAVE_LIBSWSCALE - Info("No direct match for the selected palette (%c%c%c%c) and target colorspace (%d). Format conversion is required, performance penalty expected", (capturePixFormat)&0xff,((capturePixFormat>>8)&0xff),((capturePixFormat>>16)&0xff),((capturePixFormat>>24)&0xff), colours ); + Info("No direct match for the selected palette (0x%02hhx%02hhx%02hhx%02hhx) and target colorspace (%02u). Format conversion is required, performance penalty expected", + (capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff), colours); #else - Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); + Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); #endif #if HAVE_LIBSWSCALE /* Try using swscale for the conversion */ @@ -427,16 +446,18 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, subpixelorder = ZM_SUBPIX_ORDER_NONE; imagePixFormat = AV_PIX_FMT_GRAY8; } else { - Panic("Unexpected colours: %d",colours); + Panic("Unexpected colours: %u",colours); } if( capture ) { #if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0) if(!sws_isSupportedInput(capturePixFormat)) { - Error("swscale does not support the used capture format: %c%c%c%c",(capturePixFormat)&0xff,((capturePixFormat>>8)&0xff),((capturePixFormat>>16)&0xff),((capturePixFormat>>24)&0xff)); + Error("swscale does not support the used capture format: 0x%02hhx%02hhx%02hhx%02hhx", + (capturePixFormat>>24)&0xff,((capturePixFormat>>16)&0xff),((capturePixFormat>>8)&0xff),((capturePixFormat)&0xff)); conversion_type = 2; /* Try ZM format conversions */ } if(!sws_isSupportedOutput(imagePixFormat)) { - Error("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff)); + Error("swscale does not support the target format: 0x%02hhx%02hhx%02hhx%02hhx", + (imagePixFormat>>24)&0xff,((imagePixFormat>>16)&0xff),((imagePixFormat>>8)&0xff),((imagePixFormat)&0xff)); conversion_type = 2; /* Try ZM format conversions */ } #endif @@ -449,13 +470,13 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, if(colours == ZM_COLOUR_GRAY8 && palette == V4L2_PIX_FMT_YUYV) { conversion_type = 2; } - + /* JPEG */ if(palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { Debug(2,"Using JPEG image decoding"); conversion_type = 3; } - + if(conversion_type == 2) { Debug(2,"Using ZM for image conversion"); if(palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_GRAY8) { @@ -500,11 +521,11 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } #endif // ZM_HAS_V4L2 - /* V4L1 format matching */ + /* V4L1 format matching */ #if ZM_HAS_V4L1 if ( v4l_version == 1) { /* Try to find a match for the selected palette and target colourspace */ - + /* RGB32 palette and 32bit target colourspace */ if(palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_RGB32) { conversion_type = 0; @@ -513,8 +534,8 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } else { subpixelorder = ZM_SUBPIX_ORDER_BGRA; } - - /* RGB24 palette and 24bit target colourspace */ + + /* RGB24 palette and 24bit target colourspace */ } else if(palette == VIDEO_PALETTE_RGB24 && colours == ZM_COLOUR_RGB24) { conversion_type = 0; if(BigEndian) { @@ -522,12 +543,12 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } else { subpixelorder = ZM_SUBPIX_ORDER_BGR; } - - /* Grayscale palette and grayscale target colourspace */ + + /* Grayscale palette and grayscale target colourspace */ } else if(palette == VIDEO_PALETTE_GREY && colours == ZM_COLOUR_GRAY8) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_NONE; - /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ + /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ } else { if( capture ) Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); @@ -545,7 +566,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, subpixelorder = ZM_SUBPIX_ORDER_NONE; imagePixFormat = AV_PIX_FMT_GRAY8; } else { - Panic("Unexpected colours: %d",colours); + Panic("Unexpected colours: %u",colours); } if( capture ) { if(!sws_isSupportedInput(capturePixFormat)) { @@ -565,7 +586,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, if(colours == ZM_COLOUR_GRAY8 && (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422)) { conversion_type = 2; } - + if(conversion_type == 2) { Debug(2,"Using ZM for image conversion"); if(palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_GRAY8) { @@ -610,10 +631,10 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } } } -#endif // ZM_HAS_V4L1 +#endif // ZM_HAS_V4L1 last_camera = this; - Debug(3,"Selected subpixelorder: %d",subpixelorder); + Debug(3,"Selected subpixelorder: %u",subpixelorder); #if HAVE_LIBSWSCALE /* Initialize swscale stuff */ @@ -632,15 +653,15 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, int pSize = avpicture_get_size( imagePixFormat, width, height ); #endif if( (unsigned int)pSize != imagesize) { - Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize); + Fatal("Image size mismatch. Required: %d Available: %u",pSize,imagesize); } - + imgConversionContext = sws_getContext(width, height, capturePixFormat, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); - + if ( !imgConversionContext ) { Fatal( "Unable to initialise image scaling context" ); - } - + } + } #endif } @@ -649,18 +670,14 @@ LocalCamera::~LocalCamera() { if ( device_prime && capture ) Terminate(); - + #if HAVE_LIBSWSCALE /* Clean up swscale stuff */ if(capture && conversion_type == 1) { sws_freeContext(imgConversionContext); imgConversionContext = NULL; - -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) + av_frame_free( &tmpPicture ); -#else - av_freep( &tmpPicture ); -#endif } #endif } @@ -726,23 +743,23 @@ void LocalCamera::Initialise() v4l2_data.fmt.fmt.pix.height = height; v4l2_data.fmt.fmt.pix.pixelformat = palette; - if ( (extras & 0xff) != 0 ) - { - v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff); - - if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) + if ( (extras & 0xff) != 0 ) { - Warning( "Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff) ); - v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY; + v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff); + + if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) + { + Warning( "Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff) ); + v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY; + if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) { + Fatal( "Failed to set video format: %s", strerror(errno) ); + } + } + } else { if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) { Fatal( "Failed to set video format: %s", strerror(errno) ); } } - } else { - if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) { - Fatal( "Failed to set video format: %s", strerror(errno) ); - } - } /* Note VIDIOC_S_FMT may change width and height. */ Debug( 4, " v4l2_data.fmt.type = %08x", v4l2_data.fmt.type ); @@ -764,39 +781,39 @@ void LocalCamera::Initialise() if (v4l2_data.fmt.fmt.pix.sizeimage < min) v4l2_data.fmt.fmt.pix.sizeimage = min; - v4l2_jpegcompression jpeg_comp; - if(palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { - if( vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0 ) { - if(errno == EINVAL) { - Debug(2, "JPEG compression options are not available"); - } else { - Warning("Failed to get JPEG compression options: %s", strerror(errno) ); - } - } else { - /* Set flags and quality. MJPEG should not have the huffman tables defined */ - if(palette == V4L2_PIX_FMT_MJPEG) { - jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI; - } else { - jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_DHT; - } - jpeg_comp.quality = 85; - - /* Update the JPEG options */ - if( vidioctl( vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp ) < 0 ) { - Warning("Failed to set JPEG compression options: %s", strerror(errno) ); - } else { - if(vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0) { - Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno) ); + v4l2_jpegcompression jpeg_comp; + if(palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { + if( vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0 ) { + if(errno == EINVAL) { + Debug(2, "JPEG compression options are not available"); } else { - Debug(4, "JPEG quality: %d",jpeg_comp.quality); - Debug(4, "JPEG markers: %#x",jpeg_comp.jpeg_markers); + Warning("Failed to get JPEG compression options: %s", strerror(errno) ); + } + } else { + /* Set flags and quality. MJPEG should not have the huffman tables defined */ + if(palette == V4L2_PIX_FMT_MJPEG) { + jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI; + } else { + jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_DHT; + } + jpeg_comp.quality = 85; + + /* Update the JPEG options */ + if( vidioctl( vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp ) < 0 ) { + Warning("Failed to set JPEG compression options: %s", strerror(errno) ); + } else { + if(vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0) { + Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno) ); + } else { + Debug(4, "JPEG quality: %d",jpeg_comp.quality); + Debug(4, "JPEG markers: %#x",jpeg_comp.jpeg_markers); + } } } } - } Debug( 3, "Setting up request buffers" ); - + memset( &v4l2_data.reqbufs, 0, sizeof(v4l2_data.reqbufs) ); if ( channel_count > 1 ) { Debug( 3, "Channel count is %d", channel_count ); @@ -853,27 +870,27 @@ void LocalCamera::Initialise() v4l2_data.buffers[i].start = mmap( NULL, vid_buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, vid_fd, vid_buf.m.offset ); if ( v4l2_data.buffers[i].start == MAP_FAILED ) - Fatal( "Can't map video buffer %d (%d bytes) to memory: %s(%d)", i, vid_buf.length, strerror(errno), errno ); + Fatal( "Can't map video buffer %u (%u bytes) to memory: %s(%d)", i, vid_buf.length, strerror(errno), errno ); #if HAVE_LIBSWSCALE #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - capturePictures[i] = av_frame_alloc(); + capturePictures[i] = av_frame_alloc(); #else - capturePictures[i] = avcodec_alloc_frame(); + capturePictures[i] = avcodec_alloc_frame(); #endif - if ( !capturePictures[i] ) - Fatal( "Could not allocate picture" ); + if ( !capturePictures[i] ) + Fatal( "Could not allocate picture" ); #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(capturePictures[i]->data, - capturePictures[i]->linesize, - (uint8_t*)v4l2_data.buffers[i].start,capturePixFormat, - v4l2_data.fmt.fmt.pix.width, - v4l2_data.fmt.fmt.pix.height, 1); + av_image_fill_arrays(capturePictures[i]->data, + capturePictures[i]->linesize, + (uint8_t*)v4l2_data.buffers[i].start,capturePixFormat, + v4l2_data.fmt.fmt.pix.width, + v4l2_data.fmt.fmt.pix.height, 1); #else - avpicture_fill( (AVPicture *)capturePictures[i], - (uint8_t*)v4l2_data.buffers[i].start, capturePixFormat, - v4l2_data.fmt.fmt.pix.width, - v4l2_data.fmt.fmt.pix.height ); + avpicture_fill( (AVPicture *)capturePictures[i], + (uint8_t*)v4l2_data.buffers[i].start, capturePixFormat, + v4l2_data.fmt.fmt.pix.width, + v4l2_data.fmt.fmt.pix.height ); #endif #endif // HAVE_LIBSWSCALE } @@ -932,30 +949,30 @@ void LocalCamera::Initialise() switch (vid_pic.palette = palette) { case VIDEO_PALETTE_RGB32 : - { - vid_pic.depth = 32; - break; - } + { + vid_pic.depth = 32; + break; + } case VIDEO_PALETTE_RGB24 : - { - vid_pic.depth = 24; - break; - } + { + vid_pic.depth = 24; + break; + } case VIDEO_PALETTE_GREY : - { - vid_pic.depth = 8; - break; - } + { + vid_pic.depth = 8; + break; + } case VIDEO_PALETTE_RGB565 : case VIDEO_PALETTE_YUYV : case VIDEO_PALETTE_YUV422 : case VIDEO_PALETTE_YUV420P : case VIDEO_PALETTE_YUV422P : default: - { - vid_pic.depth = 16; - break; - } + { + vid_pic.depth = 16; + break; + } } if ( brightness >= 0 ) vid_pic.brightness = brightness; @@ -983,7 +1000,7 @@ void LocalCamera::Initialise() Debug( 4, "Old Y:%d", vid_win.y ); Debug( 4, "Old W:%d", vid_win.width ); Debug( 4, "Old H:%d", vid_win.height ); - + vid_win.x = 0; vid_win.y = 0; vid_win.width = width; @@ -1034,13 +1051,13 @@ void LocalCamera::Initialise() Fatal( "Could not allocate picture" ); #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(capturePictures[i]->data, - capturePictures[i]->linesize, - (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], - capturePixFormat, width, height, 1); + capturePictures[i]->linesize, + (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], + capturePixFormat, width, height, 1); #else avpicture_fill( (AVPicture *)capturePictures[i], - (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], - capturePixFormat, width, height ); + (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], + capturePixFormat, width, height ); #endif } #endif // HAVE_LIBSWSCALE @@ -1079,7 +1096,7 @@ void LocalCamera::Initialise() Debug( 4, "New Y:%d", vid_win.y ); Debug( 4, "New W:%d", vid_win.width ); Debug( 4, "New H:%d", vid_win.height ); - + if ( ioctl( vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) Fatal( "Failed to get window data: %s", strerror(errno) ); @@ -1100,7 +1117,7 @@ void LocalCamera::Terminate() { Debug( 3, "Terminating video stream" ); //enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - // enum v4l2_buf_type type = v4l2_data.fmt.type; + // enum v4l2_buf_type type = v4l2_data.fmt.type; enum v4l2_buf_type type = (v4l2_buf_type)v4l2_data.fmt.type; if ( vidioctl( vid_fd, VIDIOC_STREAMOFF, &type ) < 0 ) Error( "Failed to stop capture stream: %s", strerror(errno) ); @@ -1110,44 +1127,44 @@ void LocalCamera::Terminate() #if HAVE_LIBSWSCALE /* Free capture pictures */ #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &capturePictures[i] ); + av_frame_free( &capturePictures[i] ); #else - av_freep( &capturePictures[i] ); + av_freep( &capturePictures[i] ); #endif #endif if ( munmap( v4l2_data.buffers[i].start, v4l2_data.buffers[i].length ) < 0 ) Error( "Failed to munmap buffer %d: %s", i, strerror(errno) ); - } - + } + } else #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 - if ( v4l_version == 1 ) - { + if ( v4l_version == 1 ) + { #if HAVE_LIBSWSCALE - for(int i=0; i < v4l1_data.frames.frames; i++) { - /* Free capture pictures */ + for(int i=0; i < v4l1_data.frames.frames; i++) { + /* Free capture pictures */ #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &capturePictures[i] ); + av_frame_free( &capturePictures[i] ); #else - av_freep( &capturePictures[i] ); + av_freep( &capturePictures[i] ); #endif - } + } #endif - - Debug( 3, "Unmapping video buffers" ); - if ( munmap((char*)v4l1_data.bufptr, v4l1_data.frames.size) < 0 ) - Error( "Failed to munmap buffers: %s", strerror(errno) ); - delete[] v4l1_data.buffers; - } + Debug( 3, "Unmapping video buffers" ); + if ( munmap((char*)v4l1_data.bufptr, v4l1_data.frames.size) < 0 ) + Error( "Failed to munmap buffers: %s", strerror(errno) ); + + delete[] v4l1_data.buffers; + } #endif // ZM_HAS_V4L1 close( vid_fd ); - + } uint32_t LocalCamera::AutoSelectFormat(int p_colours) { @@ -1160,13 +1177,13 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { unsigned int nIndex = 0; //int nRet = 0; // compiler say it isn't used int enum_fd; - + /* Open the device */ if ((enum_fd = open( device.c_str(), O_RDWR, 0 )) < 0) { Error( "Automatic format selection failed to open video device %s: %s", device.c_str(), strerror(errno) ); return selected_palette; } - + /* Enumerate available formats */ memset(&fmtinfo, 0, sizeof(fmtinfo)); fmtinfo.index = nIndex; @@ -1177,14 +1194,15 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { strcpy(fmt_desc[nIndex], (const char*)(fmtinfo.description)); fmt_fcc[nIndex] = fmtinfo.pixelformat; - Debug(6, "Got format: %s (%c%c%c%c) at index %d",fmt_desc[nIndex],fmt_fcc[nIndex]&0xff, (fmt_fcc[nIndex]>>8)&0xff, (fmt_fcc[nIndex]>>16)&0xff, (fmt_fcc[nIndex]>>24)&0xff ,nIndex); + Debug(6, "Got format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %d", + fmt_desc[nIndex], (fmt_fcc[nIndex]>>24)&0xff, (fmt_fcc[nIndex]>>16)&0xff, (fmt_fcc[nIndex]>>8)&0xff, (fmt_fcc[nIndex])&0xff ,nIndex); /* Proceed to the next index */ memset(&fmtinfo, 0, sizeof(fmtinfo)); fmtinfo.index = ++nIndex; fmtinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; } - + /* Select format */ int nIndexUsed = -1; unsigned int n_preferedformats = 0; @@ -1205,26 +1223,28 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { for( unsigned int i=0; i < n_preferedformats && nIndexUsed < 0; i++ ) { for( unsigned int j=0; j < nIndex; j++ ) { if( preferedformats[i] == fmt_fcc[j] ) { - Debug(6, "Choosing format: %s (%c%c%c%c) at index %d",fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); + Debug(6, "Choosing format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %u", + fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); /* Found a format! */ nIndexUsed = j; break; } else { - Debug(6, "No match for format: %s (%c%c%c%c) at index %d",fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); + Debug(6, "No match for format: %s (0x%02hhx%02hhx%02hhx%02hhx) at index %u", + fmt_desc[j],fmt_fcc[j]&0xff, (fmt_fcc[j]>>8)&0xff, (fmt_fcc[j]>>16)&0xff, (fmt_fcc[j]>>24)&0xff ,j); } } } - + /* Have we found a match? */ if(nIndexUsed >= 0) { /* Found a match */ selected_palette = fmt_fcc[nIndexUsed]; strcpy(palette_desc,fmt_desc[nIndexUsed]); } - + /* Close the device */ close(enum_fd); - + #endif /* ZM_HAS_V4L2 */ return selected_palette; } @@ -1288,25 +1308,25 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output+strlen(output), " Bus: %s\n", vid_cap.bus_info ); sprintf( output+strlen(output), " Version: %u.%u.%u\n", (vid_cap.version>>16)&0xff, (vid_cap.version>>8)&0xff, vid_cap.version&0xff ); sprintf( output+strlen(output), " Type: 0x%x\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s", vid_cap.capabilities, - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_CAPTURE, " ", "Supports", "Does not support", "video capture (X)" ), - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT, " ", "Supports", "Does not support", "video output" ), - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OVERLAY, " ", "Supports", "Does not support", "frame buffer overlay" ), - capString( vid_cap.capabilities&V4L2_CAP_VBI_CAPTURE, " ", "Supports", "Does not support", "VBI capture" ), - capString( vid_cap.capabilities&V4L2_CAP_VBI_OUTPUT, " ", "Supports", "Does not support", "VBI output" ), - capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_CAPTURE, " ", "Supports", "Does not support", "sliced VBI capture" ), - capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_OUTPUT, " ", "Supports", "Does not support", "sliced VBI output" ), + capString( vid_cap.capabilities&V4L2_CAP_VIDEO_CAPTURE, " ", "Supports", "Does not support", "video capture (X)" ), + capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT, " ", "Supports", "Does not support", "video output" ), + capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OVERLAY, " ", "Supports", "Does not support", "frame buffer overlay" ), + capString( vid_cap.capabilities&V4L2_CAP_VBI_CAPTURE, " ", "Supports", "Does not support", "VBI capture" ), + capString( vid_cap.capabilities&V4L2_CAP_VBI_OUTPUT, " ", "Supports", "Does not support", "VBI output" ), + capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_CAPTURE, " ", "Supports", "Does not support", "sliced VBI capture" ), + capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_OUTPUT, " ", "Supports", "Does not support", "sliced VBI output" ), #ifdef V4L2_CAP_VIDEO_OUTPUT_OVERLAY - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT_OVERLAY, " ", "Supports", "Does not support", "video output overlay" ), + capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT_OVERLAY, " ", "Supports", "Does not support", "video output overlay" ), #else // V4L2_CAP_VIDEO_OUTPUT_OVERLAY - "", + "", #endif // V4L2_CAP_VIDEO_OUTPUT_OVERLAY - capString( vid_cap.capabilities&V4L2_CAP_TUNER, " ", "Has", "Does not have", "tuner" ), - capString( vid_cap.capabilities&V4L2_CAP_AUDIO, " ", "Has", "Does not have", "audio in and/or out" ), - capString( vid_cap.capabilities&V4L2_CAP_RADIO, " ", "Has", "Does not have", "radio" ), - capString( vid_cap.capabilities&V4L2_CAP_READWRITE, " ", "Supports", "Does not support", "read/write i/o (X)" ), - capString( vid_cap.capabilities&V4L2_CAP_ASYNCIO, " ", "Supports", "Does not support", "async i/o" ), - capString( vid_cap.capabilities&V4L2_CAP_STREAMING, " ", "Supports", "Does not support", "streaming i/o (X)" ) - ); + capString( vid_cap.capabilities&V4L2_CAP_TUNER, " ", "Has", "Does not have", "tuner" ), + capString( vid_cap.capabilities&V4L2_CAP_AUDIO, " ", "Has", "Does not have", "audio in and/or out" ), + capString( vid_cap.capabilities&V4L2_CAP_RADIO, " ", "Has", "Does not have", "radio" ), + capString( vid_cap.capabilities&V4L2_CAP_READWRITE, " ", "Supports", "Does not support", "read/write i/o (X)" ), + capString( vid_cap.capabilities&V4L2_CAP_ASYNCIO, " ", "Supports", "Does not support", "async i/o" ), + capString( vid_cap.capabilities&V4L2_CAP_STREAMING, " ", "Supports", "Does not support", "streaming i/o (X)" ) + ); } else { @@ -1318,7 +1338,7 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } if ( verbose ) - sprintf( output+strlen(output), " Standards:\n" ); + sprintf( output+strlen(output), " Standards:\n" ); else sprintf( output+strlen(output), "S:" ); struct v4l2_standard standard; @@ -1347,14 +1367,14 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } } if ( verbose ) - sprintf( output+strlen(output), " %s\n", standard.name ); + sprintf( output+strlen(output), " %s\n", standard.name ); else sprintf( output+strlen(output), "%s/", standard.name ); } while ( standardIndex++ >= 0 ); if ( !verbose && output[strlen(output)-1] == '/') output[strlen(output)-1] = '|'; - + if ( verbose ) sprintf( output+strlen(output), " Formats:\n" ); else @@ -1385,63 +1405,65 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } } if ( verbose ) - sprintf( output+strlen(output), " %s (%c%c%c%c)\n", format.description, format.pixelformat&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>24)&0xff ); + sprintf( output+strlen(output), " %s (0x%02hhx%02hhx%02hhx%02hhx)\n", + format.description, (format.pixelformat>>24)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>8)&0xff, format.pixelformat&0xff); else - sprintf( output+strlen(output), "%c%c%c%c/", format.pixelformat&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>24)&0xff ); + sprintf( output+strlen(output), "0x%02hhx%02hhx%02hhx%02hhx/", + (format.pixelformat>>24)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat)&0xff); } while ( formatIndex++ >= 0 ); if ( !verbose ) output[strlen(output)-1] = '|'; - if(verbose) - sprintf( output+strlen(output), "Crop Capabilities\n" ); - - struct v4l2_cropcap cropcap; - memset( &cropcap, 0, sizeof(cropcap) ); - cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if ( vidioctl( vid_fd, VIDIOC_CROPCAP, &cropcap ) < 0 ) - { - if(errno != EINVAL) { - /* Failed querying crop capability, write error to the log and continue as if crop is not supported */ - Error( "Failed to query crop capabilities: %s", strerror(errno) ); - } - - if(verbose) { - sprintf( output+strlen(output), " Cropping is not supported\n"); - } else { - /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ - sprintf( output+strlen(output), "B:%dx%d|",0,0); - } - } else { - struct v4l2_crop crop; - memset( &crop, 0, sizeof(crop) ); - crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - - if ( vidioctl( vid_fd, VIDIOC_G_CROP, &crop ) < 0 ) - { - if ( errno != EINVAL ) + if(verbose) + sprintf( output+strlen(output), "Crop Capabilities\n" ); + + struct v4l2_cropcap cropcap; + memset( &cropcap, 0, sizeof(cropcap) ); + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if ( vidioctl( vid_fd, VIDIOC_CROPCAP, &cropcap ) < 0 ) { - /* Failed querying crop sizes, write error to the log and continue as if crop is not supported */ - Error( "Failed to query crop: %s", strerror(errno) ); - } - - if ( verbose ) { - sprintf( output+strlen(output), " Cropping is not supported\n"); + if(errno != EINVAL) { + /* Failed querying crop capability, write error to the log and continue as if crop is not supported */ + Error( "Failed to query crop capabilities: %s", strerror(errno) ); + } + + if(verbose) { + sprintf( output+strlen(output), " Cropping is not supported\n"); + } else { + /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ + sprintf( output+strlen(output), "B:%dx%d|",0,0); + } } else { - /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ - sprintf( output+strlen(output), "B:%dx%d|",0,0); - } - } else { - /* Cropping supported */ - if ( verbose ) { - sprintf( output+strlen(output), " Bounds: %d x %d\n", cropcap.bounds.width, cropcap.bounds.height ); - sprintf( output+strlen(output), " Default: %d x %d\n", cropcap.defrect.width, cropcap.defrect.height ); - sprintf( output+strlen(output), " Current: %d x %d\n", crop.c.width, crop.c.height ); - } else { - sprintf( output+strlen(output), "B:%dx%d|", cropcap.bounds.width, cropcap.bounds.height ); - } - } - } /* Crop code */ + struct v4l2_crop crop; + memset( &crop, 0, sizeof(crop) ); + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if ( vidioctl( vid_fd, VIDIOC_G_CROP, &crop ) < 0 ) + { + if ( errno != EINVAL ) + { + /* Failed querying crop sizes, write error to the log and continue as if crop is not supported */ + Error( "Failed to query crop: %s", strerror(errno) ); + } + + if ( verbose ) { + sprintf( output+strlen(output), " Cropping is not supported\n"); + } else { + /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ + sprintf( output+strlen(output), "B:%dx%d|",0,0); + } + } else { + /* Cropping supported */ + if ( verbose ) { + sprintf( output+strlen(output), " Bounds: %d x %d\n", cropcap.bounds.width, cropcap.bounds.height ); + sprintf( output+strlen(output), " Default: %d x %d\n", cropcap.defrect.width, cropcap.defrect.height ); + sprintf( output+strlen(output), " Current: %d x %d\n", crop.c.width, crop.c.height ); + } else { + sprintf( output+strlen(output), "B:%dx%d|", cropcap.bounds.width, cropcap.bounds.height ); + } + } + } /* Crop code */ struct v4l2_input input; int inputIndex = 0; @@ -1505,16 +1527,16 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output, "Error, failed to switch to input %d: %s\n", input.index, strerror(errno) ); else sprintf( output, "error%d\n", errno ); - return( false ); + return( false ); } if ( verbose ) { sprintf( output+strlen(output), " Input %d\n", input.index ); - sprintf( output+strlen(output), " Name: %s\n", input.name ); - sprintf( output+strlen(output), " Type: %s\n", input.type==V4L2_INPUT_TYPE_TUNER?"Tuner":(input.type==V4L2_INPUT_TYPE_CAMERA?"Camera":"Unknown") ); - sprintf( output+strlen(output), " Audioset: %08x\n", input.audioset ); - sprintf( output+strlen(output), " Standards: 0x%llx\n", input.std ); + sprintf( output+strlen(output), " Name: %s\n", input.name ); + sprintf( output+strlen(output), " Type: %s\n", input.type==V4L2_INPUT_TYPE_TUNER?"Tuner":(input.type==V4L2_INPUT_TYPE_CAMERA?"Camera":"Unknown") ); + sprintf( output+strlen(output), " Audioset: %08x\n", input.audioset ); + sprintf( output+strlen(output), " Standards: 0x%llx\n", input.std ); } else { @@ -1525,10 +1547,10 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers if ( verbose ) { - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_POWER, "Power ", "off", "on", " (X)" ) ); - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_SIGNAL, "Signal ", "not detected", "detected", " (X)" ) ); - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_COLOR, "Colour Signal ", "not detected", "detected", "" ) ); - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_H_LOCK, "Horizontal Lock ", "not detected", "detected", "" ) ); + sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_POWER, "Power ", "off", "on", " (X)" ) ); + sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_SIGNAL, "Signal ", "not detected", "detected", " (X)" ) ); + sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_COLOR, "Colour Signal ", "not detected", "detected", "" ) ); + sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_H_LOCK, "Horizontal Lock ", "not detected", "detected", "" ) ); } else { @@ -1562,21 +1584,21 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output+strlen(output), "Video Capabilities\n" ); sprintf( output+strlen(output), " Name: %s\n", vid_cap.name ); sprintf( output+strlen(output), " Type: %d\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s", vid_cap.type, - vid_cap.type&VID_TYPE_CAPTURE?" Can capture\n":"", - vid_cap.type&VID_TYPE_TUNER?" Can tune\n":"", - vid_cap.type&VID_TYPE_TELETEXT?" Does teletext\n":"", - vid_cap.type&VID_TYPE_OVERLAY?" Overlay onto frame buffer\n":"", - vid_cap.type&VID_TYPE_CHROMAKEY?" Overlay by chromakey\n":"", - vid_cap.type&VID_TYPE_CLIPPING?" Can clip\n":"", - vid_cap.type&VID_TYPE_FRAMERAM?" Uses the frame buffer memory\n":"", - vid_cap.type&VID_TYPE_SCALES?" Scalable\n":"", - vid_cap.type&VID_TYPE_MONOCHROME?" Monochrome only\n":"", - vid_cap.type&VID_TYPE_SUBCAPTURE?" Can capture subareas of the image\n":"", - vid_cap.type&VID_TYPE_MPEG_DECODER?" Can decode MPEG streams\n":"", - vid_cap.type&VID_TYPE_MPEG_ENCODER?" Can encode MPEG streams\n":"", - vid_cap.type&VID_TYPE_MJPEG_DECODER?" Can decode MJPEG streams\n":"", - vid_cap.type&VID_TYPE_MJPEG_ENCODER?" Can encode MJPEG streams\n":"" - ); + vid_cap.type&VID_TYPE_CAPTURE?" Can capture\n":"", + vid_cap.type&VID_TYPE_TUNER?" Can tune\n":"", + vid_cap.type&VID_TYPE_TELETEXT?" Does teletext\n":"", + vid_cap.type&VID_TYPE_OVERLAY?" Overlay onto frame buffer\n":"", + vid_cap.type&VID_TYPE_CHROMAKEY?" Overlay by chromakey\n":"", + vid_cap.type&VID_TYPE_CLIPPING?" Can clip\n":"", + vid_cap.type&VID_TYPE_FRAMERAM?" Uses the frame buffer memory\n":"", + vid_cap.type&VID_TYPE_SCALES?" Scalable\n":"", + vid_cap.type&VID_TYPE_MONOCHROME?" Monochrome only\n":"", + vid_cap.type&VID_TYPE_SUBCAPTURE?" Can capture subareas of the image\n":"", + vid_cap.type&VID_TYPE_MPEG_DECODER?" Can decode MPEG streams\n":"", + vid_cap.type&VID_TYPE_MPEG_ENCODER?" Can encode MPEG streams\n":"", + vid_cap.type&VID_TYPE_MJPEG_DECODER?" Can decode MJPEG streams\n":"", + vid_cap.type&VID_TYPE_MJPEG_ENCODER?" Can encode MJPEG streams\n":"" + ); sprintf( output+strlen(output), " Video Channels: %d\n", vid_cap.channels ); sprintf( output+strlen(output), " Audio Channels: %d\n", vid_cap.audios ); sprintf( output+strlen(output), " Maximum Width: %d\n", vid_cap.maxwidth ); @@ -1638,25 +1660,25 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers { sprintf( output+strlen(output), "Picture Attributes\n" ); sprintf( output+strlen(output), " Palette: %d - %s\n", vid_pic.palette, - vid_pic.palette==VIDEO_PALETTE_GREY?"Linear greyscale":( - vid_pic.palette==VIDEO_PALETTE_HI240?"High 240 cube (BT848)":( - vid_pic.palette==VIDEO_PALETTE_RGB565?"565 16 bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB24?"24bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB32?"32bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB555?"555 15bit RGB":( - vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422 capture":( - vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( - vid_pic.palette==VIDEO_PALETTE_UYVY?"UVYV":( - vid_pic.palette==VIDEO_PALETTE_YUV420?"YUV420":( - vid_pic.palette==VIDEO_PALETTE_YUV411?"YUV411 capture":( - vid_pic.palette==VIDEO_PALETTE_RAW?"RAW capture (BT848)":( - vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( - vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422":( - vid_pic.palette==VIDEO_PALETTE_YUV422P?"YUV 4:2:2 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV411P?"YUV 4:1:1 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV420P?"YUV 4:2:0 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV410P?"YUV 4:1:0 Planar":"Unknown" - )))))))))))))))))); + vid_pic.palette==VIDEO_PALETTE_GREY?"Linear greyscale":( + vid_pic.palette==VIDEO_PALETTE_HI240?"High 240 cube (BT848)":( + vid_pic.palette==VIDEO_PALETTE_RGB565?"565 16 bit RGB":( + vid_pic.palette==VIDEO_PALETTE_RGB24?"24bit RGB":( + vid_pic.palette==VIDEO_PALETTE_RGB32?"32bit RGB":( + vid_pic.palette==VIDEO_PALETTE_RGB555?"555 15bit RGB":( + vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422 capture":( + vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( + vid_pic.palette==VIDEO_PALETTE_UYVY?"UVYV":( + vid_pic.palette==VIDEO_PALETTE_YUV420?"YUV420":( + vid_pic.palette==VIDEO_PALETTE_YUV411?"YUV411 capture":( + vid_pic.palette==VIDEO_PALETTE_RAW?"RAW capture (BT848)":( + vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( + vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422":( + vid_pic.palette==VIDEO_PALETTE_YUV422P?"YUV 4:2:2 Planar":( + vid_pic.palette==VIDEO_PALETTE_YUV411P?"YUV 4:1:1 Planar":( + vid_pic.palette==VIDEO_PALETTE_YUV420P?"YUV 4:2:0 Planar":( + vid_pic.palette==VIDEO_PALETTE_YUV410P?"YUV 4:1:0 Planar":"Unknown" + )))))))))))))))))); sprintf( output+strlen(output), " Colour Depth: %d\n", vid_pic.depth ); sprintf( output+strlen(output), " Brightness: %d\n", vid_pic.brightness ); sprintf( output+strlen(output), " Hue: %d\n", vid_pic.hue ); @@ -1695,19 +1717,19 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output+strlen(output), " Name: %s\n", vid_src.name ); sprintf( output+strlen(output), " Channel: %d\n", vid_src.channel ); sprintf( output+strlen(output), " Flags: %d\n%s%s", vid_src.flags, - vid_src.flags&VIDEO_VC_TUNER?" Channel has a tuner\n":"", - vid_src.flags&VIDEO_VC_AUDIO?" Channel has audio\n":"" - ); + vid_src.flags&VIDEO_VC_TUNER?" Channel has a tuner\n":"", + vid_src.flags&VIDEO_VC_AUDIO?" Channel has audio\n":"" + ); sprintf( output+strlen(output), " Type: %d - %s\n", vid_src.type, - vid_src.type==VIDEO_TYPE_TV?"TV":( - vid_src.type==VIDEO_TYPE_CAMERA?"Camera":"Unknown" - )); + vid_src.type==VIDEO_TYPE_TV?"TV":( + vid_src.type==VIDEO_TYPE_CAMERA?"Camera":"Unknown" + )); sprintf( output+strlen(output), " Format: %d - %s\n", vid_src.norm, - vid_src.norm==VIDEO_MODE_PAL?"PAL":( - vid_src.norm==VIDEO_MODE_NTSC?"NTSC":( - vid_src.norm==VIDEO_MODE_SECAM?"SECAM":( - vid_src.norm==VIDEO_MODE_AUTO?"AUTO":"Unknown" - )))); + vid_src.norm==VIDEO_MODE_PAL?"PAL":( + vid_src.norm==VIDEO_MODE_NTSC?"NTSC":( + vid_src.norm==VIDEO_MODE_SECAM?"SECAM":( + vid_src.norm==VIDEO_MODE_AUTO?"AUTO":"Unknown" + )))); } else { @@ -1746,7 +1768,7 @@ int LocalCamera::Brightness( int p_brightness ) Error( "Unable to query brightness: %s", strerror(errno) ) else Warning( "Brightness control is not supported" ) - //Info( "Brightness 1 %d", vid_control.value ); + //Info( "Brightness 1 %d", vid_control.value ); } else if ( p_brightness >= 0 ) { @@ -2032,7 +2054,7 @@ int LocalCamera::Capture( Image &image ) static uint8_t* directbuffer = NULL; static int capture_frame = -1; int buffer_bytesused = 0; - + int captures_per_frame = 1; if ( channel_count > 1 ) captures_per_frame = v4l_captures_per_frame; @@ -2040,8 +2062,8 @@ int LocalCamera::Capture( Image &image ) captures_per_frame = 1; Warning( "Invalid Captures Per Frame setting: %d", captures_per_frame ); } - - + + // Do the capture, unless we are the second or subsequent camera on a channel, in which case just reuse the buffer if ( channel_prime ) { @@ -2065,7 +2087,7 @@ int LocalCamera::Capture( Image &image ) Warning( "Capture failure, possible signal loss?: %s", strerror(errno) ) else Error( "Unable to capture frame %d: %s", vid_buf.index, strerror(errno) ) - return( -1 ); + return( -1 ); } v4l2_data.bufptr = &vid_buf; @@ -2086,9 +2108,9 @@ int LocalCamera::Capture( Image &image ) buffer_bytesused = v4l2_data.bufptr->bytesused; if((v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height)) { - Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d",v4l2_data.fmt.fmt.pix.width,v4l2_data.fmt.fmt.pix.height,width,height); + Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d",v4l2_data.fmt.fmt.pix.width,v4l2_data.fmt.fmt.pix.height,width,height); } - + } #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 @@ -2120,12 +2142,12 @@ int LocalCamera::Capture( Image &image ) buffer = v4l1_data.bufptr+v4l1_data.frames.offsets[capture_frame]; } #endif // ZM_HAS_V4L1 - } /* prime capture */ - + } /* prime capture */ + if(conversion_type != 0) { - + Debug( 3, "Performing format conversion" ); - + /* Request a writeable buffer of the target image */ directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); if(directbuffer == NULL) { @@ -2139,36 +2161,36 @@ int LocalCamera::Capture( Image &image ) /* Use swscale to convert the image directly into the shared memory */ #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(tmpPicture->data, - tmpPicture->linesize, directbuffer, - imagePixFormat, width, height, 1); + tmpPicture->linesize, directbuffer, + imagePixFormat, width, height, 1); #else avpicture_fill( (AVPicture *)tmpPicture, directbuffer, - imagePixFormat, width, height ); + imagePixFormat, width, height ); #endif sws_scale( imgConversionContext, capturePictures[capture_frame]->data, capturePictures[capture_frame]->linesize, 0, height, tmpPicture->data, tmpPicture->linesize ); } #endif if(conversion_type == 2) { - + Debug( 9, "Calling the conversion function" ); /* Call the image conversion function and convert directly into the shared memory */ (*conversion_fptr)(buffer, directbuffer, pixels); } else if(conversion_type == 3) { - + Debug( 9, "Decoding the JPEG image" ); /* JPEG decoding */ image.DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder); } - + } else { Debug( 3, "No format conversion performed. Assigning the image" ); - + /* No conversion was performed, the image is in the V4L buffers and needs to be copied into the shared memory */ image.Assign( width, height, colours, subpixelorder, buffer, imagesize); - + } - + return( 0 ); } diff --git a/src/zm_local_camera.h b/src/zm_local_camera.h index 335c06ce7..dae5f830e 100644 --- a/src/zm_local_camera.h +++ b/src/zm_local_camera.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_LOCAL_CAMERA_H @@ -23,6 +23,7 @@ #include "zm.h" #include "zm_camera.h" #include "zm_image.h" +#include "zm_packetqueue.h" #if ZM_HAS_V4L @@ -36,6 +37,11 @@ #include #endif // HAVE_LINUX_VIDEODEV2_H +// Required on systems with v4l1 but without v4l2 headers +#ifndef VIDEO_MAX_FRAME +#define VIDEO_MAX_FRAME 32 +#endif + #include "zm_ffmpeg.h" // @@ -47,31 +53,31 @@ class LocalCamera : public Camera { protected: #if ZM_HAS_V4L2 - struct V4L2MappedBuffer - { - void *start; - size_t length; - }; + struct V4L2MappedBuffer + { + void *start; + size_t length; + }; - struct V4L2Data - { - v4l2_cropcap cropcap; - v4l2_crop crop; - v4l2_format fmt; - v4l2_requestbuffers reqbufs; - V4L2MappedBuffer *buffers; - v4l2_buffer *bufptr; - }; + struct V4L2Data + { + v4l2_cropcap cropcap; + v4l2_crop crop; + v4l2_format fmt; + v4l2_requestbuffers reqbufs; + V4L2MappedBuffer *buffers; + v4l2_buffer *bufptr; + }; #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 - struct V4L1Data - { - int active_frame; - video_mbuf frames; - video_mmap *buffers; - unsigned char *bufptr; - }; + struct V4L1Data + { + int active_frame; + video_mbuf frames; + video_mmap *buffers; + unsigned char *bufptr; + }; #endif // ZM_HAS_V4L1 protected: @@ -99,24 +105,42 @@ protected: unsigned int v4l_captures_per_frame; #if ZM_HAS_V4L2 - static V4L2Data v4l2_data; + static V4L2Data v4l2_data; #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 - static V4L1Data v4l1_data; + static V4L1Data v4l1_data; #endif // ZM_HAS_V4L1 #if HAVE_LIBSWSCALE - static AVFrame **capturePictures; - _AVPIXELFORMAT imagePixFormat; - _AVPIXELFORMAT capturePixFormat; + static AVFrame **capturePictures; + _AVPIXELFORMAT imagePixFormat; + _AVPIXELFORMAT capturePixFormat; struct SwsContext *imgConversionContext; - AVFrame *tmpPicture; + AVFrame *tmpPicture; #endif // HAVE_LIBSWSCALE - static LocalCamera *last_camera; + static LocalCamera *last_camera; public: - LocalCamera( int p_id, const std::string &device, int p_channel, int p_format, bool v4lmultibuffer, unsigned int v4lcapturesperframe, const std::string &p_method, int p_width, int p_height, int p_colours, int p_palette, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, unsigned int p_extras = 0); + LocalCamera( + int p_id, + const std::string &device, + int p_channel, + int p_format, + bool v4lmultibuffer, + unsigned int v4lcapturesperframe, + const std::string &p_method, + int p_width, + int p_height, + int p_colours, + int p_palette, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio, + unsigned int p_extras = 0); ~LocalCamera(); void Initialise(); @@ -138,6 +162,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose ); }; diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index 4d66deee2..f122fa1d8 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "zm_logger.h" @@ -31,9 +31,9 @@ #include #include #include +#include #ifdef __FreeBSD__ #include -#include #endif bool Logger::smInitialised = false; @@ -43,23 +43,18 @@ Logger::StringMap Logger::smCodes; Logger::IntMap Logger::smSyslogPriorities; #if 0 -static void subtractTime( struct timeval * const tp1, struct timeval * const tp2 ) -{ +static void subtractTime( struct timeval * const tp1, struct timeval * const tp2 ) { tp1->tv_sec -= tp2->tv_sec; - if ( tp1->tv_usec <= tp2->tv_usec ) - { + if ( tp1->tv_usec <= tp2->tv_usec ) { tp1->tv_sec--; tp1->tv_usec = 1000000 - (tp2->tv_usec - tp1->tv_usec); - } - else - { + } else { tp1->tv_usec = tp1->tv_usec - tp2->tv_usec; } } #endif -void Logger::usrHandler( int sig ) -{ +void Logger::usrHandler( int sig ) { Logger *logger = fetch(); if ( sig == SIGUSR1 ) logger->level( logger->level()+1 ); @@ -80,15 +75,13 @@ Logger::Logger() : mDbConnected( false ), mLogFileFP( NULL ), mHasTerm( false ), - mFlush( false ) -{ - if ( smInstance ) - { + mFlush( false ) { + + if ( smInstance ) { Panic( "Attempt to create second instance of Logger class" ); } - if ( !smInitialised ) - { + if ( !smInitialised ) { smCodes[INFO] = "INF"; smCodes[WARNING] = "WAR"; smCodes[ERROR] = "ERR"; @@ -103,8 +96,7 @@ Logger::Logger() : smSyslogPriorities[PANIC] = LOG_ERR; char code[4] = ""; - for ( int i = DEBUG1; i <= DEBUG9; i++ ) - { + for ( int i = DEBUG1; i <= DEBUG9; i++ ) { snprintf( code, sizeof(code), "DB%d", i ); smCodes[i] = code; smSyslogPriorities[i] = LOG_DEBUG; @@ -117,21 +109,18 @@ Logger::Logger() : mHasTerm = true; } -Logger::~Logger() -{ +Logger::~Logger() { terminate(); } -void Logger::initialise( const std::string &id, const Options &options ) -{ +void Logger::initialise( const std::string &id, const Options &options ) { char *envPtr; if ( !id.empty() ) this->id( id ); std::string tempLogFile; - if ( options.mLogPath.size() ) - { + if ( options.mLogPath.size() ) { mLogPath = options.mLogPath; tempLogFile = mLogPath+"/"+mId+".log"; } @@ -179,26 +168,22 @@ void Logger::initialise( const std::string &id, const Options &options ) if ( (envPtr = getTargettedEnv( "LOG_LEVEL_SYSLOG" )) ) tempSyslogLevel = atoi(envPtr); - if ( config.log_debug ) - { + if ( config.log_debug ) { StringVector targets = split( config.log_debug_target, "|" ); - for ( unsigned int i = 0; i < targets.size(); i++ ) - { + for ( unsigned int i = 0; i < targets.size(); i++ ) { const std::string &target = targets[i]; - if ( target == mId || target == "_"+mId || target == "_"+mIdRoot || target == "_"+mIdRoot || target == "" ) - { - if ( config.log_debug_level > NOLOG ) - { + if ( target == mId || target == "_"+mId || target == "_"+mIdRoot || target == "_"+mIdRoot || target == "" ) { + if ( config.log_debug_level > NOLOG ) { tempLevel = config.log_debug_level; - if ( config.log_debug_file[0] ) - { + if ( config.log_debug_file[0] ) { tempLogFile = config.log_debug_file; tempFileLevel = tempLevel; } } } - } - } + } // end foreach target + } // end if config.log_debug + logFile( tempLogFile ); @@ -209,33 +194,42 @@ void Logger::initialise( const std::string &id, const Options &options ) level( tempLevel ); - mFlush = (envPtr = getenv( "LOG_FLUSH")) ? atoi( envPtr ) : false; + mFlush = false; + if (envPtr = getenv( "LOG_FLUSH")) { + mFlush = atoi( envPtr ); + } else if ( config.log_debug ) { + mFlush = true; + } //mRuntime = (envPtr = getenv( "LOG_RUNTIME")) ? atoi( envPtr ) : false; - { struct sigaction action; memset( &action, 0, sizeof(action) ); action.sa_handler = usrHandler; action.sa_flags = SA_RESTART; - if ( sigaction( SIGUSR1, &action, 0 ) < 0 ) - { + if ( sigaction( SIGUSR1, &action, 0 ) < 0 ) { Fatal( "sigaction(), error = %s", strerror(errno) ); } - if ( sigaction( SIGUSR2, &action, 0 ) < 0) - { + if ( sigaction( SIGUSR2, &action, 0 ) < 0) { Fatal( "sigaction(), error = %s", strerror(errno) ); } } mInitialised = true; - Debug( 1, "LogOpts: level=%s/%s, screen=%s, database=%s, logfile=%s->%s, syslog=%s", smCodes[mLevel].c_str(), smCodes[mEffectiveLevel].c_str(), smCodes[mTermLevel].c_str(), smCodes[mDatabaseLevel].c_str(), smCodes[mFileLevel].c_str(), mLogFile.c_str(), smCodes[mSyslogLevel].c_str() ); + Debug( 1, "LogOpts: level=%s/%s, screen=%s, database=%s, logfile=%s->%s, syslog=%s", + smCodes[mLevel].c_str(), + smCodes[mEffectiveLevel].c_str(), + smCodes[mTermLevel].c_str(), + smCodes[mDatabaseLevel].c_str(), + smCodes[mFileLevel].c_str(), + mLogFile.c_str(), + smCodes[mSyslogLevel].c_str() + ); } -void Logger::terminate() -{ +void Logger::terminate() { Debug(1, "Terminating Logger" ); if ( mFileLevel > NOLOG ) @@ -248,33 +242,28 @@ void Logger::terminate() closeDatabase(); } -bool Logger::boolEnv( const std::string &name, bool defaultValue ) -{ +bool Logger::boolEnv( const std::string &name, bool defaultValue ) { const char *envPtr = getenv( name.c_str() ); return( envPtr ? atoi( envPtr ) : defaultValue ); } -int Logger::intEnv( const std::string &name, bool defaultValue ) -{ +int Logger::intEnv( const std::string &name, bool defaultValue ) { const char *envPtr = getenv( name.c_str() ); return( envPtr ? atoi( envPtr ) : defaultValue ); } -std::string Logger::strEnv( const std::string &name, const std::string defaultValue ) -{ +std::string Logger::strEnv( const std::string &name, const std::string defaultValue ) { const char *envPtr = getenv( name.c_str() ); return( envPtr ? envPtr : defaultValue ); } -char *Logger::getTargettedEnv( const std::string &name ) -{ +char *Logger::getTargettedEnv( const std::string &name ) { char *envPtr = NULL; std::string envName; envName = name+"_"+mId; envPtr = getenv( envName.c_str() ); - if ( !envPtr && mId != mIdRoot ) - { + if ( !envPtr && mId != mIdRoot ) { envName = name+"_"+mIdRoot; envPtr = getenv( envName.c_str() ); } @@ -283,27 +272,22 @@ char *Logger::getTargettedEnv( const std::string &name ) return( envPtr ); } -const std::string &Logger::id( const std::string &id ) -{ +const std::string &Logger::id( const std::string &id ) { std::string tempId = id; size_t pos; // Remove whitespace - while ( (pos = tempId.find_first_of( " \t" )) != std::string::npos ) - { + while ( (pos = tempId.find_first_of( " \t" )) != std::string::npos ) { tempId.replace( pos, 1, "" ); } // Replace non-alphanum with underscore - while ( (pos = tempId.find_first_not_of( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" )) != std::string::npos ) - { + while ( (pos = tempId.find_first_not_of( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" )) != std::string::npos ) { tempId.replace( pos, 1, "_" ); } - if ( mId != tempId ) - { + if ( mId != tempId ) { mId = tempId; pos = mId.find( '_' ); - if ( pos != std::string::npos ) - { + if ( pos != std::string::npos ) { mIdRoot = mId.substr( 0, pos ); if ( ++pos < mId.size() ) mIdArgs = mId.substr( pos ); @@ -312,10 +296,8 @@ const std::string &Logger::id( const std::string &id ) return( mId ); } -Logger::Level Logger::level( Logger::Level level ) -{ - if ( level > NOOPT ) - { +Logger::Level Logger::level( Logger::Level level ) { + if ( level > NOOPT ) { level = limit(level); if ( mLevel != level ) mLevel = level; @@ -335,10 +317,8 @@ Logger::Level Logger::level( Logger::Level level ) return( mLevel ); } -Logger::Level Logger::termLevel( Logger::Level termLevel ) -{ - if ( termLevel > NOOPT ) - { +Logger::Level Logger::termLevel( Logger::Level termLevel ) { + if ( termLevel > NOOPT ) { if ( !mHasTerm ) termLevel = NOLOG; termLevel = limit(termLevel); @@ -348,69 +328,62 @@ Logger::Level Logger::termLevel( Logger::Level termLevel ) return( mTermLevel ); } -Logger::Level Logger::databaseLevel( Logger::Level databaseLevel ) -{ - if ( databaseLevel > NOOPT ) - { +Logger::Level Logger::databaseLevel( Logger::Level databaseLevel ) { + if ( databaseLevel > NOOPT ) { databaseLevel = limit(databaseLevel); - if ( mDatabaseLevel != databaseLevel ) - { - if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) - { - if ( !mDbConnected ) - { - if ( !mysql_init( &mDbConnection ) ) - { + if ( mDatabaseLevel != databaseLevel ) { + if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) { + if ( !mDbConnected ) { + if ( !mysql_init( &mDbConnection ) ) { Fatal( "Can't initialise database connection: %s", mysql_error( &mDbConnection ) ); exit( mysql_errno( &mDbConnection ) ); } my_bool reconnect = 1; if ( mysql_options( &mDbConnection, MYSQL_OPT_RECONNECT, &reconnect ) ) Fatal( "Can't set database auto reconnect option: %s", mysql_error( &mDbConnection ) ); - std::string::size_type colonIndex = staticConfig.DB_HOST.find( ":/" ); - if ( colonIndex != std::string::npos ) - { + std::string::size_type colonIndex = staticConfig.DB_HOST.find( ":" ); + if ( colonIndex == std::string::npos ) { + if ( !mysql_real_connect( &mDbConnection, staticConfig.DB_HOST.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, NULL, 0 ) ) { + Fatal( "Can't connect to database: %s", mysql_error( &mDbConnection ) ); + exit( mysql_errno( &mDbConnection ) ); + } + } else { std::string dbHost = staticConfig.DB_HOST.substr( 0, colonIndex ); - std::string dbPort = staticConfig.DB_HOST.substr( colonIndex+1 ); - if ( !mysql_real_connect( &mDbConnection, dbHost.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), 0, atoi(dbPort.c_str()), 0, 0 ) ) - { - Fatal( "Can't connect to database: %s", mysql_error( &mDbConnection ) ); - exit( mysql_errno( &mDbConnection ) ); + std::string dbPortOrSocket = staticConfig.DB_HOST.substr( colonIndex+1 ); + if ( dbPortOrSocket[0] == '/' ) { + if ( !mysql_real_connect( &mDbConnection, NULL, staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, dbPortOrSocket.c_str(), 0 ) ) { + Fatal( "Can't connect to database: %s", mysql_error( &mDbConnection ) ); + exit( mysql_errno( &mDbConnection ) ); + } + } else { + if ( !mysql_real_connect( &mDbConnection, dbHost.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, atoi(dbPortOrSocket.c_str()), NULL, 0 ) ) { + Fatal( "Can't connect to database: %s", mysql_error( &mDbConnection ) ); + exit( mysql_errno( &mDbConnection ) ); + } } - } - else - { - if ( !mysql_real_connect( &mDbConnection, staticConfig.DB_HOST.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), 0, 0, 0, 0 ) ) - { - Fatal( "Can't connect to database: %s", mysql_error( &mDbConnection ) ); - exit( mysql_errno( &mDbConnection ) ); - } - } + } // end if has colon unsigned long mysqlVersion = mysql_get_server_version( &mDbConnection ); if ( mysqlVersion < 50019 ) if ( mysql_options( &mDbConnection, MYSQL_OPT_RECONNECT, &reconnect ) ) Fatal( "Can't set database auto reconnect option: %s", mysql_error( &mDbConnection ) ); - if ( mysql_select_db( &mDbConnection, staticConfig.DB_NAME.c_str() ) ) - { + if ( mysql_select_db( &mDbConnection, staticConfig.DB_NAME.c_str() ) ) { Fatal( "Can't select database: %s", mysql_error( &mDbConnection ) ); exit( mysql_errno( &mDbConnection ) ); } mDbConnected = true; - } - } + } // end if ! mDbConnected + } // end if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) mDatabaseLevel = databaseLevel; - } - } + } // end if ( mDatabaseLevel != databaseLevel ) + } // end if ( databaseLevel > NOOPT ) + return( mDatabaseLevel ); } -Logger::Level Logger::fileLevel( Logger::Level fileLevel ) -{ - if ( fileLevel > NOOPT ) - { +Logger::Level Logger::fileLevel( Logger::Level fileLevel ) { + if ( fileLevel > NOOPT ) { fileLevel = limit(fileLevel); - if ( mFileLevel != fileLevel ) - { + if ( mFileLevel != fileLevel ) { if ( mFileLevel > NOLOG ) closeFile(); mFileLevel = fileLevel; @@ -421,13 +394,10 @@ Logger::Level Logger::fileLevel( Logger::Level fileLevel ) return( mFileLevel ); } -Logger::Level Logger::syslogLevel( Logger::Level syslogLevel ) -{ - if ( syslogLevel > NOOPT ) - { +Logger::Level Logger::syslogLevel( Logger::Level syslogLevel ) { + if ( syslogLevel > NOOPT ) { syslogLevel = limit(syslogLevel); - if ( mSyslogLevel != syslogLevel ) - { + if ( mSyslogLevel != syslogLevel ) { if ( mSyslogLevel > NOLOG ) closeSyslog(); mSyslogLevel = syslogLevel; @@ -438,12 +408,10 @@ Logger::Level Logger::syslogLevel( Logger::Level syslogLevel ) return( mSyslogLevel ); } -void Logger::logFile( const std::string &logFile ) -{ +void Logger::logFile( const std::string &logFile ) { bool addLogPid = false; std::string tempLogFile = logFile; - if ( tempLogFile[tempLogFile.length()-1] == '+' ) - { + if ( tempLogFile[tempLogFile.length()-1] == '+' ) { tempLogFile.resize(tempLogFile.length()-1); addLogPid = true; } @@ -453,173 +421,151 @@ void Logger::logFile( const std::string &logFile ) mLogFile = tempLogFile; } -void Logger::openFile() -{ - if ( mLogFile.size() && (mLogFileFP = fopen( mLogFile.c_str() ,"w" )) == (FILE *)NULL ) - { +void Logger::openFile() { + if ( mLogFile.size() && (mLogFileFP = fopen( mLogFile.c_str() ,"w" )) == (FILE *)NULL ) { mFileLevel = NOLOG; Fatal( "fopen() for %s, error = %s", mLogFile.c_str(), strerror(errno) ); } } -void Logger::closeFile() -{ - if ( mLogFileFP ) - { +void Logger::closeFile() { + if ( mLogFileFP ) { fflush( mLogFileFP ); - if ( fclose( mLogFileFP ) < 0 ) - { + if ( fclose( mLogFileFP ) < 0 ) { Fatal( "fclose(), error = %s",strerror(errno) ); } mLogFileFP = (FILE *)NULL; } } -void Logger::closeDatabase() -{ - if ( mDbConnected ) - { +void Logger::closeDatabase() { + if ( mDbConnected ) { mysql_close( &mDbConnection ); mDbConnected = false; } } -void Logger::openSyslog() -{ +void Logger::openSyslog() { (void) openlog( mId.c_str(), LOG_PID|LOG_NDELAY, LOG_LOCAL1 ); } -void Logger::closeSyslog() -{ +void Logger::closeSyslog() { (void) closelog(); } -void Logger::logPrint( bool hex, const char * const filepath, const int line, const int level, const char *fstring, ... ) -{ - if ( level <= mEffectiveLevel ) - { - char classString[4]; - char timeString[64]; - char logString[8192]; - va_list argPtr; +void Logger::logPrint( bool hex, const char * const filepath, const int line, const int level, const char *fstring, ... ) { + if ( level <= mEffectiveLevel ) { + char timeString[64]; + char logString[8192]; + va_list argPtr; struct timeval timeVal; - const char * const file = basename(filepath); - + char *filecopy = strdup(filepath); + const char * const file = basename(filecopy); + const char *classString = smCodes[level].c_str(); + if ( level < PANIC || level > DEBUG9 ) Panic( "Invalid logger level %d", level ); - strncpy( classString, smCodes[level].c_str(), sizeof(classString) ); - gettimeofday( &timeVal, NULL ); - #if 0 - if ( logRuntime ) - { +#if 0 + if ( logRuntime ) { static struct timeval logStart; subtractTime( &timeVal, &logStart ); snprintf( timeString, sizeof(timeString), "%ld.%03ld", timeVal.tv_sec, timeVal.tv_usec/1000 ); - } - else - { - #endif + } else { +#endif char *timePtr = timeString; timePtr += strftime( timePtr, sizeof(timeString), "%x %H:%M:%S", localtime(&timeVal.tv_sec) ); snprintf( timePtr, sizeof(timeString)-(timePtr-timeString), ".%06ld", timeVal.tv_usec ); - #if 0 +#if 0 } - #endif +#endif pid_t tid; #ifdef __FreeBSD__ - long lwpid; - thr_self(&lwpid); - tid = lwpid; + long lwpid; + thr_self(&lwpid); + tid = lwpid; if (tid < 0 ) // Thread/Process id #else #ifdef HAVE_SYSCALL - #ifdef __FreeBSD_kernel__ - if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id +#ifdef __FreeBSD_kernel__ + if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id - # else - // SOLARIS doesn't have SYS_gettid; don't assume - #ifdef SYS_gettid - if ( (tid = syscall(SYS_gettid)) < 0 ) // Thread/Process id - #endif // SYS_gettid - #endif +# else + // SOLARIS doesn't have SYS_gettid; don't assume +#ifdef SYS_gettid + if ( (tid = syscall(SYS_gettid)) < 0 ) // Thread/Process id +#endif // SYS_gettid +#endif #endif // HAVE_SYSCALL #endif - tid = getpid(); // Process id + tid = getpid(); // Process id char *logPtr = logString; logPtr += snprintf( logPtr, sizeof(logString), "%s %s[%d].%s-%s/%d [", - timeString, - mId.c_str(), - tid, - classString, - file, - line + timeString, + mId.c_str(), + tid, + classString, + file, + line ); char *syslogStart = logPtr; va_start( argPtr, fstring ); - if ( hex ) - { + if ( hex ) { unsigned char *data = va_arg( argPtr, unsigned char * ); int len = va_arg( argPtr, int ); int i; logPtr += snprintf( logPtr, sizeof(logString)-(logPtr-logString), "%d:", len ); - for ( i = 0; i < len; i++ ) - { + for ( i = 0; i < len; i++ ) { logPtr += snprintf( logPtr, sizeof(logString)-(logPtr-logString), " %02x", data[i] ); } - } - else - { + } else { logPtr += vsnprintf( logPtr, sizeof(logString)-(logPtr-logString), fstring, argPtr ); } va_end(argPtr); char *syslogEnd = logPtr; strncpy( logPtr, "]\n", sizeof(logString)-(logPtr-logString) ); - if ( level <= mTermLevel ) - { + if ( level <= mTermLevel ) { printf( "%s", logString ); fflush( stdout ); } - if ( level <= mFileLevel ) - { + if ( level <= mFileLevel ) { fprintf( mLogFileFP, "%s", logString ); if ( mFlush ) fflush( mLogFileFP ); } *syslogEnd = '\0'; - if ( level <= mDatabaseLevel ) - { + if ( level <= mDatabaseLevel ) { char sql[ZM_SQL_MED_BUFSIZ]; char escapedString[(strlen(syslogStart)*2)+1]; mysql_real_escape_string( &mDbConnection, escapedString, syslogStart, strlen(syslogStart) ); - + snprintf( sql, sizeof(sql), "insert into Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) values ( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line ); - if ( mysql_query( &mDbConnection, sql ) ) - { + if ( mysql_query( &mDbConnection, sql ) ) { + Level tempDatabaseLevel = mDatabaseLevel; databaseLevel( NOLOG ); - Error( "Can't insert log entry: %s", mysql_error( &mDbConnection ) ); + Error( "Can't insert log entry: sql(%s) error(%s)", sql, mysql_error( &mDbConnection ) ); + databaseLevel(tempDatabaseLevel); } } - if ( level <= mSyslogLevel ) - { + if ( level <= mSyslogLevel ) { int priority = smSyslogPriorities[level]; //priority |= LOG_DAEMON; - syslog( priority, "%s [%s]", classString, syslogStart ); + syslog( priority, "%s [%s] [%s]", classString, mId.c_str(), syslogStart ); } - if ( level <= FATAL ) - { + free(filecopy); + if ( level <= FATAL ) { if ( level <= PANIC ) abort(); exit( -1 ); @@ -627,8 +573,7 @@ void Logger::logPrint( bool hex, const char * const filepath, const int line, co } } -void logInit( const char *name, const Logger::Options &options ) -{ +void logInit( const char *name, const Logger::Options &options ) { if ( !Logger::smInstance ) Logger::smInstance = new Logger(); Logger::Options tempOptions = options; @@ -636,8 +581,7 @@ void logInit( const char *name, const Logger::Options &options ) Logger::smInstance->initialise( name, tempOptions ); } -void logTerm() -{ +void logTerm() { if ( Logger::smInstance ) delete Logger::smInstance; } diff --git a/src/zm_logger.h b/src/zm_logger.h index d30396056..e50b81ca5 100644 --- a/src/zm_logger.h +++ b/src/zm_logger.h @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef ZM_LOGGER_H diff --git a/src/zm_mem_utils.h b/src/zm_mem_utils.h index 351fa3ac8..dd0e2a3f9 100644 --- a/src/zm_mem_utils.h +++ b/src/zm_mem_utils.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_MEM_UTILS_H diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index f99ebd82d..392582a21 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include @@ -28,6 +28,7 @@ #include "zm_mpeg.h" #include "zm_signal.h" #include "zm_monitor.h" +#include "zm_video.h" #if ZM_HAS_V4L #include "zm_local_camera.h" #endif // ZM_HAS_V4L @@ -74,8 +75,7 @@ std::vector split(const std::string &s, char delim) { -Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id ) -{ +Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id ) { strncpy( name, p_name, sizeof(name) ); #if ZM_MEM_MAPPED @@ -94,15 +94,12 @@ Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id ) connected = false; } -Monitor::MonitorLink::~MonitorLink() -{ +Monitor::MonitorLink::~MonitorLink() { disconnect(); } -bool Monitor::MonitorLink::connect() -{ - if ( !last_connect_time || (time( 0 ) - last_connect_time) > 60 ) - { +bool Monitor::MonitorLink::connect() { + if ( !last_connect_time || (time( 0 ) - last_connect_time) > 60 ) { last_connect_time = time( 0 ); mem_size = sizeof(SharedData) + sizeof(TriggerData); @@ -110,8 +107,7 @@ bool Monitor::MonitorLink::connect() Debug( 1, "link.mem.size=%d", mem_size ); #if ZM_MEM_MAPPED map_fd = open( mem_file, O_RDWR, (mode_t)0600 ); - if ( map_fd < 0 ) - { + if ( map_fd < 0 ) { Debug( 3, "Can't open linked memory map file %s: %s", mem_file, strerror(errno) ); disconnect(); return( false ); @@ -124,44 +120,37 @@ bool Monitor::MonitorLink::connect() } struct stat map_stat; - if ( fstat( map_fd, &map_stat ) < 0 ) - { + if ( fstat( map_fd, &map_stat ) < 0 ) { Error( "Can't stat linked memory map file %s: %s", mem_file, strerror(errno) ); disconnect(); return( false ); } - if ( map_stat.st_size == 0 ) - { + if ( map_stat.st_size == 0 ) { Error( "Linked memory map file %s is empty: %s", mem_file, strerror(errno) ); disconnect(); return( false ); - } - else if ( map_stat.st_size < mem_size ) - { + } else if ( map_stat.st_size < mem_size ) { Error( "Got unexpected memory map file size %ld, expected %d", map_stat.st_size, mem_size ); disconnect(); return( false ); } mem_ptr = (unsigned char *)mmap( NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0 ); - if ( mem_ptr == MAP_FAILED ) - { + if ( mem_ptr == MAP_FAILED ) { Error( "Can't map file %s (%d bytes) to memory: %s", mem_file, mem_size, strerror(errno) ); disconnect(); return( false ); } #else // ZM_MEM_MAPPED shm_id = shmget( (config.shm_key&0xffff0000)|id, mem_size, 0700 ); - if ( shm_id < 0 ) - { + if ( shm_id < 0 ) { Debug( 3, "Can't shmget link memory: %s", strerror(errno) ); connected = false; return( false ); } mem_ptr = (unsigned char *)shmat( shm_id, 0, 0 ); - if ( mem_ptr < 0 ) - { + if ( mem_ptr < (void *)0 ) { Debug( 3, "Can't shmat link memory: %s", strerror(errno) ); connected = false; return( false ); @@ -171,8 +160,7 @@ bool Monitor::MonitorLink::connect() shared_data = (SharedData *)mem_ptr; trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); - if ( !shared_data->valid ) - { + if ( !shared_data->valid ) { Debug( 3, "Linked memory not initialised by capture daemon" ); disconnect(); return( false ); @@ -187,15 +175,12 @@ bool Monitor::MonitorLink::connect() return( false ); } -bool Monitor::MonitorLink::disconnect() -{ - if ( connected ) - { +bool Monitor::MonitorLink::disconnect() { + if ( connected ) { connected = false; #if ZM_MEM_MAPPED - if ( mem_ptr > 0 ) - { + if ( mem_ptr > (void *)0 ) { msync( mem_ptr, mem_size, MS_ASYNC ); munmap( mem_ptr, mem_size ); } @@ -205,25 +190,21 @@ bool Monitor::MonitorLink::disconnect() map_fd = -1; #else // ZM_MEM_MAPPED struct shmid_ds shm_data; - if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) - { + if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) { Debug( 3, "Can't shmctl: %s", strerror(errno) ); return( false ); } shm_id = 0; - if ( shm_data.shm_nattch <= 1 ) - { - if ( shmctl( shm_id, IPC_RMID, 0 ) < 0 ) - { + if ( shm_data.shm_nattch <= 1 ) { + if ( shmctl( shm_id, IPC_RMID, 0 ) < 0 ) { Debug( 3, "Can't shmctl: %s", strerror(errno) ); return( false ); } } - if ( shmdt( mem_ptr ) < 0 ) - { + if ( shmdt( mem_ptr ) < 0 ) { Debug( 3, "Can't shmdt: %s", strerror(errno) ); return( false ); } @@ -235,32 +216,24 @@ bool Monitor::MonitorLink::disconnect() return( true ); } -bool Monitor::MonitorLink::isAlarmed() -{ - if ( !connected ) - { +bool Monitor::MonitorLink::isAlarmed() { + if ( !connected ) { return( false ); } return( shared_data->state == ALARM ); } -bool Monitor::MonitorLink::inAlarm() -{ - if ( !connected ) - { +bool Monitor::MonitorLink::inAlarm() { + if ( !connected ) { return( false ); } return( shared_data->state == ALARM || shared_data->state == ALERT ); } -bool Monitor::MonitorLink::hasAlarmed() -{ - if ( shared_data->state == ALARM ) - { +bool Monitor::MonitorLink::hasAlarmed() { + if ( shared_data->state == ALARM ) { return( true ); - } - else if( shared_data->last_event != (unsigned int)last_event ) - { + } else if ( shared_data->last_event != (unsigned int)last_event ) { last_event = shared_data->last_event; } return( false ); @@ -276,6 +249,10 @@ Monitor::Monitor( Camera *p_camera, int p_orientation, unsigned int p_deinterlacing, + int p_savejpegs, + VideoWriter p_videowriter, + std::string p_encoderparams, + bool p_record_audio, const char *p_event_prefix, const char *p_label_format, const Coord &p_label_coord, @@ -310,6 +287,10 @@ Monitor::Monitor( height( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Width():p_camera->Height() ), orientation( (Orientation)p_orientation ), deinterlacing( p_deinterlacing ), + savejpegspref( p_savejpegs ), + videowriter( p_videowriter ), + encoderparams( p_encoderparams ), + record_audio( p_record_audio ), label_coord( p_label_coord ), label_size( p_label_size ), image_buffer_count( p_image_buffer_count ), @@ -350,21 +331,20 @@ Monitor::Monitor( // Change \n to actual line feeds char *token_ptr = label_format; const char *token_string = "\n"; - while( ( token_ptr = strstr( token_ptr, token_string ) ) ) - { - if ( *(token_ptr+1) ) - { + while( ( token_ptr = strstr( token_ptr, token_string ) ) ) { + if ( *(token_ptr+1) ) { *token_ptr = '\n'; token_ptr++; strcpy( token_ptr, token_ptr+1 ); - } - else - { + } else { *token_ptr = '\0'; break; } } + /* Parse encoder parameters */ + ParseEncoderParameters(encoderparams.c_str(), &encoderparamsvec); + fps = 0.0; event_count = 0; image_count = 0; @@ -391,9 +371,10 @@ Monitor::Monitor( mem_size = sizeof(SharedData) + sizeof(TriggerData) + + sizeof(VideoStoreData) //Information to pass back to the capture process + (image_buffer_count*sizeof(struct timeval)) + (image_buffer_count*camera->ImageSize()) - + 64; /* Padding used to permit aligning the images buffer to 16 byte boundary */ + + 64; /* Padding used to permit aligning the images buffer to 64 byte boundary */ Debug( 1, "mem.size=%d", mem_size ); mem_ptr = NULL; @@ -426,6 +407,10 @@ Monitor::Monitor( trigger_data->trigger_text[0] = 0; trigger_data->trigger_showtext[0] = 0; shared_data->valid = true; + video_store_data->recording = (struct timeval){0}; + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "nothing"); + video_store_data->size = sizeof(VideoStoreData); + //video_store_data->frameNumber = 0; } else if ( purpose == ANALYSIS ) { this->connect(); if ( ! mem_ptr ) exit(-1); @@ -435,16 +420,15 @@ Monitor::Monitor( shared_data->alarm_y = -1; } - if ( ( ! mem_ptr ) || ! shared_data->valid ) - { - if ( purpose != QUERY ) - { + if ( ( ! mem_ptr ) || ! shared_data->valid ) { + if ( purpose != QUERY ) { Error( "Shared data not initialised by capture daemon for monitor %s", name ); exit( -1 ); } } // Will this not happen every time a monitor is instantiated? Seems like all the calls to the Monitor constructor pass a zero for n_zones, then load zones after.. + // In my storage areas branch, I took this out.. and didn't notice any problems. if ( !n_zones ) { Debug( 1, "Monitor %s has no zones, adding one.", name ); n_zones = 1; @@ -460,8 +444,10 @@ Monitor::Monitor( Debug( 1, "Monitor %s LBF = '%s', LBX = %d, LBY = %d, LBS = %d", name, label_format, label_coord.X(), label_coord.Y(), label_size ); Debug( 1, "Monitor %s IBC = %d, WUC = %d, pEC = %d, PEC = %d, EAF = %d, FRI = %d, RBP = %d, ARBP = %d, FM = %d", name, image_buffer_count, warmup_count, pre_event_count, post_event_count, alarm_frame_count, fps_report_interval, ref_blend_perc, alarm_ref_blend_perc, track_motion ); - if ( purpose == ANALYSIS ) - { + //Set video recording flag for event start constructor and easy reference in code + videoRecording = ((GetOptVideoWriter() == H264PASSTHROUGH) && camera->SupportsNativeVideo()); + + if ( purpose == ANALYSIS ) { static char path[PATH_MAX]; strncpy( path, config.dir_events, sizeof(path) ); @@ -469,10 +455,8 @@ Monitor::Monitor( struct stat statbuf; errno = 0; stat( path, &statbuf ); - if ( errno == ENOENT || errno == ENOTDIR ) - { - if ( mkdir( path, 0755 ) ) - { + if ( errno == ENOENT || errno == ENOTDIR ) { + if ( mkdir( path, 0755 ) ) { Error( "Can't make %s: %s", path, strerror(errno)); } } @@ -481,10 +465,8 @@ Monitor::Monitor( errno = 0; stat( path, &statbuf ); - if ( errno == ENOENT || errno == ENOTDIR ) - { - if ( mkdir( path, 0755 ) ) - { + if ( errno == ENOENT || errno == ENOTDIR ) { + if ( mkdir( path, 0755 ) ) { Error( "Can't make %s: %s", path, strerror(errno)); } char temp_path[PATH_MAX]; @@ -498,8 +480,7 @@ Monitor::Monitor( } while( shared_data->last_write_index == (unsigned int)image_buffer_count - && shared_data->last_write_time == 0) - { + && shared_data->last_write_time == 0) { Warning( "Waiting for capture daemon" ); sleep( 1 ); } @@ -558,46 +539,42 @@ bool Monitor::connect() { exit( -1 ); } mem_ptr = (unsigned char *)shmat( shm_id, 0, 0 ); - if ( mem_ptr < 0 ) - { + if ( mem_ptr < (void *)0 ) { Error( "Can't shmat: %s", strerror(errno)); exit( -1 ); } #endif // ZM_MEM_MAPPED shared_data = (SharedData *)mem_ptr; trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); - struct timeval *shared_timestamps = (struct timeval *)((char *)trigger_data + sizeof(TriggerData)); + video_store_data = (VideoStoreData *)((char *)trigger_data + sizeof(TriggerData)); + struct timeval *shared_timestamps = (struct timeval *)((char *)video_store_data + sizeof(VideoStoreData)); unsigned char *shared_images = (unsigned char *)((char *)shared_timestamps + (image_buffer_count*sizeof(struct timeval))); - if(((unsigned long)shared_images % 16) != 0) { - /* Align images buffer to nearest 16 byte boundary */ - Debug(3,"Aligning shared memory images to the next 16 byte boundary"); - shared_images = (uint8_t*)((unsigned long)shared_images + (16 - ((unsigned long)shared_images % 16))); + if(((unsigned long)shared_images % 64) != 0) { + /* Align images buffer to nearest 64 byte boundary */ + Debug(3,"Aligning shared memory images to the next 64 byte boundary"); + shared_images = (uint8_t*)((unsigned long)shared_images + (64 - ((unsigned long)shared_images % 64))); } image_buffer = new Snapshot[image_buffer_count]; - for ( int i = 0; i < image_buffer_count; i++ ) - { + for ( int i = 0; i < image_buffer_count; i++ ) { image_buffer[i].timestamp = &(shared_timestamps[i]); image_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()]) ); image_buffer[i].image->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */ } - if ( (deinterlacing & 0xff) == 4) - { + if ( (deinterlacing & 0xff) == 4) { /* Four field motion adaptive deinterlacing in use */ /* Allocate a buffer for the next image */ next_buffer.image = new Image( width, height, camera->Colours(), camera->SubpixelOrder()); next_buffer.timestamp = new struct timeval; } - if ( ( purpose == ANALYSIS ) && analysis_fps ) - { + if ( ( purpose == ANALYSIS ) && analysis_fps ) { // Size of pre event buffer must be greater than pre_event_count // if alarm_frame_count > 1, because in this case the buffer contains // alarmed images that must be discarded when event is created pre_event_buffer_count = pre_event_count + alarm_frame_count - 1; pre_event_buffer = new Snapshot[pre_event_buffer_count]; - for ( int i = 0; i < pre_event_buffer_count; i++ ) - { + for ( int i = 0; i < pre_event_buffer_count; i++ ) { pre_event_buffer[i].timestamp = new struct timeval; pre_event_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder()); } @@ -606,8 +583,7 @@ bool Monitor::connect() { return true; } -Monitor::~Monitor() -{ +Monitor::~Monitor() { if ( timestamps ) { delete[] timestamps; timestamps = 0; @@ -616,29 +592,27 @@ Monitor::~Monitor() delete[] images; images = 0; } - if ( privacy_bitmask ) { - delete[] privacy_bitmask; - privacy_bitmask = NULL; - } + if ( privacy_bitmask ) { + delete[] privacy_bitmask; + privacy_bitmask = NULL; + } if ( mem_ptr ) { - if ( event ) + if ( event ) { Info( "%s: %03d - Closing event %d, shutting down", name, image_count, event->Id() ); - closeEvent(); + closeEvent(); + } - if ( (deinterlacing & 0xff) == 4) - { + if ( (deinterlacing & 0xff) == 4) { delete next_buffer.image; delete next_buffer.timestamp; } - for ( int i = 0; i < image_buffer_count; i++ ) - { + for ( int i = 0; i < image_buffer_count; i++ ) { delete image_buffer[i].image; } delete[] image_buffer; } // end if mem_ptr - for ( int i = 0; i < n_zones; i++ ) - { + for ( int i = 0; i < n_zones; i++ ) { delete zones[i]; } delete[] zones; @@ -646,24 +620,19 @@ Monitor::~Monitor() delete camera; if ( mem_ptr ) { - if ( purpose == ANALYSIS ) - { + if ( purpose == ANALYSIS ) { shared_data->state = state = IDLE; shared_data->last_read_index = image_buffer_count; shared_data->last_read_time = 0; - if ( analysis_fps ) - { - for ( int i = 0; i < pre_event_buffer_count; i++ ) - { + if ( analysis_fps ) { + for ( int i = 0; i < pre_event_buffer_count; i++ ) { delete pre_event_buffer[i].image; delete pre_event_buffer[i].timestamp; } delete[] pre_event_buffer; } - } - else if ( purpose == CAPTURE ) - { + } else if ( purpose == CAPTURE ) { shared_data->valid = false; memset( mem_ptr, 0, mem_size ); } @@ -674,6 +643,15 @@ Monitor::~Monitor() if ( munmap( mem_ptr, mem_size ) < 0 ) Fatal( "Can't munmap: %s", strerror(errno) ); close( map_fd ); + + if ( purpose == CAPTURE ) { + char mmap_path[PATH_MAX] = ""; + snprintf( mmap_path, sizeof(mmap_path), "%s/zm.mmap.%d", config.path_map, id ); + + if ( unlink( mmap_path ) < 0 ) { + Warning( "Can't unlink '%s': %s", mmap_path, strerror(errno) ); + } + } #else // ZM_MEM_MAPPED struct shmid_ds shm_data; if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) { @@ -690,8 +668,7 @@ Monitor::~Monitor() } // end if mem_ptr } -void Monitor::AddZones( int p_n_zones, Zone *p_zones[] ) -{ +void Monitor::AddZones( int p_n_zones, Zone *p_zones[] ) { for ( int i = 0; i < n_zones; i++ ) delete zones[i]; delete[] zones; @@ -699,19 +676,15 @@ void Monitor::AddZones( int p_n_zones, Zone *p_zones[] ) zones = p_zones; } -void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) -{ +void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) { if ( privacy_bitmask ) delete[] privacy_bitmask; privacy_bitmask = NULL; Image *privacy_image = NULL; - for ( int i = 0; i < n_zones; i++ ) - { - if ( p_zones[i]->IsPrivacy() ) - { - if ( !privacy_image ) - { + for ( int i = 0; i < n_zones; i++ ) { + if ( p_zones[i]->IsPrivacy() ) { + if ( !privacy_image ) { privacy_image = new Image( width, height, 1, ZM_SUBPIX_ORDER_NONE); privacy_image->Clear(); } @@ -723,20 +696,16 @@ void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) privacy_bitmask = privacy_image->Buffer(); } -Monitor::State Monitor::GetState() const -{ +Monitor::State Monitor::GetState() const { return( (State)shared_data->state ); } -int Monitor::GetImage( int index, int scale ) -{ - if ( index < 0 || index > image_buffer_count ) - { +int Monitor::GetImage( int index, int scale ) { + if ( index < 0 || index > image_buffer_count ) { index = shared_data->last_write_index; } - if ( index != image_buffer_count ) - { + if ( index != image_buffer_count ) { Image *image; // If we are going to be modifying the snapshot before writing, then we need to copy it if ( ( scale != ZM_SCALE_BASE ) || ( !config.timestamp_on_capture ) ) { @@ -763,75 +732,59 @@ int Monitor::GetImage( int index, int scale ) static char filename[PATH_MAX]; snprintf( filename, sizeof(filename), "Monitor%d.jpg", id ); image->WriteJpeg( filename ); - } - else - { + } else { Error( "Unable to generate image, no images in buffer" ); } return( 0 ); } -struct timeval Monitor::GetTimestamp( int index ) const -{ - if ( index < 0 || index > image_buffer_count ) - { +struct timeval Monitor::GetTimestamp( int index ) const { + if ( index < 0 || index > image_buffer_count ) { index = shared_data->last_write_index; } - if ( index != image_buffer_count ) - { + if ( index != image_buffer_count ) { Snapshot *snap = &image_buffer[index]; return( *(snap->timestamp) ); - } - else - { + } else { static struct timeval null_tv = { 0, 0 }; return( null_tv ); } } -unsigned int Monitor::GetLastReadIndex() const -{ +unsigned int Monitor::GetLastReadIndex() const { return( shared_data->last_read_index!=(unsigned int)image_buffer_count?shared_data->last_read_index:-1 ); } -unsigned int Monitor::GetLastWriteIndex() const -{ +unsigned int Monitor::GetLastWriteIndex() const { return( shared_data->last_write_index!=(unsigned int)image_buffer_count?shared_data->last_write_index:-1 ); } -unsigned int Monitor::GetLastEvent() const -{ +unsigned int Monitor::GetLastEvent() const { return( shared_data->last_event ); } -double Monitor::GetFPS() const -{ +double Monitor::GetFPS() const { int index1 = shared_data->last_write_index; - if ( index1 == image_buffer_count ) - { + if ( index1 == image_buffer_count ) { return( 0.0 ); } Snapshot *snap1 = &image_buffer[index1]; - if ( !snap1->timestamp || !snap1->timestamp->tv_sec ) - { + if ( !snap1->timestamp || !snap1->timestamp->tv_sec ) { return( 0.0 ); } struct timeval time1 = *snap1->timestamp; int image_count = image_buffer_count; int index2 = (index1+1)%image_buffer_count; - if ( index2 == image_buffer_count ) - { + if ( index2 == image_buffer_count ) { return( 0.0 ); } Snapshot *snap2 = &image_buffer[index2]; - while ( !snap2->timestamp || !snap2->timestamp->tv_sec ) - { - if ( index1 == index2 ) - { + while ( !snap2->timestamp || !snap2->timestamp->tv_sec ) { + if ( index1 == index2 ) { return( 0.0 ); } index2 = (index2+1)%image_buffer_count; @@ -844,143 +797,110 @@ double Monitor::GetFPS() const double curr_fps = image_count/time_diff; - if ( curr_fps < 0.0 ) - { + if ( curr_fps < 0.0 ) { //Error( "Negative FPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d", curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count ); return( 0.0 ); } return( curr_fps ); } -useconds_t Monitor::GetAnalysisRate() -{ +useconds_t Monitor::GetAnalysisRate() { double capturing_fps = GetFPS(); - if ( !analysis_fps ) - { + if ( !analysis_fps ) { return( 0 ); - } - else if ( analysis_fps > capturing_fps ) - { + } else if ( analysis_fps > capturing_fps ) { Warning( "Analysis fps (%.2f) is greater than capturing fps (%.2f)", analysis_fps, capturing_fps ); return( 0 ); - } - else - { + } else { return( ( 1000000 / analysis_fps ) - ( 1000000 / capturing_fps ) ); } } -void Monitor::UpdateAdaptiveSkip() -{ - if ( config.opt_adaptive_skip ) - { +void Monitor::UpdateAdaptiveSkip() { + if ( config.opt_adaptive_skip ) { double capturing_fps = GetFPS(); - if ( adaptive_skip && analysis_fps && ( analysis_fps < capturing_fps ) ) - { + if ( adaptive_skip && analysis_fps && ( analysis_fps < capturing_fps ) ) { Info( "Analysis fps (%.2f) is lower than capturing fps (%.2f), disabling adaptive skip feature", analysis_fps, capturing_fps ); adaptive_skip = false; - } - else if ( !adaptive_skip && ( !analysis_fps || ( analysis_fps >= capturing_fps ) ) ) - { + } else if ( !adaptive_skip && ( !analysis_fps || ( analysis_fps >= capturing_fps ) ) ) { Info( "Enabling adaptive skip feature" ); adaptive_skip = true; } - } - else - { + } else { adaptive_skip = false; } } -void Monitor::ForceAlarmOn( int force_score, const char *force_cause, const char *force_text ) -{ +void Monitor::ForceAlarmOn( int force_score, const char *force_cause, const char *force_text ) { trigger_data->trigger_state = TRIGGER_ON; trigger_data->trigger_score = force_score; strncpy( trigger_data->trigger_cause, force_cause, sizeof(trigger_data->trigger_cause) ); strncpy( trigger_data->trigger_text, force_text, sizeof(trigger_data->trigger_text) ); } -void Monitor::ForceAlarmOff() -{ +void Monitor::ForceAlarmOff() { trigger_data->trigger_state = TRIGGER_OFF; } -void Monitor::CancelForced() -{ +void Monitor::CancelForced() { trigger_data->trigger_state = TRIGGER_CANCEL; } -void Monitor::actionReload() -{ +void Monitor::actionReload() { shared_data->action |= RELOAD; } -void Monitor::actionEnable() -{ +void Monitor::actionEnable() { shared_data->action |= RELOAD; static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "update Monitors set Enabled = 1 where Id = '%d'", id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } } -void Monitor::actionDisable() -{ +void Monitor::actionDisable() { shared_data->action |= RELOAD; static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "update Monitors set Enabled = 0 where Id = '%d'", id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } } -void Monitor::actionSuspend() -{ +void Monitor::actionSuspend() { shared_data->action |= SUSPEND; } -void Monitor::actionResume() -{ +void Monitor::actionResume() { shared_data->action |= RESUME; } -int Monitor::actionBrightness( int p_brightness ) -{ - if ( purpose != CAPTURE ) - { - if ( p_brightness >= 0 ) - { +int Monitor::actionBrightness( int p_brightness ) { + if ( purpose != CAPTURE ) { + if ( p_brightness >= 0 ) { shared_data->brightness = p_brightness; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & SET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to set brightness" ); return( -1 ); } } - } - else - { + } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & GET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to get brightness" ); return( -1 ); } @@ -991,36 +911,27 @@ int Monitor::actionBrightness( int p_brightness ) return( camera->Brightness( p_brightness ) ); } -int Monitor::actionContrast( int p_contrast ) -{ - if ( purpose != CAPTURE ) - { - if ( p_contrast >= 0 ) - { +int Monitor::actionContrast( int p_contrast ) { + if ( purpose != CAPTURE ) { + if ( p_contrast >= 0 ) { shared_data->contrast = p_contrast; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & SET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to set contrast" ); return( -1 ); } } - } - else - { + } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & GET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to get contrast" ); return( -1 ); } @@ -1031,36 +942,27 @@ int Monitor::actionContrast( int p_contrast ) return( camera->Contrast( p_contrast ) ); } -int Monitor::actionHue( int p_hue ) -{ - if ( purpose != CAPTURE ) - { - if ( p_hue >= 0 ) - { +int Monitor::actionHue( int p_hue ) { + if ( purpose != CAPTURE ) { + if ( p_hue >= 0 ) { shared_data->hue = p_hue; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & SET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to set hue" ); return( -1 ); } } - } - else - { + } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & GET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to get hue" ); return( -1 ); } @@ -1071,36 +973,27 @@ int Monitor::actionHue( int p_hue ) return( camera->Hue( p_hue ) ); } -int Monitor::actionColour( int p_colour ) -{ - if ( purpose != CAPTURE ) - { - if ( p_colour >= 0 ) - { +int Monitor::actionColour( int p_colour ) { + if ( purpose != CAPTURE ) { + if ( p_colour >= 0 ) { shared_data->colour = p_colour; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & SET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to set colour" ); return( -1 ); } } - } - else - { + } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & GET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to get colour" ); return( -1 ); } @@ -1111,16 +1004,13 @@ int Monitor::actionColour( int p_colour ) return( camera->Colour( p_colour ) ); } -void Monitor::DumpZoneImage( const char *zone_string ) -{ +void Monitor::DumpZoneImage( const char *zone_string ) { int exclude_id = 0; int extra_colour = 0; Polygon extra_zone; - if ( zone_string ) - { - if ( !Zone::ParseZoneString( zone_string, exclude_id, extra_colour, extra_zone ) ) - { + if ( zone_string ) { + if ( !Zone::ParseZoneString( zone_string, exclude_id, extra_colour, extra_zone ) ) { Error( "Failed to parse zone string, ignoring" ); } } @@ -1134,36 +1024,23 @@ void Monitor::DumpZoneImage( const char *zone_string ) zone_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB ); } - for( int i = 0; i < n_zones; i++ ) - { + for( int i = 0; i < n_zones; i++ ) { if ( exclude_id && (!extra_colour || extra_zone.getNumCoords()) && zones[i]->Id() == exclude_id ) continue; Rgb colour; - if ( exclude_id && !extra_zone.getNumCoords() && zones[i]->Id() == exclude_id ) - { + if ( exclude_id && !extra_zone.getNumCoords() && zones[i]->Id() == exclude_id ) { colour = extra_colour; - } - else - { - if ( zones[i]->IsActive() ) - { + } else { + if ( zones[i]->IsActive() ) { colour = RGB_RED; - } - else if ( zones[i]->IsInclusive() ) - { + } else if ( zones[i]->IsInclusive() ) { colour = RGB_ORANGE; - } - else if ( zones[i]->IsExclusive() ) - { + } else if ( zones[i]->IsExclusive() ) { colour = RGB_PURPLE; - } - else if ( zones[i]->IsPreclusive() ) - { + } else if ( zones[i]->IsPreclusive() ) { colour = RGB_BLUE; - } - else - { + } else { colour = RGB_WHITE; } } @@ -1171,8 +1048,7 @@ void Monitor::DumpZoneImage( const char *zone_string ) zone_image.Outline( colour, zones[i]->GetPolygon() ); } - if ( extra_zone.getNumCoords() ) - { + if ( extra_zone.getNumCoords() ) { zone_image.Fill( extra_colour, 2, extra_zone ); zone_image.Outline( extra_colour, extra_zone ); } @@ -1182,10 +1058,8 @@ void Monitor::DumpZoneImage( const char *zone_string ) zone_image.WriteJpeg( filename ); } -void Monitor::DumpImage( Image *dump_image ) const -{ - if ( image_count && !(image_count%10) ) - { +void Monitor::DumpImage( Image *dump_image ) const { + if ( image_count && !(image_count%10) ) { static char filename[PATH_MAX]; static char new_filename[PATH_MAX]; snprintf( filename, sizeof(filename), "Monitor%d.jpg", id ); @@ -1195,8 +1069,7 @@ void Monitor::DumpImage( Image *dump_image ) const } } -bool Monitor::CheckSignal( const Image *image ) -{ +bool Monitor::CheckSignal( const Image *image ) { static bool static_undef = true; /* RGB24 colors */ static uint8_t red_val; @@ -1206,10 +1079,8 @@ bool Monitor::CheckSignal( const Image *image ) static Rgb colour_val; /* RGB32 color */ static int usedsubpixorder; - if ( config.signal_check_points > 0 ) - { - if ( static_undef ) - { + if ( config.signal_check_points > 0 ) { + if ( static_undef ) { static_undef = false; usedsubpixorder = camera->SubpixelOrder(); colour_val = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); /* HTML colour code is actually BGR in memory, we want RGB */ @@ -1226,10 +1097,8 @@ bool Monitor::CheckSignal( const Image *image ) int colours = image->Colours(); int index = 0; - for ( int i = 0; i < config.signal_check_points; i++ ) - { - while( true ) - { + for ( int i = 0; i < config.signal_check_points; i++ ) { + while( true ) { index = (int)(((long long)rand()*(long long)(pixels-1))/RAND_MAX); if ( !config.timestamp_on_capture || !label_format[0] ) break; @@ -1238,65 +1107,63 @@ bool Monitor::CheckSignal( const Image *image ) break; } - if(colours == ZM_COLOUR_GRAY8) { - if ( *(buffer+index) != grayscale_val ) - return true; - - } else if(colours == ZM_COLOUR_RGB24) { - const uint8_t *ptr = buffer+(index*colours); - - if ( usedsubpixorder == ZM_SUBPIX_ORDER_BGR) { - if ( (RED_PTR_BGRA(ptr) != red_val) || (GREEN_PTR_BGRA(ptr) != green_val) || (BLUE_PTR_BGRA(ptr) != blue_val) ) - return true; - } else { - /* Assume RGB */ - if ( (RED_PTR_RGBA(ptr) != red_val) || (GREEN_PTR_RGBA(ptr) != green_val) || (BLUE_PTR_RGBA(ptr) != blue_val) ) + if(colours == ZM_COLOUR_GRAY8) { + if ( *(buffer+index) != grayscale_val ) return true; + + } else if(colours == ZM_COLOUR_RGB24) { + const uint8_t *ptr = buffer+(index*colours); + + if ( usedsubpixorder == ZM_SUBPIX_ORDER_BGR) { + if ( (RED_PTR_BGRA(ptr) != red_val) || (GREEN_PTR_BGRA(ptr) != green_val) || (BLUE_PTR_BGRA(ptr) != blue_val) ) + return true; + } else { + /* Assume RGB */ + if ( (RED_PTR_RGBA(ptr) != red_val) || (GREEN_PTR_RGBA(ptr) != green_val) || (BLUE_PTR_RGBA(ptr) != blue_val) ) + return true; + } + + } else if(colours == ZM_COLOUR_RGB32) { + if ( usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR) { + if ( ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val) ) + return true; + } else { + /* Assume RGBA or BGRA */ + if ( RGBA_BGRA_ZEROALPHA(*(((const Rgb*)buffer)+index)) != RGBA_BGRA_ZEROALPHA(colour_val) ) + return true; + } } - - } else if(colours == ZM_COLOUR_RGB32) { - if ( usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR) { - if ( ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val) ) - return true; - } else { - /* Assume RGBA or BGRA */ - if ( RGBA_BGRA_ZEROALPHA(*(((const Rgb*)buffer)+index)) != RGBA_BGRA_ZEROALPHA(colour_val) ) - return true; - } - } - + } return( false ); } return( true ); } -bool Monitor::Analyse() -{ - if ( shared_data->last_read_index == shared_data->last_write_index ) - { +bool Monitor::Analyse() { + if ( shared_data->last_read_index == shared_data->last_write_index ) { + // I wonder how often this happens. Maybe if this happens we should sleep or something? return( false ); } struct timeval now; gettimeofday( &now, NULL ); - if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) - { + if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) { fps = double(fps_report_interval)/(now.tv_sec-last_fps_time); Info( "%s: %d - Analysing at %.2f fps", name, image_count, fps ); last_fps_time = now.tv_sec; } int index; - if ( adaptive_skip ) - { + if ( adaptive_skip ) { int read_margin = shared_data->last_read_index - shared_data->last_write_index; if ( read_margin < 0 ) read_margin += image_buffer_count; int step = 1; - if ( read_margin > 0 ) - { + // Isn't read_margin always > 0 here? + if ( read_margin > 0 ) { + // TODO explain this so... 90% of image buffer / 50% of read margin? step = (9*image_buffer_count)/(5*read_margin); } @@ -1304,21 +1171,15 @@ bool Monitor::Analyse() if ( pending_frames < 0 ) pending_frames += image_buffer_count; Debug( 4, "RI:%d, WI: %d, PF = %d, RM = %d, Step = %d", shared_data->last_read_index, shared_data->last_write_index, pending_frames, read_margin, step ); - if ( step <= pending_frames ) - { + if ( step <= pending_frames ) { index = (shared_data->last_read_index+step)%image_buffer_count; - } - else - { - if ( pending_frames ) - { + } else { + if ( pending_frames ) { Warning( "Approaching buffer overrun, consider slowing capture, simplifying analysis or increasing ring buffer size" ); } index = shared_data->last_write_index%image_buffer_count; } - } - else - { + } else { index = shared_data->last_write_index%image_buffer_count; } @@ -1326,32 +1187,25 @@ bool Monitor::Analyse() struct timeval *timestamp = snap->timestamp; Image *snap_image = snap->image; - if ( shared_data->action ) - { - if ( shared_data->action & RELOAD ) - { + if ( shared_data->action ) { + if ( shared_data->action & RELOAD ) { Info( "Received reload indication at count %d", image_count ); shared_data->action &= ~RELOAD; Reload(); } - if ( shared_data->action & SUSPEND ) - { - if ( Active() ) - { + if ( shared_data->action & SUSPEND ) { + if ( Active() ) { Info( "Received suspend indication at count %d", image_count ); shared_data->active = false; //closeEvent(); } - if ( config.max_suspend_time ) - { + if ( config.max_suspend_time ) { auto_resume_time = now.tv_sec + config.max_suspend_time; } shared_data->action &= ~SUSPEND; } - if ( shared_data->action & RESUME ) - { - if ( Enabled() && !Active() ) - { + if ( shared_data->action & RESUME ) { + if ( Enabled() && !Active() ) { Info( "Received resume indication at count %d", image_count ); shared_data->active = true; ref_image = *snap_image; @@ -1360,9 +1214,9 @@ bool Monitor::Analyse() } shared_data->action &= ~RESUME; } - } - if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) - { + } // end if shared_data->action + + if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) { Info( "Auto resuming at count %d", image_count ); shared_data->active = true; ref_image = *snap_image; @@ -1374,31 +1228,27 @@ bool Monitor::Analyse() static int last_section_mod = 0; static bool last_signal; - if ( static_undef ) - { + if ( static_undef ) { +// Sure would be nice to be able to assume that these were already initialized. It's just 1 compare/branch, but really not neccessary. static_undef = false; timestamps = new struct timeval *[pre_event_count]; images = new Image *[pre_event_count]; last_signal = shared_data->signal; } - if ( Enabled() ) - { + if ( Enabled() ) { bool signal = shared_data->signal; bool signal_change = (signal != last_signal); - if ( trigger_data->trigger_state != TRIGGER_OFF ) - { + + if ( trigger_data->trigger_state != TRIGGER_OFF ) { unsigned int score = 0; - if ( Ready() ) - { + if ( Ready() ) { std::string cause; Event::StringSetMap noteSetMap; - if ( trigger_data->trigger_state == TRIGGER_ON ) - { + if ( trigger_data->trigger_state == TRIGGER_ON ) { score += trigger_data->trigger_score; - if ( !event ) - { + if ( !event ) { if ( cause.length() ) cause += ", "; cause += trigger_data->trigger_cause; @@ -1407,25 +1257,21 @@ bool Monitor::Analyse() noteSet.insert( trigger_data->trigger_text ); noteSetMap[trigger_data->trigger_cause] = noteSet; } - if ( signal_change ) - { + if ( signal_change ) { const char *signalText; - if ( !signal ) + if ( !signal ) { signalText = "Lost"; - else - { + } else { signalText = "Reacquired"; score += 100; } Warning( "%s: %s", SIGNAL_CAUSE, signalText ); - if ( event && !signal ) - { + if ( event && !signal ) { Info( "%s: %03d - Closing event %d, signal loss", name, image_count, event->Id() ); closeEvent(); last_section_mod = 0; } - if ( !event ) - { + if ( !event ) { if ( cause.length() ) cause += ", "; cause += SIGNAL_CAUSE; @@ -1436,28 +1282,22 @@ bool Monitor::Analyse() shared_data->state = state = IDLE; shared_data->active = signal; ref_image = *snap_image; - } - else if ( signal && Active() && (function == MODECT || function == MOCORD) ) - { + + } else if ( signal && Active() && (function == MODECT || function == MOCORD) ) { Event::StringSet zoneSet; int motion_score = last_motion_score; - if ( !(image_count % (motion_frame_skip+1) ) ) - { + if ( !(image_count % (motion_frame_skip+1) ) ) { // Get new score. motion_score = last_motion_score = DetectMotion( *snap_image, zoneSet ); } //int motion_score = DetectBlack( *snap_image, zoneSet ); - if ( motion_score ) - { - if ( !event ) - { + if ( motion_score ) { + if ( !event ) { score += motion_score; if ( cause.length() ) cause += ", "; cause += MOTION_CAUSE; - } - else - { + } else { score += motion_score; } noteSetMap[MOTION_CAUSE] = zoneSet; @@ -1465,20 +1305,14 @@ bool Monitor::Analyse() } shared_data->active = signal; } - if ( (!signal_change && signal) && n_linked_monitors > 0 ) - { + if ( (!signal_change && signal) && n_linked_monitors > 0 ) { bool first_link = true; Event::StringSet noteSet; - for ( int i = 0; i < n_linked_monitors; i++ ) - { - if ( linked_monitors[i]->isConnected() ) - { - if ( linked_monitors[i]->hasAlarmed() ) - { - if ( !event ) - { - if ( first_link ) - { + for ( int i = 0; i < n_linked_monitors; i++ ) { + if ( linked_monitors[i]->isConnected() ) { + if ( linked_monitors[i]->hasAlarmed() ) { + if ( !event ) { + if ( first_link ) { if ( cause.length() ) cause += ", "; cause += LINKED_CAUSE; @@ -1488,146 +1322,125 @@ bool Monitor::Analyse() noteSet.insert( linked_monitors[i]->Name() ); score += 50; } - } - else - { + } else { linked_monitors[i]->connect(); } } if ( noteSet.size() > 0 ) noteSetMap[LINKED_CAUSE] = noteSet; } - if ( (!signal_change && signal) && (function == RECORD || function == MOCORD) ) - { - if ( event ) - { + //TODO: What happens is the event closes and sets recording to false then recording to true again so quickly that our capture daemon never picks it up. Maybe need a refresh flag? + if ( (!signal_change && signal) && (function == RECORD || function == MOCORD) ) { + if ( event ) { + //TODO: We shouldn't have to do this every time. Not sure why it clears itself if this isn't here?? + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); int section_mod = timestamp->tv_sec%section_length; - if ( section_mod < last_section_mod ) - { - if ( state == IDLE || state == TAPE || event_close_mode == CLOSE_TIME ) - { - if ( state == TAPE ) - { + if ( section_mod < last_section_mod ) { + if ( state == IDLE || state == TAPE || event_close_mode == CLOSE_TIME ) { + if ( state == TAPE ) { shared_data->state = state = IDLE; Info( "%s: %03d - Closing event %d, section end", name, image_count, event->Id() ) - } - else + } else { Info( "%s: %03d - Closing event %d, section end forced ", name, image_count, event->Id() ); + } closeEvent(); last_section_mod = 0; } - } - else - { + } else { last_section_mod = section_mod; } } - if ( !event ) - { + if ( !event ) { // Create event - event = new Event( this, *timestamp, "Continuous", noteSetMap ); + event = new Event( this, *timestamp, "Continuous", noteSetMap, videoRecording ); shared_data->last_event = event->Id(); + //set up video store data + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); + video_store_data->recording = event->StartTime(); Info( "%s: %03d - Opening new event %d, section start", name, image_count, event->Id() ); /* To prevent cancelling out an existing alert\prealarm\alarm state */ - if ( state == IDLE ) - { + if ( state == IDLE ) { shared_data->state = state = TAPE; } //if ( config.overlap_timed_events ) - if ( false ) - { + if ( false ) { int pre_index; int pre_event_images = pre_event_count; - if ( analysis_fps ) - { + if ( analysis_fps ) { // If analysis fps is set, // compute the index for pre event images in the dedicated buffer pre_index = image_count%pre_event_buffer_count; // Seek forward the next filled slot in to the buffer (oldest data) // from the current position - while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) - { + while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) { pre_index = (pre_index + 1)%pre_event_buffer_count; // Slot is empty, removing image from counter pre_event_images--; } - } - else - { + } else { // If analysis fps is not set (analysis performed at capturing framerate), // compute the index for pre event images in the capturing buffer pre_index = ((index + image_buffer_count) - pre_event_count)%image_buffer_count; // Seek forward the next filled slot in to the buffer (oldest data) // from the current position - while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) - { + while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) { pre_index = (pre_index + 1)%image_buffer_count; // Slot is empty, removing image from counter pre_event_images--; } } - if ( pre_event_images ) - { - if ( analysis_fps ) - for ( int i = 0; i < pre_event_images; i++ ) - { + if ( pre_event_images ) { + if ( analysis_fps ) { + for ( int i = 0; i < pre_event_images; i++ ) { timestamps[i] = pre_event_buffer[pre_index].timestamp; images[i] = pre_event_buffer[pre_index].image; pre_index = (pre_index + 1)%pre_event_buffer_count; } - else - for ( int i = 0; i < pre_event_images; i++ ) - { + } else { + for ( int i = 0; i < pre_event_images; i++ ) { timestamps[i] = image_buffer[pre_index].timestamp; images[i] = image_buffer[pre_index].image; pre_index = (pre_index + 1)%image_buffer_count; } + } event->AddFrames( pre_event_images, images, timestamps ); } - } - } + } // end if false or config.overlap_timed_events + } // end if ! event } - if ( score ) - { - if ( (state == IDLE || state == TAPE || state == PREALARM ) ) - { - if ( Event::PreAlarmCount() >= (alarm_frame_count-1) ) - { + if ( score ) { + if ( (state == IDLE || state == TAPE || state == PREALARM ) ) { + if ( Event::PreAlarmCount() >= (alarm_frame_count-1) ) { Info( "%s: %03d - Gone into alarm state", name, image_count ); shared_data->state = state = ALARM; - if ( signal_change || (function != MOCORD && state != ALERT) ) - { + if ( signal_change || (function != MOCORD && state != ALERT) ) { int pre_index; int pre_event_images = pre_event_count; - if ( analysis_fps ) - { + if ( analysis_fps ) { // If analysis fps is set, // compute the index for pre event images in the dedicated buffer pre_index = image_count%pre_event_buffer_count; // Seek forward the next filled slot in to the buffer (oldest data) // from the current position - while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) - { + while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) { pre_index = (pre_index + 1)%pre_event_buffer_count; // Slot is empty, removing image from counter pre_event_images--; } event = new Event( this, *(pre_event_buffer[pre_index].timestamp), cause, noteSetMap ); - } - else - { + } else { // If analysis fps is not set (analysis performed at capturing framerate), // compute the index for pre event images in the capturing buffer if ( alarm_frame_count > 1 ) @@ -1637,8 +1450,7 @@ bool Monitor::Analyse() // Seek forward the next filled slot in to the buffer (oldest data) // from the current position - while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) - { + while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) { pre_index = (pre_index + 1)%image_buffer_count; // Slot is empty, removing image from counter pre_event_images--; @@ -1647,132 +1459,100 @@ bool Monitor::Analyse() event = new Event( this, *(image_buffer[pre_index].timestamp), cause, noteSetMap ); } shared_data->last_event = event->Id(); + //set up video store data + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); + video_store_data->recording = event->StartTime(); Info( "%s: %03d - Opening new event %d, alarm start", name, image_count, event->Id() ); - if ( pre_event_images ) - { - if ( analysis_fps ) - for ( int i = 0; i < pre_event_images; i++ ) - { + if ( pre_event_images ) { + if ( analysis_fps ) { + for ( int i = 0; i < pre_event_images; i++ ) { timestamps[i] = pre_event_buffer[pre_index].timestamp; images[i] = pre_event_buffer[pre_index].image; pre_index = (pre_index + 1)%pre_event_buffer_count; } - else - for ( int i = 0; i < pre_event_images; i++ ) - { + } else { + for ( int i = 0; i < pre_event_images; i++ ) { timestamps[i] = image_buffer[pre_index].timestamp; images[i] = image_buffer[pre_index].image; pre_index = (pre_index + 1)%image_buffer_count; } + } event->AddFrames( pre_event_images, images, timestamps ); } - if ( alarm_frame_count ) - { + if ( alarm_frame_count ) { event->SavePreAlarmFrames(); } } - } - else if ( state != PREALARM ) - { + } else if ( state != PREALARM ) { Info( "%s: %03d - Gone into prealarm state", name, image_count ); shared_data->state = state = PREALARM; } - } - else if ( state == ALERT ) - { + } else if ( state == ALERT ) { Info( "%s: %03d - Gone back into alarm state", name, image_count ); shared_data->state = state = ALARM; } last_alarm_count = image_count; - } - else - { - if ( state == ALARM ) - { + } else { + if ( state == ALARM ) { Info( "%s: %03d - Gone into alert state", name, image_count ); shared_data->state = state = ALERT; - } - else if ( state == ALERT ) - { - if ( image_count-last_alarm_count > post_event_count ) - { + } else if ( state == ALERT ) { + if ( image_count-last_alarm_count > post_event_count ) { Info( "%s: %03d - Left alarm state (%d) - %d(%d) images", name, image_count, event->Id(), event->Frames(), event->AlarmFrames() ); //if ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE ) - if ( function != MOCORD || event_close_mode == CLOSE_ALARM ) - { + if ( function != MOCORD || event_close_mode == CLOSE_ALARM ) { shared_data->state = state = IDLE; Info( "%s: %03d - Closing event %d, alarm end%s", name, image_count, event->Id(), (function==MOCORD)?", section truncated":"" ); closeEvent(); - } - else - { + } else { shared_data->state = state = TAPE; } } } - if ( state == PREALARM ) - { - if ( function != MOCORD ) - { + if ( state == PREALARM ) { + if ( function != MOCORD ) { shared_data->state = state = IDLE; - } - else - { + } else { shared_data->state = state = TAPE; } } if ( Event::PreAlarmCount() ) Event::EmptyPreAlarmFrames(); } - if ( state != IDLE ) - { - if ( state == PREALARM || state == ALARM ) - { - if ( config.create_analysis_images ) - { + if ( state != IDLE ) { + if ( state == PREALARM || state == ALARM ) { + if ( config.create_analysis_images ) { bool got_anal_image = false; alarm_image.Assign( *snap_image ); - for( int i = 0; i < n_zones; i++ ) - { - if ( zones[i]->Alarmed() ) - { - if ( zones[i]->AlarmImage() ) - { + for( int i = 0; i < n_zones; i++ ) { + if ( zones[i]->Alarmed() ) { + if ( zones[i]->AlarmImage() ) { alarm_image.Overlay( *(zones[i]->AlarmImage()) ); got_anal_image = true; } - if ( config.record_event_stats && state == ALARM ) - { + if ( config.record_event_stats && state == ALARM ) { zones[i]->RecordStats( event ); } } } - if ( got_anal_image ) - { + if ( got_anal_image ) { if ( state == PREALARM ) Event::AddPreAlarmFrame( snap_image, *timestamp, score, &alarm_image ); else event->AddFrame( snap_image, *timestamp, score, &alarm_image ); - } - else - { + } else { if ( state == PREALARM ) Event::AddPreAlarmFrame( snap_image, *timestamp, score ); else event->AddFrame( snap_image, *timestamp, score ); } - } - else - { - for( int i = 0; i < n_zones; i++ ) - { - if ( zones[i]->Alarmed() ) - { - if ( config.record_event_stats && state == ALARM ) - { + } else { + for( int i = 0; i < n_zones; i++ ) { + if ( zones[i]->Alarmed() ) { + if ( config.record_event_stats && state == ALARM ) { zones[i]->RecordStats( event ); } } @@ -1784,42 +1564,38 @@ bool Monitor::Analyse() } if ( event && noteSetMap.size() > 0 ) event->updateNotes( noteSetMap ); - } - else if ( state == ALERT ) - { + } else if ( state == ALERT ) { event->AddFrame( snap_image, *timestamp ); if ( noteSetMap.size() > 0 ) event->updateNotes( noteSetMap ); - } - else if ( state == TAPE ) - { - if ( !(image_count%(frame_skip+1)) ) - { - if ( config.bulk_frame_interval > 1 ) - { + } else if ( state == TAPE ) { + //Video Storage: activate only for supported cameras. Event::AddFrame knows whether or not we are recording video and saves frames accordingly + //if((GetOptVideoWriter() == 2) && camera->SupportsNativeVideo()) { + // I don't think this is required, and causes problems, as the event file hasn't been setup yet. + //Warning("In state TAPE, + //video_store_data->recording = event->StartTime(); + //} + + if ( !(image_count%(frame_skip+1)) ) { + if ( config.bulk_frame_interval > 1 ) { event->AddFrame( snap_image, *timestamp, (event->Frames()AddFrame( snap_image, *timestamp ); } } } - } + } // end if ! IDLE } - } - else - { - if ( event ) - { + } else { + if ( event ) { Info( "%s: %03d - Closing event %d, trigger off", name, image_count, event->Id() ); closeEvent(); } shared_data->state = state = IDLE; last_section_mod = 0; - } - if ( (!signal_change && signal) && (function == MODECT || function == MOCORD) ) - { + } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) + + if ( (!signal_change && signal) && (function == MODECT || function == MOCORD) ) { if ( state == ALARM ) { ref_image.Blend( *snap_image, alarm_ref_blend_perc ); } else { @@ -1827,14 +1603,13 @@ bool Monitor::Analyse() } } last_signal = signal; - } + } // end if Enabled() shared_data->last_read_index = index%image_buffer_count; //shared_data->last_read_time = image_buffer[index].timestamp->tv_sec; shared_data->last_read_time = now.tv_sec; - if ( analysis_fps ) - { + if ( analysis_fps ) { // If analysis fps is set, add analysed image to dedicated pre event buffer int pre_index = image_count%pre_event_buffer_count; pre_event_buffer[pre_index].image->Assign(*snap->image); @@ -1846,8 +1621,7 @@ bool Monitor::Analyse() return( true ); } -void Monitor::Reload() -{ +void Monitor::Reload() { Debug( 1, "Reloading monitor %s", name ); if ( event ) @@ -1856,29 +1630,26 @@ void Monitor::Reload() closeEvent(); static char sql[ZM_SQL_MED_BUFSIZ]; + // This seems to have fallen out of date. snprintf( sql, sizeof(sql), "select Function+0, Enabled, LinkedMonitors, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, WarmupCount, PreEventCount, PostEventCount, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Id = '%d'", id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } int n_monitors = mysql_num_rows( result ); - if ( n_monitors != 1 ) - { + if ( n_monitors != 1 ) { Error( "Bogus number of monitors, %d, returned. Can't reload", n_monitors ); return; } - if ( MYSQL_ROW dbrow = mysql_fetch_row( result ) ) - { + if ( MYSQL_ROW dbrow = mysql_fetch_row( result ) ) { int index = 0; function = (Function)atoi(dbrow[index++]); enabled = atoi(dbrow[index++]); @@ -1918,8 +1689,7 @@ void Monitor::Reload() ReloadLinkedMonitors( p_linked_monitors ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -1928,11 +1698,9 @@ void Monitor::Reload() ReloadZones(); } -void Monitor::ReloadZones() -{ +void Monitor::ReloadZones() { Debug( 1, "Reloading zones for monitor %s", name ); - for( int i = 0; i < n_zones; i++ ) - { + for( int i = 0; i < n_zones; i++ ) { delete zones[i]; } delete[] zones; @@ -1941,13 +1709,10 @@ void Monitor::ReloadZones() //DumpZoneImage(); } -void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) -{ +void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) { Debug( 1, "Reloading linked monitors for monitor %s, '%s'", name, p_linked_monitors ); - if ( n_linked_monitors ) - { - for( int i = 0; i < n_linked_monitors; i++ ) - { + if ( n_linked_monitors ) { + for( int i = 0; i < n_linked_monitors; i++ ) { delete linked_monitors[i]; } delete[] linked_monitors; @@ -1955,44 +1720,35 @@ void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) } n_linked_monitors = 0; - if ( p_linked_monitors ) - { + if ( p_linked_monitors ) { int n_link_ids = 0; unsigned int link_ids[256]; char link_id_str[8]; char *dest_ptr = link_id_str; const char *src_ptr = p_linked_monitors; - while( 1 ) - { + while( 1 ) { dest_ptr = link_id_str; - while( *src_ptr >= '0' && *src_ptr <= '9' ) - { - if ( (dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) - { + while( *src_ptr >= '0' && *src_ptr <= '9' ) { + if ( (dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) { *dest_ptr++ = *src_ptr++; - } - else - { + } else { break; } } // Add the link monitor - if ( dest_ptr != link_id_str ) - { + if ( dest_ptr != link_id_str ) { *dest_ptr = '\0'; unsigned int link_id = atoi(link_id_str); - if ( link_id > 0 && link_id != id) - { + if ( link_id > 0 && link_id != id) { Debug( 3, "Found linked monitor id %d", link_id ); int j; - for ( j = 0; j < n_link_ids; j++ ) - { + for ( j = 0; j < n_link_ids; j++ ) { if ( link_ids[j] == link_id ) break; } - if ( j == n_link_ids ) // Not already found - { + if ( j == n_link_ids ) { + // Not already found link_ids[n_link_ids++] = link_id; } } @@ -2004,38 +1760,31 @@ void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) if ( !*src_ptr ) break; } - if ( n_link_ids > 0 ) - { + if ( n_link_ids > 0 ) { Debug( 1, "Linking to %d monitors", n_link_ids ); linked_monitors = new MonitorLink *[n_link_ids]; int count = 0; - for ( int i = 0; i < n_link_ids; i++ ) - { + for ( int i = 0; i < n_link_ids; i++ ) { Debug( 1, "Checking linked monitor %d", link_ids[i] ); static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "select Id, Name from Monitors where Id = %d and Function != 'None' and Function != 'Monitor' and Enabled = 1", link_ids[i] ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } int n_monitors = mysql_num_rows( result ); - if ( n_monitors == 1 ) - { + if ( n_monitors == 1 ) { MYSQL_ROW dbrow = mysql_fetch_row( result ); Debug( 1, "Linking to monitor %d", link_ids[i] ); linked_monitors[count++] = new MonitorLink( link_ids[i], dbrow[1] ); - } - else - { + } else { Warning( "Can't link to monitor %d, invalid id, function or not enabled", link_ids[i] ); } mysql_free_result( result ); @@ -2046,9 +1795,8 @@ void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) } #if ZM_HAS_V4L -int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose purpose ) -{ - std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Function != 'None' and Type = 'Local'"; +int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose purpose ) { + std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Function != 'None' and Type = 'Local'"; if ( device[0] ) { sql += " AND Device='"; sql += device; @@ -2068,8 +1816,7 @@ int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose Debug( 1, "Got %d monitors", n_monitors ); delete[] monitors; monitors = new Monitor *[n_monitors]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int id = atoi(dbrow[col]); col++; @@ -2108,13 +1855,19 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); int palette = atoi(dbrow[col]); col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; + + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - const char *event_prefix = dbrow[col]; col++; - const char *label_format = dbrow[col]; col++; + const char *event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + const char *label_format = dbrow[col] ? dbrow[col] : ""; col++; int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; @@ -2165,6 +1918,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); hue, colour, purpose==CAPTURE, + record_audio, extras ); @@ -2178,6 +1932,10 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); camera, orientation, deinterlacing, + savejpegs, + videowriter, + encoderparams, + record_audio, event_prefix, label_format, Coord( label_x, label_y ), @@ -2205,14 +1963,14 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); 0, 0 ); + camera->setMonitor( monitors[i] ); Zone **zones = 0; int n_zones = Zone::Load( monitors[i], zones ); monitors[i]->AddZones( n_zones, zones ); monitors[i]->AddPrivacyBitmask( zones ); Debug( 1, "Loaded monitor %d(%s), %d zones", id, name, n_zones ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2223,9 +1981,8 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); } #endif // ZM_HAS_V4L -int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const char *port, const char *path, Monitor **&monitors, Purpose purpose ) -{ - std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Remote'"; +int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const char *port, const char *path, Monitor **&monitors, Purpose purpose ) { + std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Remote'"; if ( staticConfig.SERVER_ID ) { sql += stringtf( " AND ServerId=%d", staticConfig.SERVER_ID ); } @@ -2244,8 +2001,7 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c Debug( 1, "Got %d monitors", n_monitors ); delete[] monitors; monitors = new Monitor *[n_monitors]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int id = atoi(dbrow[col]); col++; @@ -2255,11 +2011,11 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c int enabled = atoi(dbrow[col]); col++; const char *linked_monitors = dbrow[col]; col++; - std::string protocol = dbrow[col]; col++; - std::string method = dbrow[col]; col++; - std::string host = dbrow[col]; col++; - std::string port = dbrow[col]; col++; - std::string path = dbrow[col]; col++; + std::string protocol = dbrow[col] ? dbrow[col] : ""; col++; + std::string method = dbrow[col] ? dbrow[col] : ""; col++; + std::string host = dbrow[col] ? dbrow[col] : ""; col++; + std::string port = dbrow[col] ? dbrow[col] : ""; col++; + std::string path = dbrow[col] ? dbrow[col] : ""; col++; int width = atoi(dbrow[col]); col++; int height = atoi(dbrow[col]); col++; @@ -2267,14 +2023,19 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c /* int palette = atoi(dbrow[col]); */ col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; - bool rtsp_describe = (*dbrow[col] != '0'); col++; + bool rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++; + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - std::string event_prefix = dbrow[col]; col++; - std::string label_format = dbrow[col]; col++; + const char *event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + const char *label_format = dbrow[col] ? dbrow[col] : ""; col++; int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; @@ -2300,8 +2061,7 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c bool embed_exif = (*dbrow[col] != '0'); col++; Camera *camera = 0; - if ( protocol == "http" ) - { + if ( protocol == "http" ) { camera = new RemoteCameraHttp( id, method, @@ -2315,12 +2075,12 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); } #if HAVE_LIBAVFORMAT - else if ( protocol == "rtsp" ) - { + else if ( protocol == "rtsp" ) { camera = new RemoteCameraRtsp( id, method, @@ -2335,12 +2095,12 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); } #endif // HAVE_LIBAVFORMAT - else - { + else { Fatal( "Unexpected remote camera protocol '%s'", protocol.c_str() ); } @@ -2354,8 +2114,12 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c camera, orientation, deinterlacing, - event_prefix.c_str(), - label_format.c_str(), + savejpegs, + videowriter, + encoderparams, + record_audio, + event_prefix, + label_format, Coord( label_x, label_y ), label_size, image_buffer_count, @@ -2382,14 +2146,14 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c 0 ); + camera->setMonitor( monitors[i] ); Zone **zones = 0; int n_zones = Zone::Load( monitors[i], zones ); monitors[i]->AddZones( n_zones, zones ); monitors[i]->AddPrivacyBitmask( zones ); Debug( 1, "Loaded monitor %d(%s), %d zones", id, name.c_str(), n_zones ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2399,9 +2163,8 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c return( n_monitors ); } -int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose purpose ) -{ - std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'File'"; +int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose purpose ) { + std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'File'"; if ( file[0] ) { sql += " AND Path='"; sql += file; @@ -2412,8 +2175,7 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu } Debug( 1, "Loading File Monitors with %s", sql.c_str() ); MYSQL_RES *result = zmDbFetch( sql.c_str() ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2421,8 +2183,7 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu Debug( 1, "Got %d monitors", n_monitors ); delete[] monitors; monitors = new Monitor *[n_monitors]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int id = atoi(dbrow[col]); col++; @@ -2440,14 +2201,20 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu /* int palette = atoi(dbrow[col]); */ col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; + + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col]; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - const char *event_prefix = dbrow[col]; col++; - const char *label_format = dbrow[col]; col++; - + const char *event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + const char *label_format = dbrow[col] ? dbrow[col] : ""; col++; + int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; int label_size = atoi(dbrow[col]); col++; @@ -2481,7 +2248,8 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); monitors[i] = new Monitor( @@ -2494,6 +2262,10 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu camera, orientation, deinterlacing, + savejpegs, + videowriter, + encoderparams, + record_audio, event_prefix, label_format, Coord( label_x, label_y ), @@ -2521,14 +2293,14 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu 0, 0 ); + camera->setMonitor( monitors[i] ); Zone **zones = 0; int n_zones = Zone::Load( monitors[i], zones ); monitors[i]->AddZones( n_zones, zones ); monitors[i]->AddPrivacyBitmask( zones ); Debug( 1, "Loaded monitor %d(%s), %d zones", id, name, n_zones ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2539,9 +2311,8 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu } #if HAVE_LIBAVFORMAT -int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose purpose ) -{ - std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Ffmpeg'"; +int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose purpose ) { + std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Ffmpeg'"; if ( file[0] ) { sql += " AND Path = '"; sql += file; @@ -2561,8 +2332,7 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose Debug( 1, "Got %d monitors", n_monitors ); delete[] monitors; monitors = new Monitor *[n_monitors]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int id = atoi(dbrow[col]); col++; @@ -2570,11 +2340,11 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose unsigned int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; int function = atoi(dbrow[col]); col++; int enabled = atoi(dbrow[col]); col++; - const char *linked_monitors = dbrow[col]; col++; + const char *linked_monitors = dbrow[col] ? dbrow[col] : ""; col++; const char *path = dbrow[col]; col++; const char *method = dbrow[col]; col++; - const char *options = dbrow[col]; col++; + const char *options = dbrow[col] ? dbrow[col] : ""; col++; int width = atoi(dbrow[col]); col++; int height = atoi(dbrow[col]); col++; @@ -2582,13 +2352,19 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose /* int palette = atoi(dbrow[col]); */ col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; + + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - const char *event_prefix = dbrow[col]; col++; - const char *label_format = dbrow[col]; col++; + const char *event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + const char *label_format = dbrow[col] ? dbrow[col] : ""; col++; int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; @@ -2603,10 +2379,14 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose int section_length = atoi(dbrow[col]); col++; int frame_skip = atoi(dbrow[col]); col++; int motion_frame_skip = atoi(dbrow[col]); col++; + double analysis_fps = dbrow[col] ? strtod(dbrow[col], NULL) : 0; col++; unsigned int analysis_update_delay = strtoul(dbrow[col++], NULL, 0); - int capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++; - int alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++; + double capture_fps = dbrow[col] ? atof(dbrow[col]) : 0;col++; + int capture_delay = capture_fps >0.0 ?int(DT_PREC_3/capture_fps):0; + double alarm_capture_fps = dbrow[col] ? atof(dbrow[col]) : 0; col++; + int alarm_capture_delay = alarm_capture_fps > 0.0 ?int(DT_PREC_3/alarm_capture_fps):0; + int fps_report_interval = atoi(dbrow[col]); col++; int ref_blend_perc = atoi(dbrow[col]); col++; int alarm_ref_blend_perc = atoi(dbrow[col]); col++; @@ -2625,7 +2405,8 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); monitors[i] = new Monitor( @@ -2638,6 +2419,10 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose camera, orientation, deinterlacing, + savejpegs, + videowriter, + encoderparams, + record_audio, event_prefix, label_format, Coord( label_x, label_y ), @@ -2665,14 +2450,14 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose 0, 0 ); + camera->setMonitor( monitors[i] ); Zone **zones = 0; int n_zones = Zone::Load( monitors[i], zones ); monitors[i]->AddZones( n_zones, zones ); monitors[i]->AddPrivacyBitmask( zones ); Debug( 1, "Loaded monitor %d(%s), %d zones", id, name, n_zones ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2683,9 +2468,8 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose } #endif // HAVE_LIBAVFORMAT -Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) -{ - std::string sql = stringtf( "select Id, Name, ServerId, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Protocol, Method, Host, Port, Path, Options, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Id = %d", p_id ); +Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) { + std::string sql = stringtf( "select Id, Name, ServerId, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Protocol, Method, Host, Port, Path, Options, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Id = %d", p_id ); MYSQL_ROW dbrow = zmDbFetchOne( sql.c_str() ); if ( ! dbrow ) { @@ -2701,7 +2485,7 @@ Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) std::string type = dbrow[col]; col++; int function = atoi(dbrow[col]); col++; int enabled = atoi(dbrow[col]); col++; - std::string linked_monitors = dbrow[col]; col++; + std::string linked_monitors = dbrow[col] ? dbrow[col] : ""; col++; std::string device = dbrow[col]; col++; int channel = atoi(dbrow[col]); col++; @@ -2723,17 +2507,17 @@ Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) } else { v4l_captures_per_frame = config.captures_per_frame; } -Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); + Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); col++; - std::string protocol = dbrow[col]; col++; - std::string method = dbrow[col]; col++; - std::string host = dbrow[col]; col++; - std::string port = dbrow[col]; col++; - std::string path = dbrow[col]; col++; - std::string options = dbrow[col]; col++; - std::string user = dbrow[col]; col++; - std::string pass = dbrow[col]; col++; + std::string protocol = dbrow[col] ? dbrow[col] : ""; col++; + std::string method = dbrow[col] ? dbrow[col] : ""; col++; + std::string host = dbrow[col] ? dbrow[col] : ""; col++; + std::string port = dbrow[col] ? dbrow[col] : ""; col++; + std::string path = dbrow[col] ? dbrow[col] : ""; col++; + std::string options = dbrow[col] ? dbrow[col] : ""; col++; + std::string user = dbrow[col] ? dbrow[col] : ""; col++; + std::string pass = dbrow[col] ? dbrow[col] : ""; col++; int width = atoi(dbrow[col]); col++; int height = atoi(dbrow[col]); col++; @@ -2741,14 +2525,19 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); int palette = atoi(dbrow[col]); col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; - bool rtsp_describe = (*dbrow[col] != '0'); col++; + bool rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++; + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - std::string event_prefix = dbrow[col]; col++; - std::string label_format = dbrow[col]; col++; + const char * event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + const char * label_format = dbrow[col] ? dbrow[col] : ""; col++; int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; @@ -2783,8 +2572,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); int extras = (deinterlacing>>24)&0xff; Camera *camera = 0; - if ( type == "Local" ) - { + if ( type == "Local" ) { #if ZM_HAS_V4L camera = new LocalCamera( id, @@ -2803,16 +2591,14 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); hue, colour, purpose==CAPTURE, + record_audio, extras ); #else // ZM_HAS_V4L Fatal( "You must have video4linux libraries and headers installed to use local analog or USB cameras for monitor %d", id ); #endif // ZM_HAS_V4L - } - else if ( type == "Remote" ) - { - if ( protocol == "http" ) - { + } else if ( type == "Remote" ) { + if ( protocol == "http" ) { camera = new RemoteCameraHttp( id, method.c_str(), @@ -2826,11 +2612,10 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); - } - else if ( protocol == "rtsp" ) - { + } else if ( protocol == "rtsp" ) { #if HAVE_LIBAVFORMAT camera = new RemoteCameraRtsp( id, @@ -2846,19 +2631,16 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); #else // HAVE_LIBAVFORMAT Fatal( "You must have ffmpeg libraries installed to use remote camera protocol '%s' for monitor %d", protocol.c_str(), id ); #endif // HAVE_LIBAVFORMAT - } - else - { + } else { Fatal( "Unexpected remote camera protocol '%s' for monitor %d", protocol.c_str(), id ); } - } - else if ( type == "File" ) - { + } else if ( type == "File" ) { camera = new FileCamera( id, path.c_str(), @@ -2869,11 +2651,10 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); - } - else if ( type == "Ffmpeg" ) - { + } else if ( type == "Ffmpeg" ) { #if HAVE_LIBAVFORMAT camera = new FfmpegCamera( id, @@ -2887,14 +2668,13 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); #else // HAVE_LIBAVFORMAT Fatal( "You must have ffmpeg libraries installed to use ffmpeg cameras for monitor %d", id ); #endif // HAVE_LIBAVFORMAT - } - else if (type == "Libvlc") - { + } else if (type == "Libvlc") { #if HAVE_LIBVLC camera = new LibvlcCamera( id, @@ -2908,14 +2688,13 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); #else // HAVE_LIBVLC Fatal( "You must have vlc libraries installed to use vlc cameras for monitor %d", id ); #endif // HAVE_LIBVLC - } - else if ( type == "cURL" ) - { + } else if ( type == "cURL" ) { #if HAVE_LIBCURL camera = new cURLCamera( id, @@ -2929,14 +2708,13 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); #else // HAVE_LIBCURL Fatal( "You must have libcurl installed to use ffmpeg cameras for monitor %d", id ); #endif // HAVE_LIBCURL - } - else - { + } else { Fatal( "Bogus monitor type '%s' for monitor %d", type.c_str(), id ); } monitor = new Monitor( @@ -2949,8 +2727,12 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); camera, orientation, deinterlacing, - event_prefix.c_str(), - label_format.c_str(), + savejpegs, + videowriter, + encoderparams, + record_audio, + event_prefix, + label_format, Coord( label_x, label_y ), label_size, image_buffer_count, @@ -2978,9 +2760,9 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); ); + camera->setMonitor( monitor ); int n_zones = 0; - if ( load_zones ) - { + if ( load_zones ) { Zone **zones = 0; n_zones = Zone::Load( monitor, zones ); monitor->AddZones( n_zones, zones ); @@ -2990,22 +2772,34 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); return( monitor ); } -int Monitor::Capture() -{ - static int FirstCapture = 1; +/* Returns 0 on success, even if no new images are available (transient error) + * Returns -1 on failure. + */ +int Monitor::Capture() { + static int FirstCapture = 1; // Used in de-interlacing to indicate whether this is the even or odd image int captureResult; - int index = image_count%image_buffer_count; + unsigned int index = image_count%image_buffer_count; Image* capture_image = image_buffer[index].image; - if ( (deinterlacing & 0xff) == 4) { + unsigned int deinterlacing_value = deinterlacing & 0xff; + + if ( deinterlacing_value == 4 ) { if ( FirstCapture != 1 ) { /* Copy the next image into the shared memory */ capture_image->CopyBuffer(*(next_buffer.image)); } /* Capture a new next image */ - captureResult = camera->Capture(*(next_buffer.image)); + + //Check if FFMPEG camera + if ( ( videowriter == H264PASSTHROUGH ) && camera->SupportsNativeVideo() ) { + captureResult = camera->CaptureAndRecord(*(next_buffer.image), + video_store_data->recording, + video_store_data->event_file ); + } else { + captureResult = camera->Capture(*(next_buffer.image)); + } if ( FirstCapture ) { FirstCapture = 0; @@ -3013,12 +2807,23 @@ int Monitor::Capture() } } else { - /* Capture directly into image buffer, avoiding the need to memcpy() */ - captureResult = camera->Capture(*capture_image); + //Check if FFMPEG camera + if ( (videowriter == H264PASSTHROUGH ) && camera->SupportsNativeVideo() ) { + //Warning("ZMC: Recording: %d", video_store_data->recording); + captureResult = camera->CaptureAndRecord(*capture_image, video_store_data->recording, video_store_data->event_file); + }else{ + /* Capture directly into image buffer, avoiding the need to memcpy() */ + captureResult = camera->Capture(*capture_image); + } } - if ( captureResult != 0 ) - { + // CaptureAndRecord returns # of frames captured I think + if ( ( videowriter == H264PASSTHROUGH ) && ( captureResult > 0 ) ) { + //video_store_data->frameNumber = captureResult; + captureResult = 0; + } + + if ( captureResult != 0 ) { // Unable to capture image for temporary reason // Fake a signal loss image Rgb signalcolor; @@ -3029,27 +2834,24 @@ int Monitor::Capture() captureResult = 1; } - if ( captureResult == 1 ) - { + if ( captureResult == 1 ) { /* Deinterlacing */ - if ( (deinterlacing & 0xff) == 1 ) { + if ( deinterlacing_value == 1 ) { capture_image->Deinterlace_Discard(); - } else if ( (deinterlacing & 0xff) == 2 ) { + } else if ( deinterlacing_value == 2 ) { capture_image->Deinterlace_Linear(); - } else if ( (deinterlacing & 0xff) == 3 ) { + } else if ( deinterlacing_value == 3 ) { capture_image->Deinterlace_Blend(); - } else if ( (deinterlacing & 0xff) == 4 ) { + } else if ( deinterlacing_value == 4 ) { capture_image->Deinterlace_4Field( next_buffer.image, (deinterlacing>>8)&0xff ); - } else if ( (deinterlacing & 0xff) == 5 ) { + } else if ( deinterlacing_value == 5 ) { capture_image->Deinterlace_Blend_CustomRatio( (deinterlacing>>8)&0xff ); } - if ( orientation != ROTATE_0 ) - { - switch ( orientation ) - { + if ( orientation != ROTATE_0 ) { + switch ( orientation ) { case ROTATE_0 : { // No action required @@ -3071,23 +2873,17 @@ int Monitor::Capture() } } - } - if ( true ) { - - if ( capture_image->Size() > camera->ImageSize() ) - { + if ( capture_image->Size() > camera->ImageSize() ) { Error( "Captured image %d does not match expected size %d check width, height and colour depth",capture_image->Size(),camera->ImageSize() ); return( -1 ); } - if ( ((unsigned int)index == shared_data->last_read_index) && (function > MONITOR) ) - { + if ( (index == shared_data->last_read_index) && (function > MONITOR) ) { Warning( "Buffer overrun at index %d, image %d, slow down capture, speed up analysis or increase ring buffer size", index, image_count ); time_t now = time(0); double approxFps = double(image_buffer_count)/double(now-image_buffer[index].timestamp->tv_sec); time_t last_read_delta = now - shared_data->last_read_time; - if ( last_read_delta > (image_buffer_count/approxFps) ) - { + if ( last_read_delta > (image_buffer_count/approxFps) ) { Warning( "Last image read from shared memory %ld seconds ago, zma may have gone away", last_read_delta ) shared_data->last_read_index = image_buffer_count; } @@ -3097,8 +2893,7 @@ int Monitor::Capture() capture_image->MaskPrivacy( privacy_bitmask ); gettimeofday( image_buffer[index].timestamp, NULL ); - if ( config.timestamp_on_capture ) - { + if ( config.timestamp_on_capture ) { TimestampImage( capture_image, image_buffer[index].timestamp ); } shared_data->signal = CheckSignal(capture_image); @@ -3107,8 +2902,7 @@ int Monitor::Capture() image_count++; - if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) - { + if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) { time_t now = image_buffer[index].timestamp->tv_sec; fps = double(fps_report_interval)/(now-last_fps_time); //Info( "%d -> %d -> %d", fps_report_interval, now, last_fps_time ); @@ -3117,16 +2911,14 @@ int Monitor::Capture() last_fps_time = now; } - if ( shared_data->action & GET_SETTINGS ) - { + if ( shared_data->action & GET_SETTINGS ) { shared_data->brightness = camera->Brightness(); shared_data->hue = camera->Hue(); shared_data->colour = camera->Colour(); shared_data->contrast = camera->Contrast(); shared_data->action &= ~GET_SETTINGS; } - if ( shared_data->action & SET_SETTINGS ) - { + if ( shared_data->action & SET_SETTINGS ) { camera->Brightness( shared_data->brightness ); camera->Hue( shared_data->hue ); camera->Colour( shared_data->colour ); @@ -3134,15 +2926,13 @@ int Monitor::Capture() shared_data->action &= ~SET_SETTINGS; } return( 0 ); - } + } // end if captureResults == 1 shared_data->signal = false; return( -1 ); } -void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) const -{ - if ( label_format[0] ) - { +void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) const { + if ( label_format[0] ) { // Expand the strftime macros first char label_time_text[256]; strftime( label_time_text, sizeof(label_time_text), label_format, localtime( &ts_time->tv_sec ) ); @@ -3150,13 +2940,10 @@ void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) c char label_text[1024]; const char *s_ptr = label_time_text; char *d_ptr = label_text; - while ( *s_ptr && ((d_ptr-label_text) < (unsigned int)sizeof(label_text)) ) - { - if ( *s_ptr == '%' ) - { + while ( *s_ptr && ((d_ptr-label_text) < (unsigned int)sizeof(label_text)) ) { + if ( *s_ptr == '%' ) { bool found_macro = false; - switch ( *(s_ptr+1) ) - { + switch ( *(s_ptr+1) ) { case 'N' : d_ptr += snprintf( d_ptr, sizeof(label_text)-(d_ptr-label_text), "%s", name ); found_macro = true; @@ -3170,8 +2957,7 @@ void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) c found_macro = true; break; } - if ( found_macro ) - { + if ( found_macro ) { s_ptr += 2; continue; } @@ -3183,33 +2969,28 @@ void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) c } } -bool Monitor::closeEvent() -{ - if ( event ) - { - if ( function == RECORD || function == MOCORD ) - { +bool Monitor::closeEvent() { + if ( event ) { + if ( function == RECORD || function == MOCORD ) { gettimeofday( &(event->EndTime()), NULL ); } delete event; + video_store_data->recording = (struct timeval){0}; event = 0; return( true ); } return( false ); } -unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ) -{ +unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ) { bool alarm = false; unsigned int score = 0; if ( n_zones <= 0 ) return( alarm ); - if ( config.record_diag_images ) - { + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( !diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-r.jpg", config.dir_events, id ); } ref_image.WriteJpeg( diag_path ); @@ -3217,25 +2998,21 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z ref_image.Delta( comp_image, &delta_image); - if ( config.record_diag_images ) - { + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( !diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-d.jpg", config.dir_events, id ); } delta_image.WriteJpeg( diag_path ); } // Blank out all exclusion zones - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; // need previous alarmed state for preclusive zone, so don't clear just yet if (!zone->IsPreclusive()) zone->ClearAlarm(); - if ( !zone->IsInactive() ) - { + if ( !zone->IsInactive() ) { continue; } Debug( 3, "Blanking inactive zone %s", zone->Label() ); @@ -3243,18 +3020,15 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z } // Check preclusive zones first - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; - if ( !zone->IsPreclusive() ) - { + if ( !zone->IsPreclusive() ) { continue; } int old_zone_score = zone->Score(); bool old_zone_alarmed = zone->Alarmed(); Debug( 3, "Checking preclusive zone %s - old score: %d, state: %s", zone->Label(),old_zone_score, zone->Alarmed()?"alarmed":"quiet" ); - if ( zone->CheckAlarms( &delta_image ) ) - { + if ( zone->CheckAlarms( &delta_image ) ) { alarm = true; score += zone->Score(); zone->SetAlarm(); @@ -3270,7 +3044,7 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z } if (zone->CheckExtendAlarmCount()) { alarm=true; - zone->SetAlarm(); + zone->SetAlarm(); } else { zone->ClearAlarm(); } @@ -3281,33 +3055,25 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z Coord alarm_centre; int top_score = -1; - if ( alarm ) - { + if ( alarm ) { alarm = false; score = 0; - } - else - { + } else { // Find all alarm pixels in active zones - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; - if ( !zone->IsActive() || zone->IsPreclusive()) - { + if ( !zone->IsActive() || zone->IsPreclusive()) { continue; } Debug( 3, "Checking active zone %s", zone->Label() ); - if ( zone->CheckAlarms( &delta_image ) ) - { + if ( zone->CheckAlarms( &delta_image ) ) { alarm = true; score += zone->Score(); zone->SetAlarm(); Debug( 3, "Zone is alarmed, zone score = %d", zone->Score() ); zoneSet.insert( zone->Label() ); - if ( config.opt_control && track_motion ) - { - if ( (int)zone->Score() > top_score ) - { + if ( config.opt_control && track_motion ) { + if ( (int)zone->Score() > top_score ) { top_score = zone->Score(); alarm_centre = zone->GetAlarmCentre(); } @@ -3315,47 +3081,36 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z } } - if ( alarm ) - { - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + if ( alarm ) { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; - if ( !zone->IsInclusive() ) - { + if ( !zone->IsInclusive() ) { continue; } Debug( 3, "Checking inclusive zone %s", zone->Label() ); - if ( zone->CheckAlarms( &delta_image ) ) - { + if ( zone->CheckAlarms( &delta_image ) ) { alarm = true; score += zone->Score(); zone->SetAlarm(); Debug( 3, "Zone is alarmed, zone score = %d", zone->Score() ); zoneSet.insert( zone->Label() ); - if ( config.opt_control && track_motion ) - { - if ( zone->Score() > (unsigned int)top_score ) - { + if ( config.opt_control && track_motion ) { + if ( zone->Score() > (unsigned int)top_score ) { top_score = zone->Score(); alarm_centre = zone->GetAlarmCentre(); } } } } - } - else - { + } else { // Find all alarm pixels in exclusive zones - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; - if ( !zone->IsExclusive() ) - { + if ( !zone->IsExclusive() ) { continue; } Debug( 3, "Checking exclusive zone %s", zone->Label() ); - if ( zone->CheckAlarms( &delta_image ) ) - { + if ( zone->CheckAlarms( &delta_image ) ) { alarm = true; score += zone->Score(); zone->SetAlarm(); @@ -3366,15 +3121,12 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z } } - if ( top_score > 0 ) - { + if ( top_score > 0 ) { shared_data->alarm_x = alarm_centre.X(); shared_data->alarm_y = alarm_centre.Y(); Info( "Got alarm centre at %d,%d, at count %d", shared_data->alarm_x, shared_data->alarm_y, image_count ); - } - else - { + } else { shared_data->alarm_x = shared_data->alarm_y = -1; } @@ -3382,44 +3134,36 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z return( score?score:alarm ); } -bool Monitor::DumpSettings( char *output, bool verbose ) -{ +bool Monitor::DumpSettings( char *output, bool verbose ) { output[0] = 0; sprintf( output+strlen(output), "Id : %d\n", id ); sprintf( output+strlen(output), "Name : %s\n", name ); sprintf( output+strlen(output), "Type : %s\n", camera->IsLocal()?"Local":(camera->IsRemote()?"Remote":"File") ); #if ZM_HAS_V4L - if ( camera->IsLocal() ) - { + if ( camera->IsLocal() ) { sprintf( output+strlen(output), "Device : %s\n", ((LocalCamera *)camera)->Device().c_str() ); sprintf( output+strlen(output), "Channel : %d\n", ((LocalCamera *)camera)->Channel() ); sprintf( output+strlen(output), "Standard : %d\n", ((LocalCamera *)camera)->Standard() ); - } - else + } else #endif // ZM_HAS_V4L - if ( camera->IsRemote() ) - { + if ( camera->IsRemote() ) { sprintf( output+strlen(output), "Protocol : %s\n", ((RemoteCamera *)camera)->Protocol().c_str() ); sprintf( output+strlen(output), "Host : %s\n", ((RemoteCamera *)camera)->Host().c_str() ); sprintf( output+strlen(output), "Port : %s\n", ((RemoteCamera *)camera)->Port().c_str() ); sprintf( output+strlen(output), "Path : %s\n", ((RemoteCamera *)camera)->Path().c_str() ); - } - else if ( camera->IsFile() ) - { + } else if ( camera->IsFile() ) { sprintf( output+strlen(output), "Path : %s\n", ((FileCamera *)camera)->Path() ); } #if HAVE_LIBAVFORMAT - else if ( camera->IsFfmpeg() ) - { + else if ( camera->IsFfmpeg() ) { sprintf( output+strlen(output), "Path : %s\n", ((FfmpegCamera *)camera)->Path().c_str() ); } #endif // HAVE_LIBAVFORMAT sprintf( output+strlen(output), "Width : %d\n", camera->Width() ); sprintf( output+strlen(output), "Height : %d\n", camera->Height() ); #if ZM_HAS_V4L - if ( camera->IsLocal() ) - { + if ( camera->IsLocal() ) { sprintf( output+strlen(output), "Palette : %d\n", ((LocalCamera *)camera)->Palette() ); } #endif // ZM_HAS_V4L @@ -3450,78 +3194,61 @@ bool Monitor::DumpSettings( char *output, bool verbose ) function==NODECT?"Externally Triggered only, no Motion Detection":"Unknown" )))))); sprintf( output+strlen(output), "Zones : %d\n", n_zones ); - for ( int i = 0; i < n_zones; i++ ) - { + for ( int i = 0; i < n_zones; i++ ) { zones[i]->DumpSettings( output+strlen(output), verbose ); } return( true ); -} +} // bool Monitor::DumpSettings( char *output, bool verbose ) -bool MonitorStream::checkSwapPath( const char *path, bool create_path ) -{ +bool MonitorStream::checkSwapPath( const char *path, bool create_path ) { uid_t uid = getuid(); gid_t gid = getgid(); struct stat stat_buf; - if ( stat( path, &stat_buf ) < 0 ) - { - if ( create_path && errno == ENOENT ) - { + if ( stat( path, &stat_buf ) < 0 ) { + if ( create_path && errno == ENOENT ) { Debug( 3, "Swap path '%s' missing, creating", path ); - if ( mkdir( path, 0755 ) ) - { + if ( mkdir( path, 0755 ) ) { Error( "Can't mkdir %s: %s", path, strerror(errno)); return( false ); } - if ( stat( path, &stat_buf ) < 0 ) - { + if ( stat( path, &stat_buf ) < 0 ) { Error( "Can't stat '%s': %s", path, strerror(errno) ); return( false ); } - } - else - { + } else { Error( "Can't stat '%s': %s", path, strerror(errno) ); return( false ); } } - if ( !S_ISDIR(stat_buf.st_mode) ) - { + if ( !S_ISDIR(stat_buf.st_mode) ) { Error( "Swap image path '%s' is not a directory", path ); return( false ); } mode_t mask = 0; - if ( uid == stat_buf.st_uid ) - { + if ( uid == stat_buf.st_uid ) { // If we are the owner mask = 00700; - } - else if ( gid == stat_buf.st_gid ) - { + } else if ( gid == stat_buf.st_gid ) { // If we are in the owner group mask = 00070; - } - else - { + } else { // We are neither the owner nor in the group mask = 00007; } - if ( (stat_buf.st_mode & mask) != mask ) - { + if ( (stat_buf.st_mode & mask) != mask ) { Error( "Insufficient permissions on swap image path '%s'", path ); return( false ); } return( true ); } -void MonitorStream::processCommand( const CmdMsg *msg ) -{ +void MonitorStream::processCommand( const CmdMsg *msg ) { Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] ); // Check for incoming command - switch( (MsgCommand)msg->msg_data[0] ) - { + switch( (MsgCommand)msg->msg_data[0] ) { case CMD_PAUSE : { Debug( 1, "Got PAUSE command" ); @@ -3783,8 +3510,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) status_msg.msg_type = MSG_DATA_WATCH; memcpy( &status_msg.msg_data, &status_data, sizeof(status_data) ); int nbytes = 0; - if ( (nbytes = sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) )) < 0 ) - { + if ( (nbytes = sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) )) < 0 ) { //if ( errno != EAGAIN ) { Error( "Can't sendto on sd %d: %s", sd, strerror(errno) ); @@ -3799,8 +3525,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) updateFrameRate( monitor->GetFPS() ); } -bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) -{ +bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) { bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)); if ( type != STREAM_JPEG ) @@ -3808,25 +3533,19 @@ bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) if ( !config.timestamp_on_capture && timestamp ) send_raw = false; - if ( !send_raw ) - { + if ( !send_raw ) { Image temp_image( filepath ); return( sendFrame( &temp_image, timestamp ) ); - } - else - { + } else { int img_buffer_size = 0; static unsigned char img_buffer[ZM_MAX_IMAGE_SIZE]; FILE *fdj = NULL; - if ( (fdj = fopen( filepath, "r" )) ) - { + if ( (fdj = fopen( filepath, "r" )) ) { img_buffer_size = fread( img_buffer, 1, sizeof(img_buffer), fdj ); fclose( fdj ); - } - else - { + } else { Error( "Can't open %s: %s", filepath, strerror(errno) ); return( false ); } @@ -3838,8 +3557,7 @@ bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) fprintf( stdout, "--ZoneMinderFrame\r\n" ); fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size ); fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" ); - if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) - { + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { if ( !zm_terminate ) Error( "Unable to send stream frame: %s", strerror(errno) ); return( false ); @@ -3851,8 +3569,7 @@ bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) gettimeofday( &frameEndTime, NULL ); int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime ); - if ( frameSendTime > 1000/maxfps ) - { + if ( frameSendTime > 1000/maxfps ) { maxfps /= 2; Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps ); } @@ -3864,17 +3581,14 @@ bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) return( false ); } -bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) -{ +bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) { Image *send_image = prepareImage( image ); if ( !config.timestamp_on_capture && timestamp ) monitor->TimestampImage( send_image, timestamp ); #if HAVE_LIBAVCODEC - if ( type == STREAM_MPEG ) - { - if ( !vid_stream ) - { + if ( type == STREAM_MPEG ) { + if ( !vid_stream ) { vid_stream = new VideoStream( "pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height() ); fprintf( stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType() ); vid_stream->OpenStream(); @@ -3885,8 +3599,7 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) base_time = *timestamp; DELTA_TIMEVAL( delta_time, *timestamp, base_time, DT_PREC_3 ); /* double pts = */ vid_stream->EncodeFrame( send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.delta ); - } - else + } else #endif // HAVE_LIBAVCODEC { static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; @@ -3899,8 +3612,7 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) gettimeofday( &frameStartTime, NULL ); fprintf( stdout, "--ZoneMinderFrame\r\n" ); - switch( type ) - { + switch( type ) { case STREAM_JPEG : send_image->EncodeJpeg( img_buffer, &img_buffer_size ); fprintf( stdout, "Content-Type: image/jpeg\r\n" ); @@ -3921,8 +3633,7 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) break; } fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); - if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) - { + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { if ( !zm_terminate ) Error( "Unable to send stream frame: %s", strerror(errno) ); return( false ); @@ -3934,8 +3645,7 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) gettimeofday( &frameEndTime, NULL ); int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime ); - if ( frameSendTime > 1000/maxfps ) - { + if ( frameSendTime > 1000/maxfps ) { maxfps /= 1.5; Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps ); } @@ -3944,10 +3654,8 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) return( true ); } -void MonitorStream::runStream() -{ - if ( type == STREAM_SINGLE ) - { +void MonitorStream::runStream() { + if ( type == STREAM_SINGLE ) { // Not yet migrated over to stream class monitor->SingleImage( scale ); return; @@ -3980,20 +3688,32 @@ void MonitorStream::runStream() // 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id const int max_swap_len_suffix = 15; - int swap_path_length = strlen(config.path_swap)+1; // +1 for NULL terminator + int swap_path_length = strlen(config.path_swap) + 1; // +1 for NULL terminator + int subfolder1_length = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id() ) + 1; + int subfolder2_length = snprintf(NULL, 0, "/zmswap-q%06d", connkey ) + 1; + int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length; if ( connkey && playback_buffer > 0 ) { - if ( swap_path_length + max_swap_len_suffix > PATH_MAX ) { - Error( "Swap Path is too long. %d > %d ", swap_path_length+max_swap_len_suffix, PATH_MAX ); + if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) { + Error( "Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX ); } else { - swap_path = (char *)malloc( swap_path_length+max_swap_len_suffix ); - Debug( 3, "Checking swap image path %s", config.path_swap ); + swap_path = (char *)malloc( total_swap_path_length+max_swap_len_suffix ); strncpy( swap_path, config.path_swap, swap_path_length ); + + Debug( 3, "Checking swap path folder: %s", swap_path ); if ( checkSwapPath( swap_path, false ) ) { - snprintf( &(swap_path[swap_path_length]), max_swap_len_suffix, "/zmswap-m%d", monitor->Id() ); + // Append the subfolder name /zmswap-m{monitor-id} to the end of swap_path + int ndx = swap_path_length - 1; // Array index of the NULL terminator + snprintf( &(swap_path[ndx]), subfolder1_length, "/zmswap-m%d", monitor->Id() ); + + Debug( 4, "Checking swap path subfolder: %s", swap_path ); if ( checkSwapPath( swap_path, true ) ) { - snprintf( &(swap_path[swap_path_length]), max_swap_len_suffix, "/zmswap-q%06d", connkey ); + // Append the subfolder name /zmswap-q{connection key} to the end of swap_path + ndx = swap_path_length+subfolder1_length - 2; // Array index of the NULL terminator + snprintf( &(swap_path[ndx]), subfolder2_length, "/zmswap-q%06d", connkey ); + + Debug( 4, "Checking swap path subfolder: %s", swap_path ); if ( checkSwapPath( swap_path, true ) ) { buffered_playback = true; } @@ -4012,28 +3732,23 @@ void MonitorStream::runStream() } float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs) - while ( !zm_terminate ) - { + while ( !zm_terminate ) { bool got_command = false; - if ( feof( stdout ) || ferror( stdout ) || !monitor->ShmValid() ) - { + if ( feof( stdout ) || ferror( stdout ) || !monitor->ShmValid() ) { break; } gettimeofday( &now, NULL ); - if ( connkey ) - { + if ( connkey ) { while(checkCommandQueue()) { got_command = true; } } //bool frame_sent = false; - if ( buffered_playback && delayed ) - { - if ( temp_read_index == temp_write_index ) - { + if ( buffered_playback && delayed ) { + if ( temp_read_index == temp_write_index ) { // Go back to live viewing Debug( 1, "Exceeded temporary streaming buffer" ); // Clear paused flag @@ -4041,34 +3756,26 @@ void MonitorStream::runStream() // Clear delayed_play flag delayed = false; replay_rate = ZM_RATE_BASE; - } - else - { - if ( !paused ) - { + } else { + if ( !paused ) { int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count ); //Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index ); SwapImage *swap_image = &temp_image_buffer[temp_index]; - if ( !swap_image->valid ) - { + if ( !swap_image->valid ) { paused = true; delayed = true; temp_read_index = MOD_ADD( temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count ); - } - else - { + } else { //Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) ); double expected_delta_time = ((TV_2_FLOAT( swap_image->timestamp ) - TV_2_FLOAT( last_frame_timestamp )) * ZM_RATE_BASE)/replay_rate; double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; //Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) ); // If the next frame is due - if ( actual_delta_time > expected_delta_time ) - { + if ( actual_delta_time > expected_delta_time ) { //Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time ); - if ( temp_index%frame_mod == 0 ) - { + if ( temp_index%frame_mod == 0 ) { Debug( 2, "Sending delayed frame %d", temp_index ); // Send the next frame if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) ) @@ -4079,9 +3786,7 @@ void MonitorStream::runStream() temp_read_index = MOD_ADD( temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count ); } } - } - else if ( step != 0 ) - { + } else if ( step != 0 ) { temp_read_index = MOD_ADD( temp_read_index, (step>0?1:-1), temp_image_buffer_count ); SwapImage *swap_image = &temp_image_buffer[temp_read_index]; @@ -4092,14 +3797,11 @@ void MonitorStream::runStream() memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) ); //frame_sent = true; step = 0; - } - else - { + } else { int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count ); double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; - if ( got_command || actual_delta_time > 5 ) - { + if ( got_command || actual_delta_time > 5 ) { // Send keepalive Debug( 2, "Sending keepalive frame %d", temp_index ); // Send the next frame @@ -4109,8 +3811,7 @@ void MonitorStream::runStream() } } } - if ( temp_read_index == temp_write_index ) - { + if ( temp_read_index == temp_write_index ) { // Go back to live viewing Warning( "Rewound over write index, resuming live play" ); // Clear paused flag @@ -4120,15 +3821,12 @@ void MonitorStream::runStream() replay_rate = ZM_RATE_BASE; } } - if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) - { + if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) { int index = monitor->shared_data->last_write_index%monitor->image_buffer_count; last_read_index = monitor->shared_data->last_write_index; //Debug( 1, "%d: %x - %x", index, image_buffer[index].image, image_buffer[index].image->buffer ); - if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) - { - if ( !paused && !delayed ) - { + if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { + if ( !paused && !delayed ) { // Send the next frame Monitor::Snapshot *snap = &monitor->image_buffer[index]; @@ -4140,24 +3838,19 @@ void MonitorStream::runStream() temp_read_index = temp_write_index; } } - if ( buffered_playback ) - { - if ( monitor->shared_data->valid ) - { - if ( monitor->image_buffer[index].timestamp->tv_sec ) - { + if ( buffered_playback ) { + if ( monitor->shared_data->valid ) { + if ( monitor->image_buffer[index].timestamp->tv_sec ) { int temp_index = temp_write_index%temp_image_buffer_count; Debug( 2, "Storing frame %d", temp_index ); - if ( !temp_image_buffer[temp_index].valid ) - { + if ( !temp_image_buffer[temp_index].valid ) { snprintf( temp_image_buffer[temp_index].file_name, sizeof(temp_image_buffer[0].file_name), "%s/zmswap-i%05d.jpg", swap_path, temp_index ); temp_image_buffer[temp_index].valid = true; } memcpy( &(temp_image_buffer[temp_index].timestamp), monitor->image_buffer[index].timestamp, sizeof(temp_image_buffer[0].timestamp) ); monitor->image_buffer[index].image->WriteJpeg( temp_image_buffer[temp_index].file_name, config.jpeg_file_quality ); temp_write_index = MOD_ADD( temp_write_index, 1, temp_image_buffer_count ); - if ( temp_write_index == temp_read_index ) - { + if ( temp_write_index == temp_read_index ) { // Go back to live viewing Warning( "Exceeded temporary buffer, resuming live play" ); // Clear paused flag @@ -4166,82 +3859,56 @@ void MonitorStream::runStream() delayed = false; replay_rate = ZM_RATE_BASE; } - } - else - { + } else { Warning( "Unable to store frame as timestamp invalid" ); } - } - else - { + } else { Warning( "Unable to store frame as shared memory invalid" ); } } frame_count++; } usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) ); - if ( ttl ) - { - if ( (now.tv_sec - stream_start_time) > ttl ) - { + if ( ttl ) { + if ( (now.tv_sec - stream_start_time) > ttl ) { break; } } - if ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame ) - { + if ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame ) { Error( "Terminating, last frame sent time %f secs more than maximum of %f", TV_2_FLOAT( now ) - last_frame_sent, max_secs_since_last_sent_frame ); break; } } - if ( buffered_playback ) - { - char swap_path[PATH_MAX] = ""; - - snprintf( swap_path, sizeof(swap_path), "%s/zmswap-m%d/zmswap-q%06d", config.path_swap, monitor->Id(), connkey ); + if ( buffered_playback ) { Debug( 1, "Cleaning swap files from %s", swap_path ); struct stat stat_buf; - if ( stat( swap_path, &stat_buf ) < 0 ) - { - if ( errno != ENOENT ) - { + if ( stat( swap_path, &stat_buf ) < 0 ) { + if ( errno != ENOENT ) { Error( "Can't stat '%s': %s", swap_path, strerror(errno) ); } - } - else if ( !S_ISDIR(stat_buf.st_mode) ) - { + } else if ( !S_ISDIR(stat_buf.st_mode) ) { Error( "Swap image path '%s' is not a directory", swap_path ); - } - else - { + } else { char glob_pattern[PATH_MAX] = ""; snprintf( glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path ); glob_t pglob; int glob_status = glob( glob_pattern, 0, 0, &pglob ); - if ( glob_status != 0 ) - { - if ( glob_status < 0 ) - { + if ( glob_status != 0 ) { + if ( glob_status < 0 ) { Error( "Can't glob '%s': %s", glob_pattern, strerror(errno) ); - } - else - { + } else { Debug( 1, "Can't glob '%s': %d", glob_pattern, glob_status ); } - } - else - { - for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) - { - if ( unlink( pglob.gl_pathv[i] ) < 0 ) - { + } else { + for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) { + if ( unlink( pglob.gl_pathv[i] ) < 0 ) { Error( "Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno) ); } } } globfree( &pglob ); - if ( rmdir( swap_path ) < 0 ) - { + if ( rmdir( swap_path ) < 0 ) { Error( "Can't rmdir '%s': %s", swap_path, strerror(errno) ); } } @@ -4250,8 +3917,7 @@ void MonitorStream::runStream() closeComms(); } -void Monitor::SingleImage( int scale) -{ +void Monitor::SingleImage( int scale) { int img_buffer_size = 0; static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; @@ -4259,14 +3925,12 @@ void Monitor::SingleImage( int scale) Snapshot *snap = &image_buffer[index]; Image *snap_image = snap->image; - if ( scale != ZM_SCALE_BASE ) - { + if ( scale != ZM_SCALE_BASE ) { scaled_image.Assign( *snap_image ); scaled_image.Scale( scale ); snap_image = &scaled_image; } - if ( !config.timestamp_on_capture ) - { + if ( !config.timestamp_on_capture ) { TimestampImage( snap_image, snap->timestamp ); } snap_image->EncodeJpeg( img_buffer, &img_buffer_size ); @@ -4276,21 +3940,18 @@ void Monitor::SingleImage( int scale) fwrite( img_buffer, img_buffer_size, 1, stdout ); } -void Monitor::SingleImageRaw( int scale) -{ +void Monitor::SingleImageRaw( int scale) { Image scaled_image; int index = shared_data->last_write_index%image_buffer_count; Snapshot *snap = &image_buffer[index]; Image *snap_image = snap->image; - if ( scale != ZM_SCALE_BASE ) - { + if ( scale != ZM_SCALE_BASE ) { scaled_image.Assign( *snap_image ); scaled_image.Scale( scale ); snap_image = &scaled_image; } - if ( !config.timestamp_on_capture ) - { + if ( !config.timestamp_on_capture ) { TimestampImage( snap_image, snap->timestamp ); } @@ -4299,8 +3960,7 @@ void Monitor::SingleImageRaw( int scale) fwrite( snap_image->Buffer(), snap_image->Size(), 1, stdout ); } -void Monitor::SingleImageZip( int scale) -{ +void Monitor::SingleImageZip( int scale) { unsigned long img_buffer_size = 0; static Bytef img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; @@ -4308,14 +3968,12 @@ void Monitor::SingleImageZip( int scale) Snapshot *snap = &image_buffer[index]; Image *snap_image = snap->image; - if ( scale != ZM_SCALE_BASE ) - { + if ( scale != ZM_SCALE_BASE ) { scaled_image.Assign( *snap_image ); scaled_image.Scale( scale ); snap_image = &scaled_image; } - if ( !config.timestamp_on_capture ) - { + if ( !config.timestamp_on_capture ) { TimestampImage( snap_image, snap->timestamp ); } snap_image->Zip( img_buffer, &img_buffer_size ); @@ -4324,3 +3982,16 @@ void Monitor::SingleImageZip( int scale) fprintf( stdout, "Content-Type: image/x-rgbz\r\n\r\n" ); fwrite( img_buffer, img_buffer_size, 1, stdout ); } + +unsigned int Monitor::Colours() const { return( camera->Colours() ); } +unsigned int Monitor::SubpixelOrder() const { return( camera->SubpixelOrder() ); } +int Monitor::PrimeCapture() { + return( camera->PrimeCapture() ); +} +int Monitor::PreCapture() { + return( camera->PreCapture() ); +} +int Monitor::PostCapture() { + return( camera->PostCapture() ); +} +Monitor::Orientation Monitor::getOrientation() const { return orientation; } diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 57fd7a369..b5c3acfce 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_MONITOR_H @@ -29,6 +29,7 @@ #include "zm_rgb.h" #include "zm_zone.h" #include "zm_event.h" +class Monitor; #include "zm_camera.h" #include "zm_utils.h" @@ -45,20 +46,17 @@ // This is the main class for monitors. Each monitor is associated // with a camera and is effectively a collector for events. // -class Monitor -{ -friend class MonitorStream; +class Monitor { + friend class MonitorStream; public: - typedef enum - { + typedef enum { QUERY=0, CAPTURE, ANALYSIS } Purpose; - typedef enum - { + typedef enum { NONE=1, MONITOR, MODECT, @@ -67,8 +65,7 @@ public: NODECT } Function; - typedef enum - { + typedef enum { ROTATE_0=1, ROTATE_90, ROTATE_180, @@ -77,8 +74,7 @@ public: FLIP_VERT } Orientation; - typedef enum - { + typedef enum { IDLE, PREALARM, ALARM, @@ -86,6 +82,12 @@ public: TAPE } State; + typedef enum { + DISABLED, + X264ENCODE, + H264PASSTHROUGH, + } VideoWriter; + protected: typedef std::set ZoneSet; @@ -94,8 +96,7 @@ protected: typedef enum { CLOSE_TIME, CLOSE_IDLE, CLOSE_ALARM } EventCloseMode; /* sizeof(SharedData) expected to be 336 bytes on 32bit and 64bit */ - typedef struct - { + typedef struct { uint32_t size; /* +0 */ uint32_t last_write_index; /* +4 */ uint32_t last_read_index; /* +8 */ @@ -120,12 +121,12 @@ protected: ** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16. */ union { /* +64 */ - time_t last_write_time; - uint64_t extrapad1; + time_t last_write_time; + uint64_t extrapad1; }; union { /* +72 */ - time_t last_read_time; - uint64_t extrapad2; + time_t last_read_time; + uint64_t extrapad2; }; uint8_t control_state[256]; /* +80 */ @@ -134,8 +135,7 @@ protected: typedef enum { TRIGGER_CANCEL, TRIGGER_ON, TRIGGER_OFF } TriggerState; /* sizeof(TriggerData) expected to be 560 on 32bit & and 64bit */ - typedef struct - { + typedef struct { uint32_t size; uint32_t trigger_state; uint32_t trigger_score; @@ -146,15 +146,25 @@ protected: } TriggerData; /* sizeof(Snapshot) expected to be 16 bytes on 32bit and 32 bytes on 64bit */ - struct Snapshot - { + struct Snapshot { struct timeval *timestamp; Image *image; void* padding; }; - class MonitorLink - { + //TODO: Technically we can't exclude this struct when people don't have avformat as the Memory.pm module doesn't know about avformat +#if 1 + //sizeOf(VideoStoreData) expected to be 4104 bytes on 32bit and 64bit + typedef struct { + uint32_t size; + char event_file[4096]; + timeval recording; // used as both bool and a pointer to the timestamp when recording should begin + //uint32_t frameNumber; + } VideoStoreData; + +#endif // HAVE_LIBAVFORMAT + + class MonitorLink { protected: unsigned int id; char name[64]; @@ -173,6 +183,7 @@ protected: volatile SharedData *shared_data; volatile TriggerData *trigger_data; + volatile VideoStoreData *video_store_data; int last_state; int last_event; @@ -181,21 +192,17 @@ protected: MonitorLink( int p_id, const char *p_name ); ~MonitorLink(); - inline int Id() const - { + inline int Id() const { return( id ); } - inline const char *Name() const - { + inline const char *Name() const { return( name ); } - inline bool isConnected() const - { + inline bool isConnected() const { return( connected ); } - inline time_t getLastConnectTime() const - { + inline time_t getLastConnectTime() const { return( last_connect_time ); } @@ -209,17 +216,25 @@ protected: protected: // These are read from the DB and thereafter remain unchanged - unsigned int id; - char name[64]; - unsigned int server_id; - Function function; // What the monitor is doing - bool enabled; // Whether the monitor is enabled or asleep - unsigned int width; // Normally the same as the camera, but not if partly rotated - unsigned int height; // Normally the same as the camera, but not if partly rotated - bool v4l_multi_buffer; - unsigned int v4l_captures_per_frame; - Orientation orientation; // Whether the image has to be rotated at all - unsigned int deinterlacing; + unsigned int id; + char name[64]; + unsigned int server_id; // Id of the Server object + Function function; // What the monitor is doing + bool enabled; // Whether the monitor is enabled or asleep + unsigned int width; // Normally the same as the camera, but not if partly rotated + unsigned int height; // Normally the same as the camera, but not if partly rotated + bool v4l_multi_buffer; + unsigned int v4l_captures_per_frame; + Orientation orientation; // Whether the image has to be rotated at all + unsigned int deinterlacing; + bool videoRecording; + + int savejpegspref; + VideoWriter videowriter; + std::string encoderparams; + std::vector encoderparamsvec; + bool record_audio; // Whether to store the audio that we receive + int brightness; // The statically saved brightness of the camera int contrast; // The statically saved contrast of the camera int hue; // The statically saved hue of the camera @@ -249,7 +264,7 @@ protected: int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm. bool track_motion; // Whether this monitor tries to track detected motion Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected - bool embed_exif; // Whether to embed Exif data into each image frame or not + bool embed_exif; // Whether to embed Exif data into each image frame or not double fps; Image delta_image; @@ -269,7 +284,7 @@ protected: time_t start_time; time_t last_fps_time; time_t auto_resume_time; - unsigned int last_motion_score; + unsigned int last_motion_score; EventCloseMode event_close_mode; @@ -284,6 +299,7 @@ protected: SharedData *shared_data; TriggerData *trigger_data; + VideoStoreData *video_store_data; Snapshot *image_buffer; Snapshot next_buffer; /* Used by four field deinterlacing */ @@ -305,65 +321,102 @@ protected: MonitorLink **linked_monitors; public: + Monitor( int p_id ); // OurCheckAlarms seems to be unused. Check it on zm_monitor.cpp for more info. //bool OurCheckAlarms( Zone *zone, const Image *pImage ); - Monitor( int p_id, const char *p_name, unsigned int p_server_id, int p_function, bool p_enabled, const char *p_linked_monitors, Camera *p_camera, int p_orientation, unsigned int p_deinterlacing, const char *p_event_prefix, const char *p_label_format, const Coord &p_label_coord, int label_size, int p_image_buffer_count, int p_warmup_count, int p_pre_event_count, int p_post_event_count, int p_stream_replay_buffer, int p_alarm_frame_count, int p_section_length, int p_frame_skip, int p_motion_frame_skip, double p_analysis_fps, unsigned int p_analysis_update_delay, int p_capture_delay, int p_alarm_capture_delay, int p_fps_report_interval, int p_ref_blend_perc, int p_alarm_ref_blend_perc, bool p_track_motion, Rgb p_signal_check_colour, bool p_embed_exif, Purpose p_purpose, int p_n_zones=0, Zone *p_zones[]=0 ); + Monitor( + int p_id, + const char *p_name, + unsigned int p_server_id, + int p_function, + bool p_enabled, + const char *p_linked_monitors, + Camera *p_camera, + int p_orientation, + unsigned int p_deinterlacing, + int p_savejpegs, + VideoWriter p_videowriter, + std::string p_encoderparams, + bool p_record_audio, + const char *p_event_prefix, + const char *p_label_format, + const Coord &p_label_coord, + int label_size, + int p_image_buffer_count, + int p_warmup_count, + int p_pre_event_count, + int p_post_event_count, + int p_stream_replay_buffer, + int p_alarm_frame_count, + int p_section_length, + int p_frame_skip, + int p_motion_frame_skip, + double p_analysis_fps, + unsigned int p_analysis_update_delay, + int p_capture_delay, + int p_alarm_capture_delay, + int p_fps_report_interval, + int p_ref_blend_perc, + int p_alarm_ref_blend_perc, + bool p_track_motion, + Rgb p_signal_check_colour, + bool p_embed_exif, + Purpose p_purpose, + int p_n_zones=0, + Zone *p_zones[]=0 + ); ~Monitor(); void AddZones( int p_n_zones, Zone *p_zones[] ); void AddPrivacyBitmask( Zone *p_zones[] ); bool connect(); - inline int ShmValid() const - { + inline int ShmValid() const { return( shared_data->valid ); } - inline int Id() const - { + inline int Id() const { return( id ); } - inline const char *Name() const - { + inline const char *Name() const { return( name ); } - inline Function GetFunction() const - { + inline Function GetFunction() const { return( function ); } - inline bool Enabled() - { + inline bool Enabled() { if ( function <= MONITOR ) return( false ); return( enabled ); } - inline const char *EventPrefix() const - { + inline const char *EventPrefix() const { return( event_prefix ); } - inline bool Ready() - { + inline bool Ready() { if ( function <= MONITOR ) return( false ); return( image_count > ready_count ); } - inline bool Active() - { + inline bool Active() { if ( function <= MONITOR ) return( false ); return( enabled && shared_data->active ); } - inline bool Exif() - { + inline bool Exif() { return( embed_exif ); } + Orientation getOrientation() const; - unsigned int Width() const { return( width ); } - unsigned int Height() const { return( height ); } - unsigned int Colours() const { return( camera->Colours() ); } - unsigned int SubpixelOrder() const { return( camera->SubpixelOrder() ); } + unsigned int Width() const { return width; } + unsigned int Height() const { return height; } + unsigned int Colours() const; + unsigned int SubpixelOrder() const; + int GetOptSaveJPEGs() const { return( savejpegspref ); } + VideoWriter GetOptVideoWriter() const { return( videowriter ); } + const std::vector* GetOptEncoderParams() const { return( &encoderparamsvec ); } + unsigned int GetPreEventCount() const { return pre_event_count; }; State GetState() const; int GetImage( int index=-1, int scale=100 ); struct timeval GetTimestamp( int index=-1 ) const; @@ -392,19 +445,10 @@ public: int actionColour( int p_colour=-1 ); int actionContrast( int p_contrast=-1 ); - inline int PrimeCapture() - { - return( camera->PrimeCapture() ); - } - inline int PreCapture() - { - return( camera->PreCapture() ); - } + int PrimeCapture(); + int PreCapture(); int Capture(); - int PostCapture() - { - return( camera->PostCapture() ); - } + int PostCapture(); unsigned int DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ); // DetectBlack seems to be unused. Check it on zm_monitor.cpp for more info. @@ -445,8 +489,7 @@ public: #define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit)) -class MonitorStream : public StreamBase -{ +class MonitorStream : public StreamBase { protected: typedef struct SwapImage { bool valid; @@ -477,19 +520,15 @@ protected: void processCommand( const CmdMsg *msg ); public: - MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) - { + MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) { } - void setStreamBuffer( int p_playback_buffer ) - { + void setStreamBuffer( int p_playback_buffer ) { playback_buffer = p_playback_buffer; } - void setStreamTTL( time_t p_ttl ) - { + void setStreamTTL( time_t p_ttl ) { ttl = p_ttl; } - bool setStreamStart( int monitor_id ) - { + bool setStreamStart( int monitor_id ) { return loadMonitor( monitor_id ); } void runStream(); diff --git a/src/zm_mpeg.cpp b/src/zm_mpeg.cpp index bf2ac9952..e949052dc 100644 --- a/src/zm_mpeg.cpp +++ b/src/zm_mpeg.cpp @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include @@ -25,8 +25,7 @@ #include "zm_mpeg.h" #if HAVE_LIBAVCODEC -extern "C" -{ +extern "C" { #include #include } @@ -34,320 +33,284 @@ extern "C" bool VideoStream::initialised = false; VideoStream::MimeData VideoStream::mime_data[] = { - { "asf", "video/x-ms-asf" }, - { "swf", "application/x-shockwave-flash" }, - { "flv", "video/x-flv" }, - { "mov", "video/quicktime" } + { "asf", "video/x-ms-asf" }, + { "swf", "application/x-shockwave-flash" }, + { "flv", "video/x-flv" }, + { "mov", "video/quicktime" } }; -void VideoStream::Initialise( ) -{ - if ( logDebugging() ) +void VideoStream::Initialise( ) { + if ( logDebugging() ) { av_log_set_level( AV_LOG_DEBUG ); - else - av_log_set_level( AV_LOG_QUIET ); - - av_register_all( ); -#if LIBAVFORMAT_VERSION_CHECK(53, 13, 0, 19, 0) - avformat_network_init(); -#endif - initialised = true; -} - -void VideoStream::SetupFormat( ) -{ - /* allocate the output media context */ - ofc = NULL; -#if (LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 2, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - avformat_alloc_output_context2( &ofc, NULL, format, filename ); -#else - AVFormatContext *s= avformat_alloc_context(); - if(!s) - { - Fatal( "avformat_alloc_context failed %d \"%s\"", (size_t)ofc, av_err2str((size_t)ofc) ); - } - - AVOutputFormat *oformat; - if (format) { -#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) - oformat = av_guess_format(format, NULL, NULL); -#else - oformat = guess_format(format, NULL, NULL); -#endif - if (!oformat) { - Fatal( "Requested output format '%s' is not a suitable output format", format ); - } } else { -#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) - oformat = av_guess_format(NULL, filename, NULL); -#else - oformat = guess_format(NULL, filename, NULL); -#endif - if (!oformat) { - Fatal( "Unable to find a suitable output format for '%s'", format ); - } - } - s->oformat = oformat; - - if (s->oformat->priv_data_size > 0) { - s->priv_data = av_mallocz(s->oformat->priv_data_size); - if (!s->priv_data) - { - Fatal( "Could not allocate private data for output format." ); - } -#if LIBAVFORMAT_VERSION_CHECK(52, 92, 0, 92, 0) - if (s->oformat->priv_class) { - *(const AVClass**)s->priv_data = s->oformat->priv_class; - av_opt_set_defaults(s->priv_data); - } -#endif - } - else - { - s->priv_data = NULL; - } - - if(filename) - { - snprintf( s->filename, sizeof(s->filename), "%s", filename ); - } - - ofc = s; -#endif - if ( !ofc ) - { - Fatal( "avformat_alloc_..._context failed: %d", ofc ); + av_log_set_level( AV_LOG_QUIET ); } - of = ofc->oformat; - Debug( 1, "Using output format: %s (%s)", of->name, of->long_name ); + av_register_all( ); +#if LIBAVFORMAT_VERSION_CHECK(53, 13, 0, 19, 0) + avformat_network_init(); +#endif + initialised = true; } -void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate ) -{ - /* ffmpeg format matching */ - switch(colours) { - case ZM_COLOUR_RGB24: - { - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { - /* BGR subpixel order */ - pf = AV_PIX_FMT_BGR24; - } else { - /* Assume RGB subpixel order */ - pf = AV_PIX_FMT_RGB24; - } - break; - } - case ZM_COLOUR_RGB32: - { - if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - /* ARGB subpixel order */ - pf = AV_PIX_FMT_ARGB; - } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - /* ABGR subpixel order */ - pf = AV_PIX_FMT_ABGR; - } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - /* BGRA subpixel order */ - pf = AV_PIX_FMT_BGRA; - } else { - /* Assume RGBA subpixel order */ - pf = AV_PIX_FMT_RGBA; - } - break; - } - case ZM_COLOUR_GRAY8: - pf = AV_PIX_FMT_GRAY8; - break; - default: - Panic("Unexpected colours: %d",colours); - break; - } - - if ( strcmp( "rtp", of->name ) == 0 ) - { - // RTP must have a packet_size. - // Not sure what this value should be really... - ofc->packet_size = width*height; - - if ( of->video_codec == AV_CODEC_ID_NONE) - { - // RTP does not have a default codec in ffmpeg <= 0.8. - of->video_codec = AV_CODEC_ID_MPEG4; - } - } - - _AVCODECID codec_id = of->video_codec; - if ( codec_name ) - { - AVCodec *a = avcodec_find_encoder_by_name(codec_name); - if ( a ) - { - codec_id = a->id; - } - else - { -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - Debug( 1, "Could not find codec \"%s\". Using default \"%s\"", codec_name, avcodec_get_name( codec_id ) ); +void VideoStream::SetupFormat( ) { + /* allocate the output media context */ + ofc = NULL; +#if (LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 2, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) + avformat_alloc_output_context2( &ofc, NULL, format, filename ); #else - Debug( 1, "Could not find codec \"%s\". Using default \"%d\"", codec_name, codec_id ); + AVFormatContext *s= avformat_alloc_context(); + if(!s) { + Fatal( "avformat_alloc_context failed %d \"%s\"", (size_t)ofc, av_err2str((size_t)ofc) ); + } + + AVOutputFormat *oformat; + if (format) { +#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) + oformat = av_guess_format(format, NULL, NULL); +#else + oformat = guess_format(format, NULL, NULL); #endif - } - } - - /* add the video streams using the default format codecs - and initialize the codecs */ - ost = NULL; - if ( codec_id != AV_CODEC_ID_NONE ) - { - codec = avcodec_find_encoder( codec_id ); - if ( !codec ) - { -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - Fatal( "Could not find encoder for '%s'", avcodec_get_name( codec_id ) ); + if (!oformat) { + Fatal( "Requested output format '%s' is not a suitable output format", format ); + } + } else { +#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) + oformat = av_guess_format(NULL, filename, NULL); #else - Fatal( "Could not find encoder for '%d'", codec_id ); + oformat = guess_format(NULL, filename, NULL); +#endif + if (!oformat) { + Fatal( "Unable to find a suitable output format for '%s'", format ); + } + } + s->oformat = oformat; + + if (s->oformat->priv_data_size > 0) { + s->priv_data = av_mallocz(s->oformat->priv_data_size); + if (!s->priv_data) { + Fatal( "Could not allocate private data for output format." ); + } +#if LIBAVFORMAT_VERSION_CHECK(52, 92, 0, 92, 0) + if (s->oformat->priv_class) { + *(const AVClass**)s->priv_data = s->oformat->priv_class; + av_opt_set_defaults(s->priv_data); + } +#endif + } else { + s->priv_data = NULL; + } + + if ( filename ) { + snprintf( s->filename, sizeof(s->filename), "%s", filename ); + } + + ofc = s; +#endif + if ( !ofc ) { + Fatal( "avformat_alloc_..._context failed: %d", ofc ); + } + + of = ofc->oformat; + Debug( 1, "Using output format: %s (%s)", of->name, of->long_name ); +} + +void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate ) { + /* ffmpeg format matching */ + switch(colours) { + case ZM_COLOUR_RGB24: + { + if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + /* BGR subpixel order */ + pf = AV_PIX_FMT_BGR24; + } else { + /* Assume RGB subpixel order */ + pf = AV_PIX_FMT_RGB24; + } + break; + } + case ZM_COLOUR_RGB32: + { + if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + /* ARGB subpixel order */ + pf = AV_PIX_FMT_ARGB; + } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + /* ABGR subpixel order */ + pf = AV_PIX_FMT_ABGR; + } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + /* BGRA subpixel order */ + pf = AV_PIX_FMT_BGRA; + } else { + /* Assume RGBA subpixel order */ + pf = AV_PIX_FMT_RGBA; + } + break; + } + case ZM_COLOUR_GRAY8: + pf = AV_PIX_FMT_GRAY8; + break; + default: + Panic("Unexpected colours: %d",colours); + break; + } + + if ( strcmp( "rtp", of->name ) == 0 ) { + // RTP must have a packet_size. + // Not sure what this value should be really... + ofc->packet_size = width*height; + + if ( of->video_codec == AV_CODEC_ID_NONE ) { + // RTP does not have a default codec in ffmpeg <= 0.8. + of->video_codec = AV_CODEC_ID_MPEG4; + } + } + + _AVCODECID codec_id = of->video_codec; + if ( codec_name ) { + AVCodec *a = avcodec_find_encoder_by_name(codec_name); + if ( a ) { + codec_id = a->id; + } else { +#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) + Debug( 1, "Could not find codec \"%s\". Using default \"%s\"", codec_name, avcodec_get_name( codec_id ) ); +#else + Debug( 1, "Could not find codec \"%s\". Using default \"%d\"", codec_name, codec_id ); #endif } + } + + /* add the video streams using the default format codecs + and initialize the codecs */ + ost = NULL; + if ( codec_id != AV_CODEC_ID_NONE ) { + codec = avcodec_find_encoder( codec_id ); + if ( !codec ) { +#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) + Fatal( "Could not find encoder for '%s'", avcodec_get_name( codec_id ) ); +#else + Fatal( "Could not find encoder for '%d'", codec_id ); +#endif + } #if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - Debug( 1, "Found encoder for '%s'", avcodec_get_name( codec_id ) ); + Debug( 1, "Found encoder for '%s'", avcodec_get_name( codec_id ) ); #else - Debug( 1, "Found encoder for '%d'", codec_id ); + Debug( 1, "Found encoder for '%d'", codec_id ); #endif #if LIBAVFORMAT_VERSION_CHECK(53, 10, 0, 17, 0) - ost = avformat_new_stream( ofc, codec ); + ost = avformat_new_stream( ofc, codec ); #else - ost = av_new_stream( ofc, 0 ); + ost = av_new_stream( ofc, 0 ); #endif - - if ( !ost ) - { - Fatal( "Could not alloc stream" ); - } - ost->id = ofc->nb_streams - 1; + + if ( !ost ) { + Fatal( "Could not alloc stream" ); + } + ost->id = ofc->nb_streams - 1; - Debug( 1, "Allocated stream" ); + Debug( 1, "Allocated stream" ); - AVCodecContext *c = ost->codec; + AVCodecContext *c = ost->codec; - c->codec_id = codec->id; - c->codec_type = codec->type; + c->codec_id = codec->id; + c->codec_type = codec->type; - c->pix_fmt = strcmp( "mjpeg", ofc->oformat->name ) == 0 ? AV_PIX_FMT_YUVJ422P : AV_PIX_FMT_YUV420P; - if ( bitrate <= 100 ) - { - // Quality based bitrate control (VBR). Scale is 1..31 where 1 is best. - // This gets rid of artifacts in the beginning of the movie; and well, even quality. - c->flags |= CODEC_FLAG_QSCALE; - c->global_quality = FF_QP2LAMBDA * (31 - (31 * (bitrate / 100.0))); - } - else - { - c->bit_rate = bitrate; - } + c->pix_fmt = strcmp( "mjpeg", ofc->oformat->name ) == 0 ? AV_PIX_FMT_YUVJ422P : AV_PIX_FMT_YUV420P; + if ( bitrate <= 100 ) { + // Quality based bitrate control (VBR). Scale is 1..31 where 1 is best. + // This gets rid of artifacts in the beginning of the movie; and well, even quality. + c->flags |= CODEC_FLAG_QSCALE; + c->global_quality = FF_QP2LAMBDA * (31 - (31 * (bitrate / 100.0))); + } else { + c->bit_rate = bitrate; + } - /* resolution must be a multiple of two */ - c->width = width; - c->height = height; - /* time base: this is the fundamental unit of time (in seconds) in terms - of which frame timestamps are represented. for fixed-fps content, - timebase should be 1/framerate and timestamp increments should be - identically 1. */ - c->time_base.den = frame_rate; - c->time_base.num = 1; - - Debug( 1, "Will encode in %d fps.", c->time_base.den ); - - /* emit one intra frame every second */ - c->gop_size = frame_rate; + /* resolution must be a multiple of two */ + c->width = width; + c->height = height; + /* time base: this is the fundamental unit of time (in seconds) in terms + of which frame timestamps are represented. for fixed-fps content, + timebase should be 1/framerate and timestamp increments should be + identically 1. */ + c->time_base.den = frame_rate; + c->time_base.num = 1; + + Debug( 1, "Will encode in %d fps.", c->time_base.den ); + + /* emit one intra frame every second */ + c->gop_size = frame_rate; - // some formats want stream headers to be separate - if ( of->flags & AVFMT_GLOBALHEADER ) - c->flags |= CODEC_FLAG_GLOBAL_HEADER; - } - else - { - Fatal( "of->video_codec == AV_CODEC_ID_NONE" ); - } + // some formats want stream headers to be separate + if ( of->flags & AVFMT_GLOBALHEADER ) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + } else { + Fatal( "of->video_codec == AV_CODEC_ID_NONE" ); + } } -void VideoStream::SetParameters( ) -{ +void VideoStream::SetParameters( ) { } -const char *VideoStream::MimeType( ) const -{ - for ( unsigned int i = 0; i < sizeof (mime_data) / sizeof (*mime_data); i++ ) - { - if ( strcmp( format, mime_data[i].format ) == 0 ) - { - Debug( 1, "MimeType is \"%s\"", mime_data[i].mime_type ); - return ( mime_data[i].mime_type); - } - } - const char *mime_type = of->mime_type; - if ( !mime_type ) - { - std::string mime = "video/"; - mime = mime.append( format ); - mime_type = mime.c_str( ); - Warning( "Unable to determine mime type for '%s' format, using '%s' as default", format, mime_type ); - } +const char *VideoStream::MimeType( ) const { + for ( unsigned int i = 0; i < sizeof (mime_data) / sizeof (*mime_data); i++ ) { + if ( strcmp( format, mime_data[i].format ) == 0 ) { + Debug( 1, "MimeType is \"%s\"", mime_data[i].mime_type ); + return ( mime_data[i].mime_type); + } + } + const char *mime_type = of->mime_type; + if ( !mime_type ) { + std::string mime = "video/"; + mime = mime.append( format ); + mime_type = mime.c_str( ); + Warning( "Unable to determine mime type for '%s' format, using '%s' as default", format, mime_type ); + } - Debug( 1, "MimeType is \"%s\"", mime_type ); + Debug( 1, "MimeType is \"%s\"", mime_type ); - return ( mime_type); + return ( mime_type); } -void VideoStream::OpenStream( ) -{ - int avRet; +void VideoStream::OpenStream( ) { + int avRet; - /* now that all the parameters are set, we can open the - video codecs and allocate the necessary encode buffers */ - if ( ost ) - { - AVCodecContext *c = ost->codec; - - /* open the codec */ + /* now that all the parameters are set, we can open the + video codecs and allocate the necessary encode buffers */ + if ( ost ) { + AVCodecContext *c = ost->codec; + + /* open the codec */ #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - if ( (avRet = avcodec_open( c, codec )) < 0 ) + if ( (avRet = avcodec_open( c, codec )) < 0 ) #else - if ( (avRet = avcodec_open2( c, codec, 0 )) < 0 ) + if ( (avRet = avcodec_open2( c, codec, 0 )) < 0 ) #endif - { - Fatal( "Could not open codec. Error code %d \"%s\"", avRet, av_err2str( avRet ) ); - } + { + Fatal( "Could not open codec. Error code %d \"%s\"", avRet, av_err2str( avRet ) ); + } - Debug( 1, "Opened codec" ); + Debug( 1, "Opened codec" ); - /* allocate the encoded raw picture */ + /* allocate the encoded raw picture */ #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - opicture = av_frame_alloc( ); + opicture = av_frame_alloc( ); #else - opicture = avcodec_alloc_frame( ); + opicture = avcodec_alloc_frame( ); #endif - if ( !opicture ) - { + if ( !opicture ) { Panic( "Could not allocate opicture" ); } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - int size = av_image_get_buffer_size( c->pix_fmt, c->width, - c->height, 1 ); + int size = av_image_get_buffer_size( c->pix_fmt, c->width, c->height, 1 ); #else int size = avpicture_get_size( c->pix_fmt, c->width, c->height ); #endif uint8_t *opicture_buf = (uint8_t *)av_malloc( size ); - if ( !opicture_buf ) - { -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &opicture ); -#else - av_freep( &opicture ); -#endif + if ( !opicture_buf ) { + av_frame_free( &opicture ); Panic( "Could not allocate opicture_buf" ); } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) @@ -362,31 +325,23 @@ void VideoStream::OpenStream( ) picture is needed too. It is then converted to the required output format */ tmp_opicture = NULL; - if ( c->pix_fmt != pf ) - { + if ( c->pix_fmt != pf ) { #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - tmp_opicture = av_frame_alloc( ); + tmp_opicture = av_frame_alloc( ); #else - tmp_opicture = avcodec_alloc_frame( ); + tmp_opicture = avcodec_alloc_frame( ); #endif - if ( !tmp_opicture ) - { + if ( !tmp_opicture ) { Panic( "Could not allocate tmp_opicture" ); } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - int size = av_image_get_buffer_size( pf, c->width, - c->height,1 ); + int size = av_image_get_buffer_size( pf, c->width, c->height,1 ); #else int size = avpicture_get_size( pf, c->width, c->height ); #endif uint8_t *tmp_opicture_buf = (uint8_t *)av_malloc( size ); - if ( !tmp_opicture_buf ) - { -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &tmp_opicture ); -#else - av_freep( &tmp_opicture ); -#endif + if ( !tmp_opicture_buf ) { + av_frame_free( &tmp_opicture ); Panic( "Could not allocate tmp_opicture_buf" ); } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) @@ -401,307 +356,262 @@ void VideoStream::OpenStream( ) } /* open the output file, if needed */ - if ( !(of->flags & AVFMT_NOFILE) ) - { + if ( !(of->flags & AVFMT_NOFILE) ) { int ret; #if LIBAVFORMAT_VERSION_CHECK(53, 15, 0, 21, 0) - ret = avio_open2( &ofc->pb, filename, AVIO_FLAG_WRITE, NULL, NULL ); + ret = avio_open2( &ofc->pb, filename, AVIO_FLAG_WRITE, NULL, NULL ); #elif LIBAVFORMAT_VERSION_CHECK(52, 102, 0, 102, 0) - ret = avio_open( &ofc->pb, filename, AVIO_FLAG_WRITE ); + ret = avio_open( &ofc->pb, filename, AVIO_FLAG_WRITE ); #else - ret = url_fopen( &ofc->pb, filename, AVIO_FLAG_WRITE ); + ret = url_fopen( &ofc->pb, filename, AVIO_FLAG_WRITE ); #endif - if ( ret < 0 ) - { - Fatal( "Could not open '%s'", filename ); - } + if ( ret < 0 ) { + Fatal( "Could not open '%s'", filename ); + } - Debug( 1, "Opened output \"%s\"", filename ); - } - else - { - Fatal( "of->flags & AVFMT_NOFILE" ); - } + Debug( 1, "Opened output \"%s\"", filename ); + } else { + Fatal( "of->flags & AVFMT_NOFILE" ); + } - video_outbuf = NULL; - if ( !(of->flags & AVFMT_RAWPICTURE) ) - { - /* allocate output buffer */ - /* XXX: API change will be done */ - // TODO: Make buffer dynamic. - video_outbuf_size = 4000000; - video_outbuf = (uint8_t *)malloc( video_outbuf_size ); - if ( video_outbuf == NULL ) { - Fatal("Unable to malloc memory for outbuf"); - } - } + video_outbuf = NULL; + if ( !(of->flags & AVFMT_RAWPICTURE) ) { + /* allocate output buffer */ + /* XXX: API change will be done */ + // TODO: Make buffer dynamic. + video_outbuf_size = 4000000; + video_outbuf = (uint8_t *)malloc( video_outbuf_size ); + if ( video_outbuf == NULL ) { + Fatal("Unable to malloc memory for outbuf"); + } + } #if LIBAVFORMAT_VERSION_CHECK(52, 101, 0, 101, 0) - av_dump_format(ofc, 0, filename, 1); + av_dump_format(ofc, 0, filename, 1); #else - dump_format(ofc, 0, filename, 1); + dump_format(ofc, 0, filename, 1); #endif #if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0) - int ret = av_write_header( ofc ); + int ret = av_write_header( ofc ); #else - int ret = avformat_write_header( ofc, NULL ); + int ret = avformat_write_header( ofc, NULL ); #endif - if ( ret < 0 ) - { - Fatal( "?_write_header failed with error %d \"%s\"", ret, av_err2str( ret ) ); - } + if ( ret < 0 ) { + Fatal( "?_write_header failed with error %d \"%s\"", ret, av_err2str( ret ) ); + } } VideoStream::VideoStream( const char *in_filename, const char *in_format, int bitrate, double frame_rate, int colours, int subpixelorder, int width, int height ) : - filename(in_filename), - format(in_format), - last_pts( -1 ), - streaming_thread(0), - do_streaming(true), - buffer_copy(NULL), - buffer_copy_lock(new pthread_mutex_t), - buffer_copy_size(0), - buffer_copy_used(0), + filename(in_filename), + format(in_format), + last_pts( -1 ), + streaming_thread(0), + do_streaming(true), + buffer_copy(NULL), + buffer_copy_lock(new pthread_mutex_t), + buffer_copy_size(0), + buffer_copy_used(0), packet_index(0) { - if ( !initialised ) - { - Initialise( ); - } - - if ( format ) - { - int length = strlen(format); - codec_and_format = new char[length+1];; - strcpy( codec_and_format, format ); - format = codec_and_format; - codec_name = NULL; - char *f = strchr(codec_and_format, '/'); - if (f != NULL) - { - *f = 0; - codec_name = f+1; - } - } + if ( !initialised ) { + Initialise( ); + } + + if ( format ) { + int length = strlen(format); + codec_and_format = new char[length+1];; + strcpy( codec_and_format, format ); + format = codec_and_format; + codec_name = NULL; + char *f = strchr(codec_and_format, '/'); + if (f != NULL) { + *f = 0; + codec_name = f+1; + } + } - SetupFormat( ); - SetupCodec( colours, subpixelorder, width, height, bitrate, frame_rate ); - SetParameters( ); - + SetupFormat( ); + SetupCodec( colours, subpixelorder, width, height, bitrate, frame_rate ); + SetParameters( ); + // Allocate buffered packets. packet_buffers = new AVPacket*[2]; packet_buffers[0] = new AVPacket(); packet_buffers[1] = new AVPacket(); packet_index = 0; - - // Initialize mutex used by streaming thread. - if ( pthread_mutex_init( buffer_copy_lock, NULL ) != 0 ) - { - Fatal("pthread_mutex_init failed"); - } + + // Initialize mutex used by streaming thread. + if ( pthread_mutex_init( buffer_copy_lock, NULL ) != 0 ) { + Fatal("pthread_mutex_init failed"); + } } -VideoStream::~VideoStream( ) -{ - Debug( 1, "VideoStream destructor." ); - - // Stop streaming thread. - if ( streaming_thread ) - { - do_streaming = false; - void* thread_exit_code; +VideoStream::~VideoStream( ) { + Debug( 1, "VideoStream destructor." ); + + // Stop streaming thread. + if ( streaming_thread ) { + do_streaming = false; + void* thread_exit_code; + + Debug( 1, "Asking streaming thread to exit." ); + + // Wait for thread to exit. + pthread_join(streaming_thread, &thread_exit_code); + } + + if ( buffer_copy != NULL ) { + av_free( buffer_copy ); + } - Debug( 1, "Asking streaming thread to exit." ); + if ( buffer_copy_lock ) { + if ( pthread_mutex_destroy( buffer_copy_lock ) != 0 ) { + Error( "pthread_mutex_destroy failed" ); + } + delete buffer_copy_lock; + } - // Wait for thread to exit. - pthread_join(streaming_thread, &thread_exit_code); - } - - if ( buffer_copy != NULL ) - { - av_free( buffer_copy ); - } - - if ( buffer_copy_lock ) - { - if ( pthread_mutex_destroy( buffer_copy_lock ) != 0 ) - { - Error( "pthread_mutex_destroy failed" ); - } - delete buffer_copy_lock; - } - if (packet_buffers) { delete packet_buffers[0]; delete packet_buffers[1]; delete[] packet_buffers; } - - /* close each codec */ - if ( ost ) - { - avcodec_close( ost->codec ); - av_free( opicture->data[0] ); -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &opicture ); -#else - av_freep( &opicture ); -#endif - if ( tmp_opicture ) - { - av_free( tmp_opicture->data[0] ); -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &tmp_opicture ); -#else - av_freep( &tmp_opicture ); -#endif - } - av_free( video_outbuf ); - } + + /* close each codec */ + if ( ost ) { + avcodec_close( ost->codec ); + av_free( opicture->data[0] ); + av_frame_free( &opicture ); + if ( tmp_opicture ) { + av_free( tmp_opicture->data[0] ); + av_frame_free( &tmp_opicture ); + } + av_free( video_outbuf ); + } - /* write the trailer, if any */ - av_write_trailer( ofc ); + /* write the trailer, if any */ + av_write_trailer( ofc ); - /* free the streams */ - for ( unsigned int i = 0; i < ofc->nb_streams; i++ ) - { - av_freep( &ofc->streams[i] ); - } + /* free the streams */ + for ( unsigned int i = 0; i < ofc->nb_streams; i++ ) { + av_freep( &ofc->streams[i] ); + } - if ( !(of->flags & AVFMT_NOFILE) ) - { - /* close the output file */ + if ( !(of->flags & AVFMT_NOFILE) ) { + /* close the output file */ #if LIBAVFORMAT_VERSION_CHECK(52, 105, 0, 105, 0) - avio_close( ofc->pb ); + avio_close( ofc->pb ); #else - url_fclose( ofc->pb ); + url_fclose( ofc->pb ); #endif - } + } - /* free the stream */ - av_free( ofc ); - - /* free format and codec_name data. */ - if ( codec_and_format ) - { - delete codec_and_format; - } + /* free the stream */ + av_free( ofc ); + + /* free format and codec_name data. */ + if ( codec_and_format ) { + delete codec_and_format; + } } -double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool _add_timestamp, unsigned int _timestamp ) -{ - if ( pthread_mutex_lock( buffer_copy_lock ) != 0 ) - { - Fatal( "EncodeFrame: pthread_mutex_lock failed." ); - } - - if (buffer_copy_size < buffer_size) - { - if ( buffer_copy ) - { - av_free( buffer_copy ); - } - - // Allocate a buffer to store source images for the streaming thread to encode. - buffer_copy = (uint8_t *)av_malloc( buffer_size ); - if ( !buffer_copy ) - { - Panic( "Could not allocate buffer_copy" ); - } - buffer_copy_size = buffer_size; - } - - add_timestamp = _add_timestamp; - timestamp = _timestamp; - buffer_copy_used = buffer_size; - memcpy(buffer_copy, buffer, buffer_size); - - if ( pthread_mutex_unlock( buffer_copy_lock ) != 0 ) - { - Fatal( "EncodeFrame: pthread_mutex_unlock failed." ); - } - - if ( streaming_thread == 0 ) - { - Debug( 1, "Starting streaming thread" ); - - // Start a thread for streaming encoded video. - if (pthread_create( &streaming_thread, NULL, StreamingThreadCallback, (void*) this) != 0){ - // Log a fatal error and exit the process. - Fatal( "VideoStream failed to create streaming thread." ); - } - } - - //return ActuallyEncodeFrame( buffer, buffer_size, add_timestamp, timestamp); - - return _timestamp; +double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool _add_timestamp, unsigned int _timestamp ) { + if ( pthread_mutex_lock( buffer_copy_lock ) != 0 ) { + Fatal( "EncodeFrame: pthread_mutex_lock failed." ); + } + + if (buffer_copy_size < buffer_size) { + if ( buffer_copy ) { + av_free( buffer_copy ); + } + + // Allocate a buffer to store source images for the streaming thread to encode. + buffer_copy = (uint8_t *)av_malloc( buffer_size ); + if ( !buffer_copy ) { + Panic( "Could not allocate buffer_copy" ); + } + buffer_copy_size = buffer_size; + } + + add_timestamp = _add_timestamp; + timestamp = _timestamp; + buffer_copy_used = buffer_size; + memcpy(buffer_copy, buffer, buffer_size); + + if ( pthread_mutex_unlock( buffer_copy_lock ) != 0 ) { + Fatal( "EncodeFrame: pthread_mutex_unlock failed." ); + } + + if ( streaming_thread == 0 ) { + Debug( 1, "Starting streaming thread" ); + + // Start a thread for streaming encoded video. + if (pthread_create( &streaming_thread, NULL, StreamingThreadCallback, (void*) this) != 0){ + // Log a fatal error and exit the process. + Fatal( "VideoStream failed to create streaming thread." ); + } + } + + //return ActuallyEncodeFrame( buffer, buffer_size, add_timestamp, timestamp); + + return _timestamp; } -double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp, unsigned int timestamp ) -{ +double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp, unsigned int timestamp ) { #ifdef HAVE_LIBSWSCALE - static struct SwsContext *img_convert_ctx = 0; + static struct SwsContext *img_convert_ctx = 0; #endif // HAVE_LIBSWSCALE - AVCodecContext *c = ost->codec; + AVCodecContext *c = ost->codec; - if ( c->pix_fmt != pf ) - { - memcpy( tmp_opicture->data[0], buffer, buffer_size ); + if ( c->pix_fmt != pf ) { + memcpy( tmp_opicture->data[0], buffer, buffer_size ); #ifdef HAVE_LIBSWSCALE - if ( !img_convert_ctx ) - { - img_convert_ctx = sws_getCachedContext( NULL, c->width, c->height, pf, c->width, c->height, c->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL ); - if ( !img_convert_ctx ) - Panic( "Unable to initialise image scaling context" ); - } - sws_scale( img_convert_ctx, tmp_opicture->data, tmp_opicture->linesize, 0, c->height, opicture->data, opicture->linesize ); + if ( !img_convert_ctx ) { + img_convert_ctx = sws_getCachedContext( NULL, c->width, c->height, pf, c->width, c->height, c->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL ); + if ( !img_convert_ctx ) + Panic( "Unable to initialise image scaling context" ); + } + sws_scale( img_convert_ctx, tmp_opicture->data, tmp_opicture->linesize, 0, c->height, opicture->data, opicture->linesize ); #else // HAVE_LIBSWSCALE - Fatal( "swscale is required for MPEG mode" ); + Fatal( "swscale is required for MPEG mode" ); #endif // HAVE_LIBSWSCALE - } - else - { - memcpy( opicture->data[0], buffer, buffer_size ); - } - AVFrame *opicture_ptr = opicture; - - AVPacket *pkt = packet_buffers[packet_index]; - av_init_packet( pkt ); + } else { + memcpy( opicture->data[0], buffer, buffer_size ); + } + AVFrame *opicture_ptr = opicture; + + AVPacket *pkt = packet_buffers[packet_index]; + av_init_packet( pkt ); int got_packet = 0; - if ( of->flags & AVFMT_RAWPICTURE ) - { + if ( of->flags & AVFMT_RAWPICTURE ) { #if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2) - pkt->flags |= AV_PKT_FLAG_KEY; + pkt->flags |= AV_PKT_FLAG_KEY; #else - pkt->flags |= PKT_FLAG_KEY; + pkt->flags |= PKT_FLAG_KEY; #endif - pkt->stream_index = ost->index; - pkt->data = (uint8_t *)opicture_ptr; - pkt->size = sizeof (AVPicture); + pkt->stream_index = ost->index; + pkt->data = (uint8_t *)opicture_ptr; + pkt->size = sizeof (AVPicture); got_packet = 1; - } - else - { - opicture_ptr->pts = c->frame_number; - opicture_ptr->quality = c->global_quality; + } else { + opicture_ptr->pts = c->frame_number; + opicture_ptr->quality = c->global_quality; #if LIBAVFORMAT_VERSION_CHECK(54, 1, 0, 2, 100) - int ret = avcodec_encode_video2( c, pkt, opicture_ptr, &got_packet ); - if ( ret != 0 ) - { - Fatal( "avcodec_encode_video2 failed with errorcode %d \"%s\"", ret, av_err2str( ret ) ); - } + int ret = avcodec_encode_video2( c, pkt, opicture_ptr, &got_packet ); + if ( ret != 0 ) { + Fatal( "avcodec_encode_video2 failed with errorcode %d \"%s\"", ret, av_err2str( ret ) ); + } #else - int out_size = avcodec_encode_video( c, video_outbuf, video_outbuf_size, opicture_ptr ); - got_packet = out_size > 0 ? 1 : 0; - pkt->data = got_packet ? video_outbuf : NULL; - pkt->size = got_packet ? out_size : 0; + int out_size = avcodec_encode_video( c, video_outbuf, video_outbuf_size, opicture_ptr ); + got_packet = out_size > 0 ? 1 : 0; + pkt->data = got_packet ? video_outbuf : NULL; + pkt->size = got_packet ? out_size : 0; #endif - if ( got_packet ) - { + if ( got_packet ) { // if ( c->coded_frame->key_frame ) // { //#if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2) @@ -711,12 +621,10 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, //#endif // } - if ( pkt->pts != (int64_t)AV_NOPTS_VALUE ) - { + if ( pkt->pts != (int64_t)AV_NOPTS_VALUE ) { pkt->pts = av_rescale_q( pkt->pts, c->time_base, ost->time_base ); } - if ( pkt->dts != (int64_t)AV_NOPTS_VALUE ) - { + if ( pkt->dts != (int64_t)AV_NOPTS_VALUE ) { pkt->dts = av_rescale_q( pkt->dts, c->time_base, ost->time_base ); } pkt->duration = av_rescale_q( pkt->duration, c->time_base, ost->time_base ); @@ -728,52 +636,49 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, } int VideoStream::SendPacket(AVPacket *packet) { - - int ret = av_write_frame( ofc, packet ); - if ( ret != 0 ) - { - Fatal( "Error %d while writing video frame: %s", ret, av_err2str( errno ) ); - } + + int ret = av_write_frame( ofc, packet ); + if ( ret != 0 ) { + Fatal( "Error %d while writing video frame: %s", ret, av_err2str( errno ) ); + } #if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) av_packet_unref( packet ); #else av_free_packet( packet ); #endif - return ret; + return ret; } void *VideoStream::StreamingThreadCallback(void *ctx){ - - Debug( 1, "StreamingThreadCallback started" ); - + + Debug( 1, "StreamingThreadCallback started" ); + if (ctx == NULL) return NULL; VideoStream* videoStream = reinterpret_cast(ctx); - - const uint64_t nanosecond_multiplier = 1000000000; - - uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->ost->codec->time_base.num) / (videoStream->ost->codec->time_base.den) ); - uint64_t frame_count = 0; - timespec start_time; - clock_gettime(CLOCK_MONOTONIC, &start_time); - uint64_t start_time_ns = (start_time.tv_sec*nanosecond_multiplier) + start_time.tv_nsec; - while(videoStream->do_streaming) - { - timespec current_time; - clock_gettime(CLOCK_MONOTONIC, ¤t_time); - uint64_t current_time_ns = (current_time.tv_sec*nanosecond_multiplier) + current_time.tv_nsec; - uint64_t target_ns = start_time_ns + (target_interval_ns * frame_count); - - if ( current_time_ns < target_ns ) - { - // It's not time to render a frame yet. - usleep( (target_ns - current_time_ns) * 0.001 ); - } - + + const uint64_t nanosecond_multiplier = 1000000000; + + uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->ost->codec->time_base.num) / (videoStream->ost->codec->time_base.den) ); + uint64_t frame_count = 0; + timespec start_time; + clock_gettime(CLOCK_MONOTONIC, &start_time); + uint64_t start_time_ns = (start_time.tv_sec*nanosecond_multiplier) + start_time.tv_nsec; + while(videoStream->do_streaming) { + timespec current_time; + clock_gettime(CLOCK_MONOTONIC, ¤t_time); + uint64_t current_time_ns = (current_time.tv_sec*nanosecond_multiplier) + current_time.tv_nsec; + uint64_t target_ns = start_time_ns + (target_interval_ns * frame_count); + + if ( current_time_ns < target_ns ) { + // It's not time to render a frame yet. + usleep( (target_ns - current_time_ns) * 0.001 ); + } + // By sending the last rendered frame we deliver frames to the client more accurate. // If we're encoding the frame before sending it there will be lag. // Since this lag is not constant the client may skip frames. - + // Get the last rendered packet. AVPacket *packet = videoStream->packet_buffers[videoStream->packet_index]; if (packet->size) { @@ -785,29 +690,26 @@ void *VideoStream::StreamingThreadCallback(void *ctx){ av_free_packet( packet ); #endif videoStream->packet_index = videoStream->packet_index ? 0 : 1; - + // Lock buffer and render next frame. - - if ( pthread_mutex_lock( videoStream->buffer_copy_lock ) != 0 ) - { - Fatal( "StreamingThreadCallback: pthread_mutex_lock failed." ); - } - - if ( videoStream->buffer_copy ) - { - // Encode next frame. - videoStream->ActuallyEncodeFrame( videoStream->buffer_copy, videoStream->buffer_copy_used, videoStream->add_timestamp, videoStream->timestamp ); - } - - if ( pthread_mutex_unlock( videoStream->buffer_copy_lock ) != 0 ) - { - Fatal( "StreamingThreadCallback: pthread_mutex_unlock failed." ); - } - - frame_count++; - } - - return 0; + + if ( pthread_mutex_lock( videoStream->buffer_copy_lock ) != 0 ) { + Fatal( "StreamingThreadCallback: pthread_mutex_lock failed." ); + } + + if ( videoStream->buffer_copy ) { + // Encode next frame. + videoStream->ActuallyEncodeFrame( videoStream->buffer_copy, videoStream->buffer_copy_used, videoStream->add_timestamp, videoStream->timestamp ); + } + + if ( pthread_mutex_unlock( videoStream->buffer_copy_lock ) != 0 ) { + Fatal( "StreamingThreadCallback: pthread_mutex_unlock failed." ); + } + + frame_count++; + } + + return 0; } #endif // HAVE_LIBAVCODEC diff --git a/src/zm_mpeg.h b/src/zm_mpeg.h index e2ec33592..931f9d687 100644 --- a/src/zm_mpeg.h +++ b/src/zm_mpeg.h @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef ZM_MPEG_H diff --git a/src/zm_packet.cpp b/src/zm_packet.cpp new file mode 100644 index 000000000..8fbb65cb8 --- /dev/null +++ b/src/zm_packet.cpp @@ -0,0 +1,44 @@ +//ZoneMinder Packet Implementation Class +//Copyright 2017 ZoneMinder LLC +// +//This file is part of ZoneMinder. +// +//ZoneMinder is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//ZoneMinder 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 General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with ZoneMinder. If not, see . + + +#include "zm_packet.h" +#include "zm_ffmpeg.h" + +using namespace std; + +ZMPacket::ZMPacket( AVPacket *p ) { + av_init_packet( &packet ); + if ( zm_av_packet_ref( &packet, p ) < 0 ) { + Error("error refing packet"); + } + gettimeofday( ×tamp, NULL ); +} + +ZMPacket::ZMPacket( AVPacket *p, struct timeval *t ) { + av_init_packet( &packet ); + if ( zm_av_packet_ref( &packet, p ) < 0 ) { + Error("error refing packet"); + } + timestamp = *t; +} + +ZMPacket::~ZMPacket() { + zm_av_packet_unref( &packet ); +} + diff --git a/src/zm_packet.h b/src/zm_packet.h new file mode 100644 index 000000000..9fd7ed8ee --- /dev/null +++ b/src/zm_packet.h @@ -0,0 +1,39 @@ +//ZoneMinder Packet Wrapper Class +//Copyright 2017 ZoneMinder LLC +// +//This file is part of ZoneMinder. +// +//ZoneMinder is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//ZoneMinder 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 General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with ZoneMinder. If not, see . + + +#ifndef ZM_PACKET_H +#define ZM_PACKET_H + +extern "C" { +#include +} + +class ZMPacket { + public: + + AVPacket packet; + struct timeval timestamp; + public: + AVPacket *av_packet() { return &packet; } + ZMPacket( AVPacket *packet, struct timeval *timestamp ); + ZMPacket( AVPacket *packet ); + ~ZMPacket(); +}; + +#endif /* ZM_PACKET_H */ diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp new file mode 100644 index 000000000..d4520caba --- /dev/null +++ b/src/zm_packetqueue.cpp @@ -0,0 +1,171 @@ +//ZoneMinder Packet Queue Implementation Class +//Copyright 2016 Steve Gilvarry +// +//This file is part of ZoneMinder. +// +//ZoneMinder is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//ZoneMinder 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 General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with ZoneMinder. If not, see . + + +#include "zm_packetqueue.h" +#include "zm_ffmpeg.h" + +#define VIDEO_QUEUESIZE 200 +#define AUDIO_QUEUESIZE 50 + +using namespace std; + +zm_packetqueue::zm_packetqueue(){ + +} + +zm_packetqueue::~zm_packetqueue() { + +} + +bool zm_packetqueue::queuePacket( ZMPacket* zm_packet ) { + pktQueue.push_back( zm_packet ); + + return true; +} +bool zm_packetqueue::queuePacket( AVPacket* av_packet ) { + + ZMPacket *zm_packet = new ZMPacket( av_packet ); + + pktQueue.push_back( zm_packet ); + + return true; +} + +ZMPacket* zm_packetqueue::popPacket( ) { + if ( pktQueue.empty() ) { + return NULL; + } + + ZMPacket *packet = pktQueue.front(); + pktQueue.pop_front(); + + return packet; +} + +unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) { + + Debug(3, "Clearing all but %d frames, queue has %d", frames_to_keep, pktQueue.size() ); + frames_to_keep += 1; + + if ( pktQueue.empty() ) { + Debug(3, "Queue is empty"); + return 0; + } + + list::reverse_iterator it; + ZMPacket *packet = NULL; + + for ( it = pktQueue.rbegin(); it != pktQueue.rend() && frames_to_keep; ++it ) { + ZMPacket *zm_packet = *it; + AVPacket *av_packet = &(zm_packet->packet); + + Debug(4, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); + + // Want frames_to_keep video keyframes. Otherwise, we may not have enough + if ( ( av_packet->stream_index == stream_id) && ( av_packet->flags & AV_PKT_FLAG_KEY ) ) { + frames_to_keep --; + } + } + if ( frames_to_keep ) { + Debug(3, "Hit end of queue, still need (%d) video keyframes", frames_to_keep ); + } + unsigned int delete_count = 0; + while ( it != pktQueue.rend() ) { + Debug(4, "Deleting a packet from the front, count is (%d)", delete_count ); + + packet = pktQueue.front(); + pktQueue.pop_front(); + delete packet; + + delete_count += 1; + } + Debug(3, "Deleted (%d) packets", delete_count ); + return delete_count; +} // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) + +void zm_packetqueue::clearQueue() { + ZMPacket *packet = NULL; + while(!pktQueue.empty()) { + packet = pktQueue.front(); + pktQueue.pop_front(); + delete packet; + } +} + +unsigned int zm_packetqueue::size() { + return pktQueue.size(); +} + + +void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId ) { + // Need to find the keyframe <= recording_started. Can get rid of audio packets. + if ( pktQueue.empty() ) { + return; + } + + // Step 1 - find keyframe < recording_started. + // Step 2 - pop packets until we get to the packet in step 2 + list::reverse_iterator it; + + Debug(3, "Looking for keyframe after start recording stream id (%d)", mVideoStreamId ); + for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { + ZMPacket *zm_packet = *it; + AVPacket *av_packet = &(zm_packet->packet); + if ( + ( av_packet->flags & AV_PKT_FLAG_KEY ) + && + ( av_packet->stream_index == mVideoStreamId ) + && + timercmp( &(zm_packet->timestamp), recording_started, < ) + ) { + Debug(3, "Found keyframe before start with stream index (%d) with keyframe (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ) ); + break; + } + } + if ( it == pktQueue.rend() ) { + Debug(1, "Didn't find a keyframe packet keeping all" ); + return; + } + + ZMPacket *zm_packet = *it; + AVPacket *av_packet = &(zm_packet->packet); + Debug(3, "Found packet before start with stream index (%d) with keyframe (%d), distance(%d), size(%d)", + av_packet->stream_index, + ( av_packet->flags & AV_PKT_FLAG_KEY ), + distance( it, pktQueue.rend() ), + pktQueue.size() ); + + unsigned int deleted_frames = 0; + ZMPacket *packet = NULL; + while ( distance( it, pktQueue.rend() ) > 1 ) { + //while ( pktQueue.rend() != it ) { + packet = pktQueue.front(); + pktQueue.pop_front(); + delete packet; + deleted_frames += 1; + } + + zm_packet = pktQueue.front(); + av_packet = &(zm_packet->packet); + if ( ( ! ( av_packet->flags & AV_PKT_FLAG_KEY ) ) || ( av_packet->stream_index != mVideoStreamId ) ) { + Error( "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); + } else { + Debug(1, "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); + } +} diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h new file mode 100644 index 000000000..39160ddfd --- /dev/null +++ b/src/zm_packetqueue.h @@ -0,0 +1,52 @@ +//ZoneMinder Packet Queue Interface Class +//Copyright 2016 Steve Gilvarry +// +//This file is part of ZoneMinder. +// +//ZoneMinder is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//ZoneMinder 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 General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with ZoneMinder. If not, see . + + +#ifndef ZM_PACKETQUEUE_H +#define ZM_PACKETQUEUE_H + +//#include +//#include +//#include +#include +#include "zm_packet.h" + +extern "C" { +#include +} + +class zm_packetqueue { +public: + zm_packetqueue(); + virtual ~zm_packetqueue(); + bool queuePacket( AVPacket* packet, struct timeval *timestamp ); + bool queuePacket( ZMPacket* packet ); + bool queuePacket( AVPacket* packet ); + ZMPacket * popPacket( ); + bool popVideoPacket(ZMPacket* packet); + bool popAudioPacket(ZMPacket* packet); + unsigned int clearQueue( unsigned int video_frames_to_keep, int stream_id ); + void clearQueue( ); + unsigned int size(); + void clear_unwanted_packets( timeval *recording, int mVideoStreamId ); +private: + std::list pktQueue; + +}; + +#endif /* ZM_PACKETQUEUE_H */ diff --git a/src/zm_poly.cpp b/src/zm_poly.cpp index cddcced46..13a21e7e0 100644 --- a/src/zm_poly.cpp +++ b/src/zm_poly.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" diff --git a/src/zm_poly.h b/src/zm_poly.h index 7ba3043fd..6bbeeea75 100644 --- a/src/zm_poly.h +++ b/src/zm_poly.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_POLY_H diff --git a/src/zm_regexp.cpp b/src/zm_regexp.cpp index cfa686688..d1bca34a5 100644 --- a/src/zm_regexp.cpp +++ b/src/zm_regexp.cpp @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include diff --git a/src/zm_regexp.h b/src/zm_regexp.h index 1b1a9d518..c859823d5 100644 --- a/src/zm_regexp.h +++ b/src/zm_regexp.h @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "zm.h" diff --git a/src/zm_remote_camera.cpp b/src/zm_remote_camera.cpp index b82589cd6..e2a77a7f9 100644 --- a/src/zm_remote_camera.cpp +++ b/src/zm_remote_camera.cpp @@ -14,52 +14,64 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm_remote_camera.h" #include "zm_utils.h" -RemoteCamera::RemoteCamera( int p_id, const std::string &p_protocol, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - Camera( p_id, REMOTE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), - protocol( p_protocol ), - host( p_host ), - port( p_port ), - path( p_path ), - hp( 0 ) +RemoteCamera::RemoteCamera( + unsigned int p_monitor_id, + const std::string &p_protocol, + const std::string &p_host, + const std::string &p_port, + const std::string &p_path, + int p_width, + int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ) : + Camera( p_monitor_id, REMOTE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), + protocol( p_protocol ), + host( p_host ), + port( p_port ), + path( p_path ), + hp( 0 ) { - if ( path[0] != '/' ) - path = '/'+path; + if ( path[0] != '/' ) + path = '/'+path; } -RemoteCamera::~RemoteCamera() -{ - if(hp != NULL) { +RemoteCamera::~RemoteCamera() { + if ( hp != NULL ) { freeaddrinfo(hp); hp = NULL; } } -void RemoteCamera::Initialise() -{ +void RemoteCamera::Initialise() { if( protocol.empty() ) Fatal( "No protocol specified for remote camera" ); - if( host.empty() ) - Fatal( "No host specified for remote camera" ); + if( host.empty() ) + Fatal( "No host specified for remote camera" ); - if( port.empty() ) - Fatal( "No port specified for remote camera" ); + if( port.empty() ) + Fatal( "No port specified for remote camera" ); - //if( path.empty() ) - //Fatal( "No path specified for remote camera" ); + //if( path.empty() ) + //Fatal( "No path specified for remote camera" ); - // Cache as much as we can to speed things up + // Cache as much as we can to speed things up std::string::size_type authIndex = host.rfind( '@' ); - if ( authIndex != std::string::npos ) - { + if ( authIndex != std::string::npos ) { auth = host.substr( 0, authIndex ); host.erase( 0, authIndex+1 ); auth64 = base64Encode( auth ); @@ -67,21 +79,18 @@ void RemoteCamera::Initialise() authIndex = auth.rfind( ':' ); username = auth.substr(0,authIndex); password = auth.substr( authIndex+1, auth.length() ); - } - mNeedAuth = false; - mAuthenticator = new zm::Authenticator(username,password); + mNeedAuth = false; + mAuthenticator = new zm::Authenticator(username,password); - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp); - if ( ret != 0 ) - { + if ( ret != 0 ) { Fatal( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) ); } } - diff --git a/src/zm_remote_camera.h b/src/zm_remote_camera.h index 647782fd1..b081baeb0 100644 --- a/src/zm_remote_camera.h +++ b/src/zm_remote_camera.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_REMOTE_CAMERA_H @@ -27,6 +27,7 @@ #include #include #include +#include // // Class representing 'remote' cameras, i.e. those which are @@ -55,7 +56,22 @@ protected: struct addrinfo *hp; public: - RemoteCamera( int p_id, const std::string &p_proto, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + RemoteCamera( + unsigned int p_monitor_id, + const std::string &p_proto, + const std::string &p_host, + const std::string &p_port, + const std::string &p_path, + int p_width, + int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ); virtual ~RemoteCamera(); const std::string &Protocol() const { return( protocol ); } @@ -73,6 +89,7 @@ public: virtual int PreCapture() = 0; virtual int Capture( Image &image ) = 0; virtual int PostCapture() = 0; + virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory )=0; }; #endif // ZM_REMOTE_CAMERA_H diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index 9454b8d69..95c8ae507 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm_remote_camera_http.h" @@ -30,9 +30,39 @@ #ifdef SOLARIS #include // FIONREAD and friends #endif +#ifdef __FreeBSD__ +#include +#endif -RemoteCameraHttp::RemoteCameraHttp( int p_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - RemoteCamera( p_id, "http", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture ) +RemoteCameraHttp::RemoteCameraHttp( + unsigned int p_monitor_id, + const std::string &p_method, + const std::string &p_host, + const std::string &p_port, + const std::string &p_path, + int p_width, int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio ) : + RemoteCamera( + p_monitor_id, + "http", + p_host, + p_port, + p_path, + p_width, + p_height, + p_colours, + p_brightness, + p_contrast, + p_hue, + p_colour, + p_capture, + p_record_audio ) { sd = -1; @@ -44,7 +74,7 @@ RemoteCameraHttp::RemoteCameraHttp( int p_id, const std::string &p_method, const else if ( p_method == "regexp" ) method = REGEXP; else - Fatal( "Unrecognised method '%s' when creating HTTP camera %d", p_method.c_str(), id ); + Fatal( "Unrecognised method '%s' when creating HTTP camera %d", p_method.c_str(), monitor_id ); if ( capture ) { Initialise(); @@ -108,12 +138,17 @@ int RemoteCameraHttp::Connect() { close(sd); sd = -1; - Warning("Can't connect to remote camera: %s", strerror(errno) ); + char buf[sizeof(struct in6_addr)]; + struct sockaddr_in *addr; + addr = (struct sockaddr_in *)p->ai_addr; + inet_ntop( AF_INET, &(addr->sin_addr), buf, INET6_ADDRSTRLEN ); + + Warning("Can't connect to remote camera mid: %d at %s: %s", monitor_id, buf, strerror(errno) ); continue; } - /* If we got here, we must have connected successfully */ - break; + /* If we got here, we must have connected successfully */ + break; } if(p == NULL) { @@ -154,8 +189,7 @@ int RemoteCameraHttp::SendRequest() * > 0 is the # of bytes read. */ -int RemoteCameraHttp::ReadData( Buffer &buffer, int bytes_expected ) -{ +int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { fd_set rfds; FD_ZERO(&rfds); FD_SET(sd, &rfds); @@ -163,35 +197,49 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, int bytes_expected ) struct timeval temp_timeout = timeout; int n_found = select( sd+1, &rfds, NULL, NULL, &temp_timeout ); - if( n_found == 0 ) - { + if( n_found == 0 ) { Debug( 4, "Select timed out timeout was %d secs %d usecs", temp_timeout.tv_sec, temp_timeout.tv_usec ); + int error = 0; + socklen_t len = sizeof (error); + int retval = getsockopt (sd, SOL_SOCKET, SO_ERROR, &error, &len); + if(retval != 0 ) { + Debug( 1, "error getting socket error code %s", strerror(retval) ); + } + if (error != 0) { + return -1; + } // Why are we disconnecting? It's just a timeout, meaning that data wasn't available. //Disconnect(); return( 0 ); - } - else if ( n_found < 0) - { + } else if ( n_found < 0) { Error( "Select error: %s", strerror(errno) ); return( -1 ); } - int total_bytes_to_read = 0; + unsigned int total_bytes_to_read = 0; - if ( bytes_expected ) - { + if ( bytes_expected ) { total_bytes_to_read = bytes_expected; - } - else - { - if ( ioctl( sd, FIONREAD, &total_bytes_to_read ) < 0 ) - { + } else { + if ( ioctl( sd, FIONREAD, &total_bytes_to_read ) < 0 ) { Error( "Can't ioctl(): %s", strerror(errno) ); return( -1 ); } - if ( total_bytes_to_read == 0 ) - { + if ( total_bytes_to_read == 0 ) { + if ( mode == SINGLE_IMAGE ) { + int error = 0; + socklen_t len = sizeof (error); + int retval = getsockopt( sd, SOL_SOCKET, SO_ERROR, &error, &len ); + if(retval != 0 ) { + Debug( 1, "error getting socket error code %s", strerror(retval) ); + } + if (error != 0) { + return -1; + } + // Case where we are grabbing a single jpg, but no content-length was given, so the expectation is that we read until close. + return( 0 ); + } // If socket is closed locally, then select will fail, but if it is closed remotely // then we have an exception on our socket.. but no data. Debug( 3, "Socket closed remotely" ); @@ -206,34 +254,27 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, int bytes_expected ) } else { Debug(3, "Just getting %d", total_bytes_to_read ); } - } + } // end if bytes_expected or not Debug( 3, "Expecting %d bytes", total_bytes_to_read ); int total_bytes_read = 0; - do - { + do { int bytes_read = buffer.read_into( sd, total_bytes_to_read ); - if ( bytes_read < 0) - { + if ( bytes_read < 0 ) { Error( "Read error: %s", strerror(errno) ); return( -1 ); - } - else if ( bytes_read == 0) - { + } else if ( bytes_read == 0 ) { Debug( 2, "Socket closed" ); //Disconnect(); // Disconnect is done outside of ReadData now. return( -1 ); - } - else if ( bytes_read < total_bytes_to_read ) - { + } else if ( (unsigned int)bytes_read < total_bytes_to_read ) { Error( "Incomplete read, expected %d, got %d", total_bytes_to_read, bytes_read ); return( -1 ); } Debug( 3, "Read %d bytes", bytes_read ); total_bytes_read += bytes_read; total_bytes_to_read -= bytes_read; - } - while ( total_bytes_to_read ); + } while ( total_bytes_to_read ); Debug( 4, buffer ); @@ -265,279 +306,282 @@ int RemoteCameraHttp::GetResponse() switch( state ) { case HEADER : - { - static RegExpr *header_expr = 0; - static RegExpr *status_expr = 0; - static RegExpr *connection_expr = 0; - static RegExpr *content_length_expr = 0; - static RegExpr *content_type_expr = 0; + { + static RegExpr *header_expr = 0; + static RegExpr *status_expr = 0; + static RegExpr *connection_expr = 0; + static RegExpr *content_length_expr = 0; + static RegExpr *content_type_expr = 0; - while ( ! ( buffer_len = ReadData( buffer ) ) ) { - } + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(4, "Timeout waiting for REGEXP HEADER"); + } if ( buffer_len < 0 ) { Error( "Unable to read header data" ); return( -1 ); } - if ( !header_expr ) - header_expr = new RegExpr( "^(.+?\r?\n\r?\n)", PCRE_DOTALL ); - if ( header_expr->Match( (char*)buffer, buffer.size() ) == 2 ) - { - header = header_expr->MatchString( 1 ); - header_len = header_expr->MatchLength( 1 ); - Debug( 4, "Captured header (%d bytes):\n'%s'", header_len, header ); - - if ( !status_expr ) - status_expr = new RegExpr( "^HTTP/(1\\.[01]) +([0-9]+) +(.+?)\r?\n", PCRE_CASELESS ); - if ( status_expr->Match( header, header_len ) < 4 ) + if ( !header_expr ) + header_expr = new RegExpr( "^(.+?\r?\n\r?\n)", PCRE_DOTALL ); + if ( header_expr->Match( (char*)buffer, buffer.size() ) == 2 ) { - Error( "Unable to extract HTTP status from header" ); - return( -1 ); - } - http_version = status_expr->MatchString( 1 ); - status_code = atoi( status_expr->MatchString( 2 ) ); - status_mesg = status_expr->MatchString( 3 ); + header = header_expr->MatchString( 1 ); + header_len = header_expr->MatchLength( 1 ); + Debug( 4, "Captured header (%d bytes):\n'%s'", header_len, header ); - if ( status_code == 401 ) { - if ( mNeedAuth ) { - Error( "Failed authentication: " ); + if ( !status_expr ) + status_expr = new RegExpr( "^HTTP/(1\\.[01]) +([0-9]+) +(.+?)\r?\n", PCRE_CASELESS ); + if ( status_expr->Match( header, header_len ) < 4 ) + { + Error( "Unable to extract HTTP status from header" ); return( -1 ); } - mNeedAuth = true; - std::string Header = header; - - mAuthenticator->checkAuthResponse(Header); - if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { - Debug( 2, "Need Digest Authentication" ); - request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); - request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); - request += stringtf( "Host: %s\r\n", host.c_str()); - if ( strcmp( config.http_version, "1.0" ) == 0 ) - request += stringtf( "Connection: Keep-Alive\r\n" ); - request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); - request += "\r\n"; - - Debug( 2, "New request header: %s", request.c_str() ); - return( 0 ); - } + http_version = status_expr->MatchString( 1 ); + status_code = atoi( status_expr->MatchString( 2 ) ); + status_mesg = status_expr->MatchString( 3 ); - } else if ( status_code < 200 || status_code > 299 ) { - Error( "Invalid response status %d: %s\n%s", status_code, status_mesg, (char *)buffer ); - return( -1 ); - } - Debug( 3, "Got status '%d' (%s), http version %s", status_code, status_mesg, http_version ); + if ( status_code == 401 ) { + if ( mNeedAuth ) { + Error( "Failed authentication: " ); + return( -1 ); + } + mNeedAuth = true; + std::string Header = header; - if ( !connection_expr ) - connection_expr = new RegExpr( "Connection: ?(.+?)\r?\n", PCRE_CASELESS ); - if ( connection_expr->Match( header, header_len ) == 2 ) - { - connection_type = connection_expr->MatchString( 1 ); - Debug( 3, "Got connection '%s'", connection_type ); - } + mAuthenticator->checkAuthResponse(Header); + if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { + Debug( 2, "Need Digest Authentication" ); + request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); + request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); + request += stringtf( "Host: %s\r\n", host.c_str()); + if ( strcmp( config.http_version, "1.0" ) == 0 ) + request += stringtf( "Connection: Keep-Alive\r\n" ); + request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); + request += "\r\n"; - if ( !content_length_expr ) - content_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); - if ( content_length_expr->Match( header, header_len ) == 2 ) - { - content_length = atoi( content_length_expr->MatchString( 1 ) ); - Debug( 3, "Got content length '%d'", content_length ); - } + Debug( 2, "New request header: %s", request.c_str() ); + return( 0 ); + } - if ( !content_type_expr ) - content_type_expr = new RegExpr( "Content-type: ?(.+?)(?:; ?boundary=(.+?))?\r?\n", PCRE_CASELESS ); - if ( content_type_expr->Match( header, header_len ) >= 2 ) - { - content_type = content_type_expr->MatchString( 1 ); - Debug( 3, "Got content type '%s'\n", content_type ); - if ( content_type_expr->MatchCount() > 2 ) - { - content_boundary = content_type_expr->MatchString( 2 ); - Debug( 3, "Got content boundary '%s'", content_boundary ); - } - } - - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = JPEG; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGB; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGBZ; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) - { - // Image stream, so start processing - if ( !content_boundary[0] ) - { - Error( "No content boundary found in header '%s'", header ); + } else if ( status_code < 200 || status_code > 299 ) { + Error( "Invalid response status %d: %s\n%s", status_code, status_mesg, (char *)buffer ); return( -1 ); } - mode = MULTI_IMAGE; - state = SUBHEADER; - } - //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) - //{ + Debug( 3, "Got status '%d' (%s), http version %s", status_code, status_mesg, http_version ); + + if ( !connection_expr ) + connection_expr = new RegExpr( "Connection: ?(.+?)\r?\n", PCRE_CASELESS ); + if ( connection_expr->Match( header, header_len ) == 2 ) + { + connection_type = connection_expr->MatchString( 1 ); + Debug( 3, "Got connection '%s'", connection_type ); + } + + if ( !content_length_expr ) + content_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); + if ( content_length_expr->Match( header, header_len ) == 2 ) + { + content_length = atoi( content_length_expr->MatchString( 1 ) ); + Debug( 3, "Got content length '%d'", content_length ); + } + + if ( !content_type_expr ) + content_type_expr = new RegExpr( "Content-type: ?(.+?)(?:; ?boundary=\x22?(.+?)\x22?)?\r?\n", PCRE_CASELESS ); + if ( content_type_expr->Match( header, header_len ) >= 2 ) + { + content_type = content_type_expr->MatchString( 1 ); + Debug( 3, "Got content type '%s'\n", content_type ); + if ( content_type_expr->MatchCount() > 2 ) + { + content_boundary = content_type_expr->MatchString( 2 ); + Debug( 3, "Got content boundary '%s'", content_boundary ); + } + } + + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) + { + // Single image + mode = SINGLE_IMAGE; + format = JPEG; + state = CONTENT; + } + else if ( !strcasecmp( content_type, "image/x-rgb" ) ) + { + // Single image + mode = SINGLE_IMAGE; + format = X_RGB; + state = CONTENT; + } + else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) + { + // Single image + mode = SINGLE_IMAGE; + format = X_RGBZ; + state = CONTENT; + } + else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) + { + // Image stream, so start processing + if ( !content_boundary[0] ) + { + Error( "No content boundary found in header '%s'", header ); + return( -1 ); + } + mode = MULTI_IMAGE; + state = SUBHEADER; + } + //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) + //{ //// MPEG stream, coming soon! - //} + //} + else + { + Error( "Unrecognised content type '%s'", content_type ); + return( -1 ); + } + buffer.consume( header_len ); + } else { - Error( "Unrecognised content type '%s'", content_type ); - return( -1 ); + Debug( 3, "Unable to extract header from stream, retrying" ); + //return( -1 ); } - buffer.consume( header_len ); + break; } - else - { - Debug( 3, "Unable to extract header from stream, retrying" ); - //return( -1 ); - } - break; - } case SUBHEADER : - { - static RegExpr *subheader_expr = 0; - static RegExpr *subcontent_length_expr = 0; - static RegExpr *subcontent_type_expr = 0; - - if ( !subheader_expr ) { - char subheader_pattern[256] = ""; - snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary ); - subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL ); - } - if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) - { - subheader = subheader_expr->MatchString( 1 ); - subheader_len = subheader_expr->MatchLength( 1 ); - Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader ); + static RegExpr *subheader_expr = 0; + static RegExpr *subcontent_length_expr = 0; + static RegExpr *subcontent_type_expr = 0; - if ( !subcontent_length_expr ) - subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); - if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) + if ( !subheader_expr ) { - content_length = atoi( subcontent_length_expr->MatchString( 1 ) ); - Debug( 3, "Got subcontent length '%d'", content_length ); + char subheader_pattern[256] = ""; + snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary ); + subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL ); } - - if ( !subcontent_type_expr ) - subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS ); - if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) + if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) { - content_type = subcontent_type_expr->MatchString( 1 ); - Debug( 3, "Got subcontent type '%s'", content_type ); - } + subheader = subheader_expr->MatchString( 1 ); + subheader_len = subheader_expr->MatchLength( 1 ); + Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader ); - buffer.consume( subheader_len ); - state = CONTENT; - } - else - { - Debug( 3, "Unable to extract subheader from stream, retrying" ); - while ( ! ( buffer_len = ReadData( buffer ) ) ) { + if ( !subcontent_length_expr ) + subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); + if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) + { + content_length = atoi( subcontent_length_expr->MatchString( 1 ) ); + Debug( 3, "Got subcontent length '%d'", content_length ); + } + + if ( !subcontent_type_expr ) + subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS ); + if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) + { + content_type = subcontent_type_expr->MatchString( 1 ); + Debug( 3, "Got subcontent type '%s'", content_type ); + } + + buffer.consume( subheader_len ); + state = CONTENT; } + else + { + Debug( 3, "Unable to extract subheader from stream, retrying" ); + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(4, "Timeout waiting to extract subheader"); + } if ( buffer_len < 0 ) { Error( "Unable to extract subheader data" ); return( -1 ); } - } - break; - } - case CONTENT : - { - - // if content_type is something like image/jpeg;size=, this will strip the ;size= - char * semicolon = strchr( (char *)content_type, ';' ); - if ( semicolon ) { - *semicolon = '\0'; - } - - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - format = JPEG; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - format = X_RGB; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - format = X_RGBZ; - } - else - { - Error( "Found unsupported content type '%s'", content_type ); - return( -1 ); - } - - if ( content_length ) - { - while ( (long)buffer.size() < content_length ) - { -Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length ); - if ( ReadData( buffer ) < 0 ) { - Error( "Unable to read content" ); - return( -1 ); - } } - Debug( 3, "Got end of image by length, content-length = %d", content_length ); + break; } - else + case CONTENT : { - while ( !content_length ) + + // if content_type is something like image/jpeg;size=, this will strip the ;size= + char * semicolon = strchr( (char *)content_type, ';' ); + if ( semicolon ) { + *semicolon = '\0'; + } + + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { - while ( ! ( buffer_len = ReadData( buffer ) ) ) { + format = JPEG; + } + else if ( !strcasecmp( content_type, "image/x-rgb" ) ) + { + format = X_RGB; + } + else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) + { + format = X_RGBZ; + } + else + { + Error( "Found unsupported content type '%s'", content_type ); + return( -1 ); + } + + if ( content_length ) + { + while ( (long)buffer.size() < content_length ) + { + Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length ); + if ( ReadData( buffer ) < 0 ) { + Error( "Unable to read content" ); + return( -1 ); + } } + Debug( 3, "Got end of image by length, content-length = %d", content_length ); + } + else + { + while ( !content_length ) + { + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(4, "Timeout waiting for content"); + } if ( buffer_len < 0 ) { Error( "Unable to read content" ); return( -1 ); } - static RegExpr *content_expr = 0; - if ( mode == MULTI_IMAGE ) - { - if ( !content_expr ) + static RegExpr *content_expr = 0; + if ( mode == MULTI_IMAGE ) { - char content_pattern[256] = ""; - snprintf( content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary ); - content_expr = new RegExpr( content_pattern, PCRE_DOTALL ); - } - if ( content_expr->Match( buffer, buffer.size() ) == 2 ) - { - content_length = content_expr->MatchLength( 1 ); - Debug( 3, "Got end of image by pattern, content-length = %d", content_length ); + if ( !content_expr ) + { + char content_pattern[256] = ""; + snprintf( content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary ); + content_expr = new RegExpr( content_pattern, PCRE_DOTALL ); + } + if ( content_expr->Match( buffer, buffer.size() ) == 2 ) + { + content_length = content_expr->MatchLength( 1 ); + Debug( 3, "Got end of image by pattern, content-length = %d", content_length ); + } } } } + if ( mode == SINGLE_IMAGE ) + { + state = HEADER; + Disconnect(); + } + else + { + state = SUBHEADER; + } + Debug( 3, "Returning %d (%d) bytes of captured content", content_length, buffer.size() ); + return( content_length ); } - if ( mode == SINGLE_IMAGE ) - { - state = HEADER; - Disconnect(); - } - else - { - state = SUBHEADER; - } - Debug( 3, "Returning %d (%d) bytes of captured content", content_length, buffer.size() ); - return( content_length ); - } case HEADERCONT : case SUBHEADERCONT : - { - // Ignore - break; - } + { + // Ignore + break; + } } } } @@ -588,7 +632,7 @@ Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_ static char *authenticate_header; static char subcontent_length_header[32]; static char subcontent_type_header[64]; - + static char http_version[16]; static char status_code[16]; static char status_mesg[256]; @@ -598,506 +642,422 @@ Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_ static char content_boundary[64]; static int content_boundary_len; - while ( true ) - { - switch( state ) - { + while ( true ) { + switch( state ) { case HEADER : - { - n_headers = 0; - http_header = 0; - connection_header = 0; - content_length_header = 0; - content_type_header = 0; - authenticate_header = 0; + { + n_headers = 0; + http_header = 0; + connection_header = 0; + content_length_header = 0; + content_type_header = 0; + authenticate_header = 0; - http_version[0] = '\0'; - status_code [0]= '\0'; - status_mesg [0]= '\0'; - connection_type [0]= '\0'; - content_length = 0; - content_type[0] = '\0'; - content_boundary[0] = '\0'; - content_boundary_len = 0; - } - case HEADERCONT : - { - while ( ! ( buffer_len = ReadData( buffer ) ) ) { + http_version[0] = '\0'; + status_code [0]= '\0'; + status_mesg [0]= '\0'; + connection_type [0]= '\0'; + content_length = 0; + content_type[0] = '\0'; + content_boundary[0] = '\0'; + content_boundary_len = 0; } + case HEADERCONT : + { + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(4, "Timeout waiting for HEADERCONT"); + } if ( buffer_len < 0 ) { Error( "Unable to read header" ); return( -1 ); } - char *crlf = 0; - char *header_ptr = (char *)buffer; - int header_len = buffer.size(); - bool all_headers = false; + char *crlf = 0; + char *header_ptr = (char *)buffer; + int header_len = buffer.size(); + bool all_headers = false; - while( true ) - { - int crlf_len = memspn( header_ptr, "\r\n", header_len ); - if ( n_headers ) - { - if ( (crlf_len == 2 && !strncmp( header_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( header_ptr, "\r\n\r\n", crlf_len )) ) - { - *header_ptr = '\0'; - header_ptr += crlf_len; + while( true ) { + int crlf_len = memspn( header_ptr, "\r\n", header_len ); + if ( n_headers ) { + if ( (crlf_len == 2 && !strncmp( header_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( header_ptr, "\r\n\r\n", crlf_len )) ) { + *header_ptr = '\0'; + header_ptr += crlf_len; + header_len -= buffer.consume( header_ptr-(char *)buffer ); + all_headers = true; + break; + } + } + if ( crlf_len ) { + if ( header_len == crlf_len ) { + break; + } else { + *header_ptr = '\0'; + header_ptr += crlf_len; + header_len -= buffer.consume( header_ptr-(char *)buffer ); + } + } + + Debug( 6, "%s", header_ptr ); + if ( (crlf = mempbrk( header_ptr, "\r\n", header_len )) ) { + //headers[n_headers++] = header_ptr; + n_headers++; + + if ( !http_header && (strncasecmp( header_ptr, http_match, http_match_len ) == 0) ) { + http_header = header_ptr+http_match_len; + Debug( 6, "Got http header '%s'", header_ptr ); + } else if ( !connection_header && (strncasecmp( header_ptr, connection_match, connection_match_len) == 0) ) { + connection_header = header_ptr+connection_match_len; + Debug( 6, "Got connection header '%s'", header_ptr ); + } else if ( !content_length_header && (strncasecmp( header_ptr, content_length_match, content_length_match_len) == 0) ) { + content_length_header = header_ptr+content_length_match_len; + Debug( 6, "Got content length header '%s'", header_ptr ); + } else if ( !authenticate_header && (strncasecmp( header_ptr, authenticate_match, authenticate_match_len) == 0) ) { + authenticate_header = header_ptr; + Debug( 6, "Got authenticate header '%s'", header_ptr ); + } else if ( !content_type_header && (strncasecmp( header_ptr, content_type_match, content_type_match_len) == 0) ) { + content_type_header = header_ptr+content_type_match_len; + Debug( 6, "Got content type header '%s'", header_ptr ); + } else { + Debug( 6, "Got ignored header '%s'", header_ptr ); + } + header_ptr = crlf; header_len -= buffer.consume( header_ptr-(char *)buffer ); - all_headers = true; - break; - } - } - if ( crlf_len ) - { - if ( header_len == crlf_len ) - { - break; - } - else - { - *header_ptr = '\0'; - header_ptr += crlf_len; - header_len -= buffer.consume( header_ptr-(char *)buffer ); - } - } - - Debug( 6, "%s", header_ptr ); - if ( (crlf = mempbrk( header_ptr, "\r\n", header_len )) ) - { - //headers[n_headers++] = header_ptr; - n_headers++; - - if ( !http_header && (strncasecmp( header_ptr, http_match, http_match_len ) == 0) ) - { - http_header = header_ptr+http_match_len; - Debug( 6, "Got http header '%s'", header_ptr ); - } - else if ( !connection_header && (strncasecmp( header_ptr, connection_match, connection_match_len) == 0) ) - { - connection_header = header_ptr+connection_match_len; - Debug( 6, "Got connection header '%s'", header_ptr ); - } - else if ( !content_length_header && (strncasecmp( header_ptr, content_length_match, content_length_match_len) == 0) ) - { - content_length_header = header_ptr+content_length_match_len; - Debug( 6, "Got content length header '%s'", header_ptr ); - } - - else if ( !authenticate_header && (strncasecmp( header_ptr, authenticate_match, authenticate_match_len) == 0) ) - { - authenticate_header = header_ptr; - Debug( 6, "Got authenticate header '%s'", header_ptr ); - } - else if ( !content_type_header && (strncasecmp( header_ptr, content_type_match, content_type_match_len) == 0) ) - { - content_type_header = header_ptr+content_type_match_len; - Debug( 6, "Got content type header '%s'", header_ptr ); - } - else - { - Debug( 6, "Got ignored header '%s'", header_ptr ); - } - header_ptr = crlf; - header_len -= buffer.consume( header_ptr-(char *)buffer ); - } - else - { - // No end of line found - break; - } - } - - if ( all_headers ) - { - char *start_ptr, *end_ptr; - - if ( !http_header ) - { - Error( "Unable to extract HTTP status from header" ); - return( -1 ); - } - - start_ptr = http_header; - end_ptr = start_ptr+strspn( start_ptr, "10." ); - - memset( http_version, 0, sizeof(http_version) ); - strncpy( http_version, start_ptr, end_ptr-start_ptr ); - - start_ptr = end_ptr; - start_ptr += strspn( start_ptr, " " ); - end_ptr = start_ptr+strspn( start_ptr, "0123456789" ); - - memset( status_code, 0, sizeof(status_code) ); - strncpy( status_code, start_ptr, end_ptr-start_ptr ); - int status = atoi( status_code ); - - start_ptr = end_ptr; - start_ptr += strspn( start_ptr, " " ); - strcpy( status_mesg, start_ptr ); - - if ( status == 401 ) { - if ( mNeedAuth ) { - Error( "Failed authentication: " ); - return( -1 ); - } - if ( ! authenticate_header ) { - Error( "Failed authentication, but don't have an authentication header: " ); - return( -1 ); - } - mNeedAuth = true; - std::string Header = authenticate_header; - Debug(2, "Checking for digest auth in %s", authenticate_header ); - - mAuthenticator->checkAuthResponse(Header); - if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { - Debug( 2, "Need Digest Authentication" ); - request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); - request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); - request += stringtf( "Host: %s\r\n", host.c_str()); - if ( strcmp( config.http_version, "1.0" ) == 0 ) - request += stringtf( "Connection: Keep-Alive\r\n" ); - request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); - request += "\r\n"; - - Debug( 2, "New request header: %s", request.c_str() ); - return( 0 ); } else { - Debug( 2, "Need some other kind of Authentication" ); + // No end of line found + break; } - } else if ( status < 200 || status > 299 ) - { - Error( "Invalid response status %s: %s", status_code, status_mesg ); - return( -1 ); - } - Debug( 3, "Got status '%d' (%s), http version %s", status, status_mesg, http_version ); + } // end while search for headers - if ( connection_header ) - { - memset( connection_type, 0, sizeof(connection_type) ); - start_ptr = connection_header + strspn( connection_header, " " ); - strcpy( connection_type, start_ptr ); - Debug( 3, "Got connection '%s'", connection_type ); - } - if ( content_length_header ) - { - start_ptr = content_length_header + strspn( content_length_header, " " ); - content_length = atoi( start_ptr ); - Debug( 3, "Got content length '%d'", content_length ); - } - if ( content_type_header ) - { - memset( content_type, 0, sizeof(content_type) ); - start_ptr = content_type_header + strspn( content_type_header, " " ); - if ( (end_ptr = strchr( start_ptr, ';' )) ) - { - strncpy( content_type, start_ptr, end_ptr-start_ptr ); - Debug( 3, "Got content type '%s'", content_type ); + if ( all_headers ) { + char *start_ptr, *end_ptr; - start_ptr = end_ptr + strspn( end_ptr, "; " ); - - if ( strncasecmp( start_ptr, boundary_match, boundary_match_len ) == 0 ) - { - start_ptr += boundary_match_len; - start_ptr += strspn( start_ptr, "-" ); - content_boundary_len = sprintf( content_boundary, "--%s", start_ptr ); - Debug( 3, "Got content boundary '%s'", content_boundary ); - } - else - { - Error( "No content boundary found in header '%s'", content_type_header ); - } - } - else - { - strcpy( content_type, start_ptr ); - Debug( 3, "Got content type '%s'", content_type ); - } - } - - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = JPEG; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGB; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGBZ; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) - { - // Image stream, so start processing - if ( !content_boundary[0] ) - { - Error( "No content boundary found in header '%s'", content_type_header ); + if ( !http_header ) { + Error( "Unable to extract HTTP status from header" ); return( -1 ); } - mode = MULTI_IMAGE; - state = SUBHEADER; - } - //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) - //{ + + start_ptr = http_header; + end_ptr = start_ptr+strspn( start_ptr, "10." ); + + // FIXME WHy are we memsetting every time? Can we not do it once? + memset( http_version, 0, sizeof(http_version) ); + strncpy( http_version, start_ptr, end_ptr-start_ptr ); + + start_ptr = end_ptr; + start_ptr += strspn( start_ptr, " " ); + end_ptr = start_ptr+strspn( start_ptr, "0123456789" ); + + memset( status_code, 0, sizeof(status_code) ); + strncpy( status_code, start_ptr, end_ptr-start_ptr ); + int status = atoi( status_code ); + + start_ptr = end_ptr; + start_ptr += strspn( start_ptr, " " ); + strcpy( status_mesg, start_ptr ); + + if ( status == 401 ) { + if ( mNeedAuth ) { + Error( "Failed authentication: " ); + return( -1 ); + } + if ( ! authenticate_header ) { + Error( "Failed authentication, but don't have an authentication header: " ); + return( -1 ); + } + mNeedAuth = true; + std::string Header = authenticate_header; + Debug(2, "Checking for digest auth in %s", authenticate_header ); + + mAuthenticator->checkAuthResponse(Header); + if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { + Debug( 2, "Need Digest Authentication" ); + request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); + request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); + request += stringtf( "Host: %s\r\n", host.c_str()); + if ( strcmp( config.http_version, "1.0" ) == 0 ) + request += stringtf( "Connection: Keep-Alive\r\n" ); + request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); + request += "\r\n"; + + Debug( 2, "New request header: %s", request.c_str() ); + return( 0 ); + } else { + Debug( 2, "Need some other kind of Authentication" ); + } + } else if ( status < 200 || status > 299 ) { + Error( "Invalid response status %s: %s", status_code, status_mesg ); + return( -1 ); + } + Debug( 3, "Got status '%d' (%s), http version %s", status, status_mesg, http_version ); + + if ( connection_header ) { + memset( connection_type, 0, sizeof(connection_type) ); + start_ptr = connection_header + strspn( connection_header, " " ); + // FIXME Should we not use strncpy? + strcpy( connection_type, start_ptr ); + Debug( 3, "Got connection '%s'", connection_type ); + } + if ( content_length_header ) { + start_ptr = content_length_header + strspn( content_length_header, " " ); + content_length = atoi( start_ptr ); + Debug( 3, "Got content length '%d'", content_length ); + } + if ( content_type_header ) { + memset( content_type, 0, sizeof(content_type) ); + start_ptr = content_type_header + strspn( content_type_header, " " ); + if ( (end_ptr = strchr( start_ptr, ';' )) ) { + strncpy( content_type, start_ptr, end_ptr-start_ptr ); + Debug( 3, "Got content type '%s'", content_type ); + + start_ptr = end_ptr + strspn( end_ptr, "; " ); + + if ( strncasecmp( start_ptr, boundary_match, boundary_match_len ) == 0 ) { + start_ptr += boundary_match_len; + start_ptr += strspn( start_ptr, "-" ); + content_boundary_len = sprintf( content_boundary, "--%s", start_ptr ); + Debug( 3, "Got content boundary '%s'", content_boundary ); + } else { + Error( "No content boundary found in header '%s'", content_type_header ); + } + } else { + strcpy( content_type, start_ptr ); + Debug( 3, "Got content type '%s'", content_type ); + } + } + + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { + // Single image + mode = SINGLE_IMAGE; + format = JPEG; + state = CONTENT; + } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { + // Single image + mode = SINGLE_IMAGE; + format = X_RGB; + state = CONTENT; + } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { + // Single image + mode = SINGLE_IMAGE; + format = X_RGBZ; + state = CONTENT; + } else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) { + // Image stream, so start processing + if ( !content_boundary[0] ) { + Error( "No content boundary found in header '%s'", content_type_header ); + return( -1 ); + } + mode = MULTI_IMAGE; + state = SUBHEADER; + } + //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) + //{ //// MPEG stream, coming soon! - //} - else - { - Error( "Unrecognised content type '%s'", content_type ); - return( -1 ); + //} + else { + Error( "Unrecognised content type '%s'", content_type ); + return( -1 ); + } + } else { + Debug( 3, "Unable to extract entire header from stream, continuing" ); + state = HEADERCONT; + //return( -1 ); } + break; } - else - { - Debug( 3, "Unable to extract entire header from stream, continuing" ); - state = HEADERCONT; - //return( -1 ); - } - break; - } case SUBHEADER : - { - n_subheaders = 0; - boundary_header = 0; - subcontent_length_header[0] = '\0'; - subcontent_type_header[0] = '\0'; - content_length = 0; - content_type[0] = '\0'; - } + { + n_subheaders = 0; + boundary_header = 0; + subcontent_length_header[0] = '\0'; + subcontent_type_header[0] = '\0'; + content_length = 0; + content_type[0] = '\0'; + } case SUBHEADERCONT : - { - char *crlf = 0; - char *subheader_ptr = (char *)buffer; - int subheader_len = buffer.size(); - bool all_headers = false; - - while( true ) { - int crlf_len = memspn( subheader_ptr, "\r\n", subheader_len ); - if ( n_subheaders ) - { - if ( (crlf_len == 2 && !strncmp( subheader_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( subheader_ptr, "\r\n\r\n", crlf_len )) ) - { - *subheader_ptr = '\0'; - subheader_ptr += crlf_len; + char *crlf = 0; + char *subheader_ptr = (char *)buffer; + int subheader_len = buffer.size(); + bool all_headers = false; + + while( true ) { + int crlf_len = memspn( subheader_ptr, "\r\n", subheader_len ); + if ( n_subheaders ) { + if ( (crlf_len == 2 && !strncmp( subheader_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( subheader_ptr, "\r\n\r\n", crlf_len )) ) { + *subheader_ptr = '\0'; + subheader_ptr += crlf_len; + subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); + all_headers = true; + break; + } + } + if ( crlf_len ) { + if ( subheader_len == crlf_len ) { + break; + } else { + *subheader_ptr = '\0'; + subheader_ptr += crlf_len; + subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); + } + } + + Debug( 6, "%d: %s", subheader_len, subheader_ptr ); + + if ( (crlf = mempbrk( subheader_ptr, "\r\n", subheader_len )) ) { + //subheaders[n_subheaders++] = subheader_ptr; + n_subheaders++; + + if ( !boundary_header && (strncasecmp( subheader_ptr, content_boundary, content_boundary_len ) == 0) ) { + boundary_header = subheader_ptr; + Debug( 4, "Got boundary subheader '%s'", subheader_ptr ); + } else if ( !subcontent_length_header[0] && (strncasecmp( subheader_ptr, content_length_match, content_length_match_len) == 0) ) { + strncpy( subcontent_length_header, subheader_ptr+content_length_match_len, sizeof(subcontent_length_header) ); + *(subcontent_length_header+strcspn( subcontent_length_header, "\r\n" )) = '\0'; + Debug( 4, "Got content length subheader '%s'", subcontent_length_header ); + } else if ( !subcontent_type_header[0] && (strncasecmp( subheader_ptr, content_type_match, content_type_match_len) == 0) ) { + strncpy( subcontent_type_header, subheader_ptr+content_type_match_len, sizeof(subcontent_type_header) ); + *(subcontent_type_header+strcspn( subcontent_type_header, "\r\n" )) = '\0'; + Debug( 4, "Got content type subheader '%s'", subcontent_type_header ); + } else { + Debug( 6, "Got ignored subheader '%s' found", subheader_ptr ); + } + subheader_ptr = crlf; subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); - all_headers = true; + } else { + // No line end found break; } } - if ( crlf_len ) - { - if ( subheader_len == crlf_len ) - { - break; - } - else - { - *subheader_ptr = '\0'; - subheader_ptr += crlf_len; - subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); - } - } - Debug( 6, "%d: %s", subheader_len, subheader_ptr ); + if ( all_headers && boundary_header ) { + char *start_ptr/*, *end_ptr*/; - if ( (crlf = mempbrk( subheader_ptr, "\r\n", subheader_len )) ) - { - //subheaders[n_subheaders++] = subheader_ptr; - n_subheaders++; + Debug( 3, "Got boundary '%s'", boundary_header ); - if ( !boundary_header && (strncasecmp( subheader_ptr, content_boundary, content_boundary_len ) == 0) ) - { - boundary_header = subheader_ptr; - Debug( 4, "Got boundary subheader '%s'", subheader_ptr ); + if ( subcontent_length_header[0] ) { + start_ptr = subcontent_length_header + strspn( subcontent_length_header, " " ); + content_length = atoi( start_ptr ); + Debug( 3, "Got subcontent length '%d'", content_length ); } - else if ( !subcontent_length_header[0] && (strncasecmp( subheader_ptr, content_length_match, content_length_match_len) == 0) ) - { - strncpy( subcontent_length_header, subheader_ptr+content_length_match_len, sizeof(subcontent_length_header) ); - *(subcontent_length_header+strcspn( subcontent_length_header, "\r\n" )) = '\0'; - Debug( 4, "Got content length subheader '%s'", subcontent_length_header ); + if ( subcontent_type_header[0] ) { + memset( content_type, 0, sizeof(content_type) ); + start_ptr = subcontent_type_header + strspn( subcontent_type_header, " " ); + strcpy( content_type, start_ptr ); + Debug( 3, "Got subcontent type '%s'", content_type ); } - else if ( !subcontent_type_header[0] && (strncasecmp( subheader_ptr, content_type_match, content_type_match_len) == 0) ) - { - strncpy( subcontent_type_header, subheader_ptr+content_type_match_len, sizeof(subcontent_type_header) ); - *(subcontent_type_header+strcspn( subcontent_type_header, "\r\n" )) = '\0'; - Debug( 4, "Got content type subheader '%s'", subcontent_type_header ); + state = CONTENT; + } else { + Debug( 3, "Unable to extract subheader from stream, retrying" ); + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(1, "Timeout waiting to extra subheader non regexp"); } - else - { - Debug( 6, "Got ignored subheader '%s' found", subheader_ptr ); - } - subheader_ptr = crlf; - subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); - } - else - { - // No line end found - break; - } - } - - if ( all_headers && boundary_header ) - { - char *start_ptr/*, *end_ptr*/; - - Debug( 3, "Got boundary '%s'", boundary_header ); - - if ( subcontent_length_header[0] ) - { - start_ptr = subcontent_length_header + strspn( subcontent_length_header, " " ); - content_length = atoi( start_ptr ); - Debug( 3, "Got subcontent length '%d'", content_length ); - } - if ( subcontent_type_header[0] ) - { - memset( content_type, 0, sizeof(content_type) ); - start_ptr = subcontent_type_header + strspn( subcontent_type_header, " " ); - strcpy( content_type, start_ptr ); - Debug( 3, "Got subcontent type '%s'", content_type ); - } - state = CONTENT; - } - else - { - Debug( 3, "Unable to extract subheader from stream, retrying" ); - while ( ! ( buffer_len = ReadData( buffer ) ) ) { - } if ( buffer_len < 0 ) { Error( "Unable to read subheader" ); return( -1 ); } - state = SUBHEADERCONT; + state = SUBHEADERCONT; + } + break; } - break; - } - case CONTENT : - { + case CONTENT : { - // if content_type is something like image/jpeg;size=, this will strip the ;size= - char * semicolon = strchr( content_type, ';' ); - if ( semicolon ) { - *semicolon = '\0'; - } + // if content_type is something like image/jpeg;size=, this will strip the ;size= + char * semicolon = strchr( content_type, ';' ); + if ( semicolon ) { + *semicolon = '\0'; + } - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - format = JPEG; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - format = X_RGB; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - format = X_RGBZ; - } - else - { - Error( "Found unsupported content type '%s'", content_type ); - return( -1 ); - } - - if ( format == JPEG && buffer.size() >= 2 ) - { - if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) - { - Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { + format = JPEG; + } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { + format = X_RGB; + } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { + format = X_RGBZ; + } else { + Error( "Found unsupported content type '%s'", content_type ); return( -1 ); } - } - if ( content_length ) - { - while ( (long)buffer.size() < content_length ) - { - //int buffer_len = ReadData( buffer, content_length-buffer.size() ); - if ( ReadData( buffer ) < 0 ) { - Error( "Unable to read content" ); + // This is an early test for jpeg content, so we can bail early + if ( format == JPEG && buffer.size() >= 2 ) { + if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) { + Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); return( -1 ); } } - Debug( 3, "Got end of image by length, content-length = %d", content_length ); - } - else - { - int content_pos = 0; - while ( !content_length ) - { - buffer_len = ReadData( buffer ); - if ( buffer_len < 0 ) - { - Error( "Unable to read content" ); + + if ( content_length ) { + while ( (long)buffer.size() < content_length ) { + //int buffer_len = ReadData( buffer, content_length-buffer.size() ); + Debug(4, "getting more data"); + if ( ReadData( buffer ) < 0 ) { + Error( "Unable to read content" ); + return( -1 ); + } + } + Debug( 3, "Got end of image by length, content-length = %d", content_length ); + } else { + // Read until we find the end of image or the stream closes. + while ( !content_length ) { + Debug(4, "!content_length, ReadData"); + buffer_len = ReadData( buffer ); + if ( buffer_len < 0 ) + { + Error( "Unable to read content" ); + return( -1 ); + } + int buffer_size = buffer.size(); + if ( buffer_len ) { + // Got some data + + if ( mode == MULTI_IMAGE ) { + // Look for the boundary marker, determine content length using it's position + if ( char *start_ptr = (char *)memstr( (char *)buffer, "\r\n--", buffer_size ) ) { + content_length = start_ptr - (char *)buffer; + Debug( 2, "Got end of image by pattern (crlf--), content-length = %d", content_length ); + } else { + Debug( 2, "Did not find end of image by patten (crlf--) yet, content-length = %d", content_length ); + } + } // end if MULTI_IMAGE + } else { + content_length = buffer_size; + Debug( 2, "Got end of image by closure, content-length = %d", content_length ); + if ( mode == SINGLE_IMAGE ) { + char *end_ptr = (char *)buffer+buffer_size; + + // strip off any last line feeds + while( *end_ptr == '\r' || *end_ptr == '\n' ) { + content_length--; + end_ptr--; + } + + if ( end_ptr != ((char *)buffer+buffer_size) ) { + Debug( 2, "Trimmed end of image, new content-length = %d", content_length ); + } + } // end if SINGLE_IMAGE + } // end if read some data + } // end while ! content_length + } // end if content_length + + if ( mode == SINGLE_IMAGE ) { + state = HEADER; + Disconnect(); + } else { + state = SUBHEADER; + } + + if ( format == JPEG && buffer.size() >= 2 ) { + if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) { + Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); return( -1 ); } - int buffer_size = buffer.size(); - if ( buffer_len ) - { - if ( mode == MULTI_IMAGE ) - { - while ( char *start_ptr = (char *)memstr( (char *)buffer+content_pos, "\r\n--", buffer_size-content_pos ) ) - { - content_length = start_ptr - (char *)buffer; - Debug( 3, "Got end of image by pattern (crlf--), content-length = %d", content_length ); - break; - } - } - } - else - { - content_length = buffer_size; - Debug( 3, "Got end of image by closure, content-length = %d", content_length ); - if ( mode == SINGLE_IMAGE ) - { - char *end_ptr = (char *)buffer+buffer_size; - - while( *end_ptr == '\r' || *end_ptr == '\n' ) - { - content_length--; - end_ptr--; - } - - if ( end_ptr != ((char *)buffer+buffer_size) ) - { - Debug( 3, "Trimmed end of image, new content-length = %d", content_length ); - } - } - } } - } - if ( mode == SINGLE_IMAGE ) - { - state = HEADER; - Disconnect(); - } - else - { - state = SUBHEADER; - } - if ( format == JPEG && buffer.size() >= 2 ) - { - if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) - { - Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); - return( -1 ); - } - } - - Debug( 3, "Returning %d bytes, buffer size: (%d) bytes of captured content", content_length, buffer.size() ); - return( content_length ); - } - } + Debug( 3, "Returning %d bytes, buffer size: (%d) bytes of captured content", content_length, buffer.size() ); + return( content_length ); + } // end cast CONTENT + } // end switch } } return( 0 ); @@ -1145,43 +1105,43 @@ int RemoteCameraHttp::Capture( Image &image ) switch( format ) { case JPEG : - { - if ( !image.DecodeJpeg( buffer.extract( content_length ), content_length, colours, subpixelorder ) ) { - Error( "Unable to decode jpeg" ); - Disconnect(); - return( -1 ); + if ( !image.DecodeJpeg( buffer.extract( content_length ), content_length, colours, subpixelorder ) ) + { + Error( "Unable to decode jpeg" ); + Disconnect(); + return( -1 ); + } + break; } - break; - } case X_RGB : - { - if ( content_length != (long)image.Size() ) { - Error( "Image length mismatch, expected %d bytes, content length was %d", image.Size(), content_length ); - Disconnect(); - return( -1 ); + if ( content_length != (long)image.Size() ) + { + Error( "Image length mismatch, expected %d bytes, content length was %d", image.Size(), content_length ); + Disconnect(); + return( -1 ); + } + image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); + break; } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - break; - } case X_RGBZ : - { - if ( !image.Unzip( buffer.extract( content_length ), content_length ) ) { - Error( "Unable to unzip RGB image" ); + if ( !image.Unzip( buffer.extract( content_length ), content_length ) ) + { + Error( "Unable to unzip RGB image" ); + Disconnect(); + return( -1 ); + } + image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); + break; + } + default : + { + Error( "Unexpected image format encountered" ); Disconnect(); return( -1 ); } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - break; - } - default : - { - Error( "Unexpected image format encountered" ); - Disconnect(); - return( -1 ); - } } return( 0 ); } diff --git a/src/zm_remote_camera_http.h b/src/zm_remote_camera_http.h index bfadecce5..fe5823397 100644 --- a/src/zm_remote_camera_http.h +++ b/src/zm_remote_camera_http.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_REMOTE_CAMERA_HTTP_H @@ -30,8 +30,7 @@ // Class representing 'http' cameras, i.e. those which are // accessed over a network connection using http // -class RemoteCameraHttp : public RemoteCamera -{ +class RemoteCameraHttp : public RemoteCamera { protected: std::string request; struct timeval timeout; @@ -45,7 +44,7 @@ protected: enum { SIMPLE, REGEXP } method; public: - RemoteCameraHttp( int p_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + RemoteCameraHttp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~RemoteCameraHttp(); void Initialise(); @@ -53,11 +52,12 @@ public: int Connect(); int Disconnect(); int SendRequest(); - int ReadData( Buffer &buffer, int bytes_expected=0 ); + int ReadData( Buffer &buffer, unsigned int bytes_expected=0 ); int GetResponse(); int PreCapture(); int Capture( Image &image ); int PostCapture(); + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; }; #endif // ZM_REMOTE_CAMERA_HTTP_H diff --git a/src/zm_remote_camera_rtsp.cpp b/src/zm_remote_camera_rtsp.cpp index 42df5c9ac..9b0b6b41d 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" @@ -28,8 +28,8 @@ #include #include -RemoteCameraRtsp::RemoteCameraRtsp( int p_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - RemoteCamera( p_id, "rtsp", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture ), +RemoteCameraRtsp::RemoteCameraRtsp( unsigned int p_monitor_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + RemoteCamera( p_monitor_id, "rtsp", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), rtsp_describe( p_rtsp_describe ), rtspThread( 0 ) @@ -43,22 +43,23 @@ RemoteCameraRtsp::RemoteCameraRtsp( int p_id, const std::string &p_method, const else if ( p_method == "rtpRtspHttp" ) method = RtspThread::RTP_RTSP_HTTP; else - Fatal( "Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), id ); + Fatal( "Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), monitor_id ); - if ( capture ) - { + if ( capture ) { Initialise(); } mFormatContext = NULL; mVideoStreamId = -1; + mAudioStreamId = -1; mCodecContext = NULL; mCodec = NULL; mRawFrame = NULL; mFrame = NULL; frameCount = 0; + startTime=0; -#if HAVE_LIBSWSCALE +#if HAVE_LIBSWSCALE mConvertContext = NULL; #endif /* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */ @@ -74,45 +75,36 @@ RemoteCameraRtsp::RemoteCameraRtsp( int p_id, const std::string &p_method, const } else { Panic("Unexpected colours: %d",colours); } - -} +} // end RemoteCameraRtsp::RemoteCameraRtsp(...) -RemoteCameraRtsp::~RemoteCameraRtsp() -{ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) +RemoteCameraRtsp::~RemoteCameraRtsp() { av_frame_free( &mFrame ); av_frame_free( &mRawFrame ); -#else - av_freep( &mFrame ); - av_freep( &mRawFrame ); -#endif #if HAVE_LIBSWSCALE - if ( mConvertContext ) - { + if ( mConvertContext ) { sws_freeContext( mConvertContext ); mConvertContext = NULL; } #endif - if ( mCodecContext ) - { + if ( mCodecContext ) { avcodec_close( mCodecContext ); mCodecContext = NULL; // Freed by avformat_free_context in the destructor of RtspThread class } - if ( capture ) - { + if ( capture ) { Terminate(); } } -void RemoteCameraRtsp::Initialise() -{ +void RemoteCameraRtsp::Initialise() { RemoteCamera::Initialise(); int max_size = width*height*colours; + // This allocates a buffer able to hold a raw fframe, which is a little artbitrary. Might be nice to get some + // decent data on how large a buffer is really needed. I think in ffmpeg there are now some functions to do that. buffer.size( max_size ); if ( logDebugging() ) @@ -125,24 +117,20 @@ void RemoteCameraRtsp::Initialise() Connect(); } -void RemoteCameraRtsp::Terminate() -{ +void RemoteCameraRtsp::Terminate() { Disconnect(); } -int RemoteCameraRtsp::Connect() -{ - rtspThread = new RtspThread( id, method, protocol, host, port, path, auth, rtsp_describe ); +int RemoteCameraRtsp::Connect() { + rtspThread = new RtspThread( monitor_id, method, protocol, host, port, path, auth, rtsp_describe ); rtspThread->start(); return( 0 ); } -int RemoteCameraRtsp::Disconnect() -{ - if ( rtspThread ) - { +int RemoteCameraRtsp::Disconnect() { + if ( rtspThread ) { rtspThread->stop(); rtspThread->join(); delete rtspThread; @@ -151,11 +139,9 @@ int RemoteCameraRtsp::Disconnect() return( 0 ); } -int RemoteCameraRtsp::PrimeCapture() -{ +int RemoteCameraRtsp::PrimeCapture() { Debug( 2, "Waiting for sources" ); - for ( int i = 0; i < 100 && !rtspThread->hasSources(); i++ ) - { + for ( int i = 0; i < 100 && !rtspThread->hasSources(); i++ ) { usleep( 100000 ); } if ( !rtspThread->hasSources() ) @@ -167,19 +153,41 @@ int RemoteCameraRtsp::PrimeCapture() // Find first video stream present mVideoStreamId = -1; + mAudioStreamId = -1; - for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) + // Find the first video stream. + for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) { #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) #else if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) #endif { - mVideoStreamId = i; - break; + if ( mVideoStreamId == -1 ) { + mVideoStreamId = i; + continue; + } else { + Debug(2, "Have another video stream." ); + } } +#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) + if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO ) +#else + if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) +#endif + { + if ( mAudioStreamId == -1 ) { + mAudioStreamId = i; + } else { + Debug(2, "Have another audio stream." ); + } + } + } // end foreach stream + if ( mVideoStreamId == -1 ) Fatal( "Unable to locate video stream" ); + if ( mAudioStreamId == -1 ) + Debug( 3, "Unable to locate audio stream" ); // Get a pointer to the codec context for the video stream mCodecContext = mFormatContext->streams[mVideoStreamId]->codec; @@ -220,7 +228,7 @@ int RemoteCameraRtsp::PrimeCapture() int pSize = avpicture_get_size( imagePixFormat, width, height ); #endif - if( (unsigned int)pSize != imagesize) { + if ( (unsigned int)pSize != imagesize ) { Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize); } /* @@ -241,20 +249,17 @@ int RemoteCameraRtsp::PrimeCapture() return( 0 ); } -int RemoteCameraRtsp::PreCapture() -{ +int RemoteCameraRtsp::PreCapture() { if ( !rtspThread->isRunning() ) return( -1 ); - if ( !rtspThread->hasSources() ) - { + if ( !rtspThread->hasSources() ) { Error( "Cannot precapture, no RTP sources" ); return( -1 ); } return( 0 ); } -int RemoteCameraRtsp::Capture( Image &image ) -{ +int RemoteCameraRtsp::Capture( Image &image ) { AVPacket packet; uint8_t* directbuffer; int frameComplete = false; @@ -266,14 +271,12 @@ int RemoteCameraRtsp::Capture( Image &image ) return (-1); } - while ( true ) - { + while ( true ) { buffer.clear(); if ( !rtspThread->isRunning() ) return (-1); - if ( rtspThread->getFrame( buffer ) ) - { + if ( rtspThread->getFrame( buffer ) ) { Debug( 3, "Read frame %d bytes", buffer.size() ); Debug( 4, "Address %p", buffer.head() ); Hexdump( 4, buffer.head(), 16 ); @@ -281,102 +284,265 @@ int RemoteCameraRtsp::Capture( Image &image ) if ( !buffer.size() ) return( -1 ); - if(mCodecContext->codec_id == AV_CODEC_ID_H264) - { + if(mCodecContext->codec_id == AV_CODEC_ID_H264) { + // SPS and PPS frames should be saved and appended to IDR frames + int nalType = (buffer.head()[3] & 0x1f); + + // SPS The SPS NAL unit contains parameters that apply to a series of consecutive coded video pictures + if(nalType == 7) { + lastSps = buffer; + continue; + } else if(nalType == 8) { + // PPS The PPS NAL unit contains parameters that apply to the decoding of one or more individual pictures inside a coded video sequence + lastPps = buffer; + continue; + } else if(nalType == 5) { + // IDR + buffer += lastSps; + buffer += lastPps; + } + } else { + Debug(3, "Not an h264 packet"); + } + + av_init_packet( &packet ); + + while ( !frameComplete && buffer.size() > 0 ) { + packet.data = buffer.head(); + packet.size = buffer.size(); + + // So I think this is the magic decode step. Result is a raw image? +#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) + int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ); +#else + int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ); +#endif + if ( len < 0 ) { + Error( "Error while decoding frame %d", frameCount ); + Hexdump( Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size() ); + buffer.clear(); + continue; + } + Debug( 2, "Frame: %d - %d/%d", frameCount, len, buffer.size() ); + //if ( buffer.size() < 400 ) + //Hexdump( 0, buffer.head(), buffer.size() ); + + buffer -= len; + } + // At this point, we either have a frame or ran out of buffer. What happens if we run out of buffer? + if ( frameComplete ) { + + Debug( 3, "Got frame %d", frameCount ); + + avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height ); + + #if HAVE_LIBSWSCALE + if(mConvertContext == NULL) { + mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); + + if(mConvertContext == NULL) + Fatal( "Unable to create conversion context"); + } + + if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) + Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); + #else // HAVE_LIBSWSCALE + Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); + #endif // HAVE_LIBSWSCALE + + frameCount++; + + } /* frame complete */ + + zm_av_packet_unref( &packet ); + } /* getFrame() */ + + if(frameComplete) + return (0); + + } // end while true + + // can never get here. + return (0); +} + +//Function to handle capture and store + +int RemoteCameraRtsp::CaptureAndRecord(Image &image, timeval recording, char* event_file ) { + AVPacket packet; + uint8_t* directbuffer; + int frameComplete = false; + + while ( true ) { + +// WHY Are we clearing it? Might be something good in it. + buffer.clear(); + + if ( !rtspThread->isRunning() ) + return (-1); + + //Video recording + if ( recording.tv_sec ) { + // The directory we are recording to is no longer tied to the current event. + // Need to re-init the videostore with the correct directory and start recording again + // Not sure why we are only doing this on keyframe, al + if ( videoStore && (strcmp(oldDirectory, event_file)!=0) ) { + //don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?...if we store our key frame location with the event will that be enough? + Info("Re-starting video storage module"); + if ( videoStore ) { + delete videoStore; + videoStore = NULL; + } + } // end if changed to new event + + if ( ! videoStore ) { + //Instantiate the video storage module + + videoStore = new VideoStore((const char *)event_file, "mp4", + mFormatContext->streams[mVideoStreamId], + mAudioStreamId==-1?NULL:mFormatContext->streams[mAudioStreamId], + startTime, + this->getMonitor() ); + strcpy(oldDirectory, event_file); + } // end if ! videoStore + + } else { + if ( videoStore ) { + Info("Deleting videoStore instance"); + delete videoStore; + videoStore = NULL; + } + } // end if recording or not + + if ( rtspThread->getFrame( buffer ) ) { + Debug( 3, "Read frame %d bytes", buffer.size() ); + Debug( 4, "Address %p", buffer.head() ); + Hexdump( 4, buffer.head(), 16 ); + + if ( !buffer.size() ) + return( -1 ); + + if ( mCodecContext->codec_id == AV_CODEC_ID_H264 ) { // SPS and PPS frames should be saved and appended to IDR frames int nalType = (buffer.head()[3] & 0x1f); // SPS - if(nalType == 7) - { + if(nalType == 7) { lastSps = buffer; continue; - } + } else if(nalType == 8) { // PPS - else if(nalType == 8) - { lastPps = buffer; continue; - } + } else if(nalType == 5) { // IDR - else if(nalType == 5) - { buffer += lastSps; buffer += lastPps; } - } + } // end if H264, what about other codecs? av_init_packet( &packet ); - while ( !frameComplete && buffer.size() > 0 ) - { - packet.data = buffer.head(); - packet.size = buffer.size(); -#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) - int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ); -#else - int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ); -#endif - if ( len < 0 ) - { - Error( "Error while decoding frame %d", frameCount ); - Hexdump( Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size() ); - buffer.clear(); - continue; - } - Debug( 2, "Frame: %d - %d/%d", frameCount, len, buffer.size() ); - //if ( buffer.size() < 400 ) - //Hexdump( 0, buffer.head(), buffer.size() ); - - buffer -= len; + // Keep decoding until a complete frame is had. + while ( !frameComplete && buffer.size() > 0 ) { + packet.data = buffer.head(); + packet.size = buffer.size(); - } - if ( frameComplete ) { - - Debug( 3, "Got frame %d", frameCount ); - -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(mFrame->data, mFrame->linesize, - directbuffer, imagePixFormat, width, height, 1); + // Why are we checking for it being the video stream? Because it might be audio or something else. + // Um... we just initialized packet... we can't be testing for what it is yet.... + if ( packet.stream_index == mVideoStreamId ) { + // So this does the decode +#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) + int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ); #else - avpicture_fill( (AVPicture *)mFrame, directbuffer, - imagePixFormat, width, height); + int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ); #endif + if ( len < 0 ) { + Error( "Error while decoding frame %d", frameCount ); + Hexdump( Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size() ); + buffer.clear(); + continue; + } + Debug( 2, "Frame: %d - %d/%d", frameCount, len, buffer.size() ); + //if ( buffer.size() < 400 ) + //Hexdump( 0, buffer.head(), buffer.size() ); + + buffer -= len; + + if ( frameComplete ) { + + Debug( 3, "Got frame %d", frameCount ); + + /* Request a writeable buffer of the target image */ + directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); + if(directbuffer == NULL) { + Error("Failed requesting writeable buffer for the captured image."); + return (-1); + } + +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + av_image_fill_arrays(mFrame->data, mFrame->linesize, + directbuffer, imagePixFormat, width, height, 1); +#else + avpicture_fill( (AVPicture *)mFrame, directbuffer, + imagePixFormat, width, height); +#endif + + } // endif frameComplete + + if ( videoStore ) { + //Write the packet to our video store + int ret = videoStore->writeVideoFramePacket(&packet);//, &lastKeyframePkt); + if ( ret < 0 ) {//Less than zero and we skipped a frame +// Should not + zm_av_packet_unref( &packet ); + return 0; + } + } // end if videoStore, so we are recording #if HAVE_LIBSWSCALE - if(mConvertContext == NULL) { - mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); + // Why are we re-scaling after writing out the packet? + if ( mConvertContext == NULL ) { + mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); - if(mConvertContext == NULL) - Fatal( "Unable to create conversion context"); - } - - if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) - Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); + if ( mConvertContext == NULL ) + Fatal( "Unable to create conversion context"); + } + + if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) + Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); #else // HAVE_LIBSWSCALE - Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); + Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); #endif // HAVE_LIBSWSCALE - - frameCount++; - } /* frame complete */ + frameCount++; + + } else if ( packet.stream_index == mAudioStreamId ) { + Debug( 4, "Got audio packet" ); + if ( videoStore && record_audio ) { + Debug( 4, "Storing Audio packet" ); + //Write the packet to our video store + int ret = videoStore->writeAudioFramePacket( &packet ); //FIXME no relevance of last key frame + if ( ret < 0 ) { //Less than zero and we skipped a frame + zm_av_packet_unref( &packet ); + return 0; + } + } + } // end if video or audio packet -#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) - av_packet_unref( &packet); -#else - av_free_packet( &packet ); -#endif - } /* getFrame() */ - + zm_av_packet_unref( &packet ); + } // end while ! framecomplete and buffer.size() if(frameComplete) return (0); - - } - return (0) ; -} + } /* getFrame() */ -int RemoteCameraRtsp::PostCapture() -{ +} // end while true + +// can never get here. + return (0) ; +} // int RemoteCameraRtsp::CaptureAndRecord( Image &image, bool recording, char* event_file ) + +int RemoteCameraRtsp::PostCapture() { return( 0 ); } #endif // HAVE_LIBAVFORMAT diff --git a/src/zm_remote_camera_rtsp.h b/src/zm_remote_camera_rtsp.h index 20261f82d..8ed8b713c 100644 --- a/src/zm_remote_camera_rtsp.h +++ b/src/zm_remote_camera_rtsp.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_REMOTE_CAMERA_RTSP_H @@ -26,6 +26,8 @@ #include "zm_utils.h" #include "zm_rtsp.h" #include "zm_ffmpeg.h" +#include "zm_videostore.h" +#include "zm_packetqueue.h" // // Class representing 'rtsp' cameras, i.e. those which are @@ -51,23 +53,28 @@ protected: RtspThread *rtspThread; int frameCount; - + #if HAVE_LIBAVFORMAT - AVFormatContext *mFormatContext; - int mVideoStreamId; - AVCodecContext *mCodecContext; - AVCodec *mCodec; - AVFrame *mRawFrame; - AVFrame *mFrame; - _AVPIXELFORMAT imagePixFormat; + AVFormatContext *mFormatContext; + int mVideoStreamId; + int mAudioStreamId; + AVCodecContext *mCodecContext; + AVCodec *mCodec; + AVFrame *mRawFrame; + AVFrame *mFrame; + _AVPIXELFORMAT imagePixFormat; #endif // HAVE_LIBAVFORMAT + bool wasRecording; + VideoStore *videoStore; + char oldDirectory[4096]; + int64_t startTime; #if HAVE_LIBSWSCALE struct SwsContext *mConvertContext; #endif public: - RemoteCameraRtsp( int p_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + RemoteCameraRtsp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~RemoteCameraRtsp(); void Initialise(); @@ -79,7 +86,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); - + int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); }; #endif // ZM_REMOTE_CAMERA_RTSP_H diff --git a/src/zm_rgb.h b/src/zm_rgb.h index af74d872b..1b6cb7550 100644 --- a/src/zm_rgb.h +++ b/src/zm_rgb.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_RGB_H diff --git a/src/zm_rtp.cpp b/src/zm_rtp.cpp index 2ce21db5a..e02dbc2df 100644 --- a/src/zm_rtp.cpp +++ b/src/zm_rtp.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm_rtp.h" diff --git a/src/zm_rtp.h b/src/zm_rtp.h index 6fbeeabe6..25403f1a5 100644 --- a/src/zm_rtp.h +++ b/src/zm_rtp.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_RTP_H diff --git a/src/zm_rtp_ctrl.cpp b/src/zm_rtp_ctrl.cpp index 012a377fc..3973e5a71 100644 --- a/src/zm_rtp_ctrl.cpp +++ b/src/zm_rtp_ctrl.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" diff --git a/src/zm_rtp_ctrl.h b/src/zm_rtp_ctrl.h index f487d1191..6d8f3024c 100644 --- a/src/zm_rtp_ctrl.h +++ b/src/zm_rtp_ctrl.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_RTP_CTRL_H diff --git a/src/zm_rtp_data.cpp b/src/zm_rtp_data.cpp index 5742c60b1..496c0bcfd 100644 --- a/src/zm_rtp_data.cpp +++ b/src/zm_rtp_data.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" diff --git a/src/zm_rtp_data.h b/src/zm_rtp_data.h index 378c5e2e6..dae449ea5 100644 --- a/src/zm_rtp_data.h +++ b/src/zm_rtp_data.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_RTP_DATA_H diff --git a/src/zm_rtp_source.cpp b/src/zm_rtp_source.cpp index bbdd59a6a..0cd838902 100644 --- a/src/zm_rtp_source.cpp +++ b/src/zm_rtp_source.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm_rtp_source.h" @@ -64,7 +64,7 @@ RtpSource::RtpSource( int id, const std::string &localHost, int localPortBase, c mLastSrTimeReal = tvZero(); mLastSrTimeNtp = tvZero(); mLastSrTimeRtp = 0; - + if(mCodecId != AV_CODEC_ID_H264 && mCodecId != AV_CODEC_ID_MPEG4) Warning( "The device is using a codec that may not be supported. Do not be surprised if things don't work." ); } @@ -120,7 +120,7 @@ bool RtpSource::updateSeq( uint16_t seq ) { if ( uDelta == 1 ) { - Debug( 3, "Packet in sequence, gap %d", uDelta ); + Debug( 4, "Packet in sequence, gap %d", uDelta ); } else { @@ -198,7 +198,7 @@ void RtpSource::updateRtcpData( uint32_t ntpTimeSecs, uint32_t ntpTimeFrac, uint struct timeval ntpTime = tvMake( ntpTimeSecs, suseconds_t((USEC_PER_SEC*(ntpTimeFrac>>16))/(1<<16)) ); Debug( 5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime ); - + if ( mBaseTimeNtp.tv_sec == 0 ) { mBaseTimeReal = tvNow(); @@ -262,6 +262,11 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) int rtpHeaderSize = 12 + rtpHeader->cc * 4; // No need to check for nal type as non fragmented packets already have 001 start sequence appended bool h264FragmentEnd = (mCodecId == AV_CODEC_ID_H264) && (packet[rtpHeaderSize+1] & 0x40); + // M stands for Marker, it is the 8th bit + // The interpretation of the marker is defined by a profile. It is intended + // to allow significant events such as frame boundaries to be marked in the + // packet stream. A profile may define additional marker bits or specify + // that there is no marker bit by changing the number of bits in the payload type field. bool thisM = rtpHeader->m || h264FragmentEnd; if ( updateSeq( ntohs(rtpHeader->seqN) ) ) @@ -271,46 +276,55 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) if ( mFrameGood ) { int extraHeader = 0; - + if( mCodecId == AV_CODEC_ID_H264 ) { int nalType = (packet[rtpHeaderSize] & 0x1f); - + Debug( 3, "Have H264 frame: nal type is %d", nalType ); + switch (nalType) { - case 24: - { - extraHeader = 2; - break; - } - case 25: case 26: case 27: - { - extraHeader = 3; - break; - } - // FU-A and FU-B - case 28: case 29: - { - // Is this NAL the first NAL in fragmentation sequence - if ( packet[rtpHeaderSize+1] & 0x80 ) + case 24: // STAP-A { - // Now we will form new header of frame - mFrame.append( "\x0\x0\x1\x0", 4 ); - // Reconstruct NAL header from FU headers - *(mFrame+3) = (packet[rtpHeaderSize+1] & 0x1f) | - (packet[rtpHeaderSize] & 0xe0); + extraHeader = 2; + break; + } + case 25: // STAP-B + case 26: // MTAP-16 + case 27: // MTAP-24 + { + extraHeader = 3; + break; + } + // FU-A and FU-B + case 28: case 29: + { + // Is this NAL the first NAL in fragmentation sequence + if ( packet[rtpHeaderSize+1] & 0x80 ) + { + // Now we will form new header of frame + mFrame.append( "\x0\x0\x1\x0", 4 ); + // Reconstruct NAL header from FU headers + *(mFrame+3) = (packet[rtpHeaderSize+1] & 0x1f) | + (packet[rtpHeaderSize] & 0xe0); + } + + extraHeader = 2; + break; + } + default: + { + Debug(3, "Unhandled nalType %d", nalType ); } - - extraHeader = 2; - break; - } } - + // Append NAL frame start code if ( !mFrame.size() ) mFrame.append( "\x0\x0\x1", 3 ); } mFrame.append( packet+rtpHeaderSize+extraHeader, packetLen-rtpHeaderSize-extraHeader ); + } else { + Debug( 3, "NOT H264 frame: type is %d", mCodecId ); } Hexdump( 4, mFrame.head(), 16 ); @@ -319,12 +333,15 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) { if ( mFrameGood ) { - Debug( 2, "Got new frame %d, %d bytes", mFrameCount, mFrame.size() ); + Debug( 3, "Got new frame %d, %d bytes", mFrameCount, mFrame.size() ); mFrameProcessed.setValueImmediate( false ); mFrameReady.updateValueSignal( true ); if ( !mFrameProcessed.getValueImmediate() ) { + // What is the point of this for loop? Is it just me, or will it call getUpdatedValue once or twice? Could it not be better written as + // if ( ! mFrameProcessed.getUpdatedValue( 1 ) && mFrameProcessed.getUpdatedValue( 1 ) ) return false; + for ( int count = 0; !mFrameProcessed.getUpdatedValue( 1 ); count++ ) if( count > 1 ) return( false ); @@ -377,7 +394,7 @@ bool RtpSource::getFrame( Buffer &buffer ) buffer = mFrame; mFrameReady.setValueImmediate( false ); mFrameProcessed.updateValueSignal( true ); - Debug( 3, "Copied %d bytes", buffer.size() ); + Debug( 4, "Copied %d bytes", buffer.size() ); return( true ); } diff --git a/src/zm_rtp_source.h b/src/zm_rtp_source.h index b86577e01..b53fb975b 100644 --- a/src/zm_rtp_source.h +++ b/src/zm_rtp_source.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_RTP_SOURCE_H diff --git a/src/zm_rtsp.cpp b/src/zm_rtsp.cpp index 67f347d81..f84b2aa53 100644 --- a/src/zm_rtsp.cpp +++ b/src/zm_rtsp.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" @@ -739,8 +739,8 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali { if ( buffer[0] == '$' ) { - if ( buffer.size() < 4 ) - break; + if ( buffer.size() < 4 ) + break; unsigned char channel = buffer[1]; unsigned short len = ntohs( *((unsigned short *)(buffer+2)) ); diff --git a/src/zm_rtsp.h b/src/zm_rtsp.h index 656db65e3..51a2c99aa 100644 --- a/src/zm_rtsp.h +++ b/src/zm_rtsp.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_RTSP_H diff --git a/src/zm_rtsp_auth.cpp b/src/zm_rtsp_auth.cpp index 4f816b64a..6598bd254 100644 --- a/src/zm_rtsp_auth.cpp +++ b/src/zm_rtsp_auth.cpp @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" diff --git a/src/zm_rtsp_auth.h b/src/zm_rtsp_auth.h index 9249a15e2..152dab85a 100644 --- a/src/zm_rtsp_auth.h +++ b/src/zm_rtsp_auth.h @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_RTSP_AUTH_H diff --git a/src/zm_sdp.cpp b/src/zm_sdp.cpp index fa8a602ee..ffcea791b 100644 --- a/src/zm_sdp.cpp +++ b/src/zm_sdp.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" diff --git a/src/zm_sdp.h b/src/zm_sdp.h index 2d08905c8..48a05b706 100644 --- a/src/zm_sdp.h +++ b/src/zm_sdp.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_SDP_H diff --git a/src/zm_signal.cpp b/src/zm_signal.cpp index bbc40e916..08f3d76a0 100644 --- a/src/zm_signal.cpp +++ b/src/zm_signal.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" diff --git a/src/zm_signal.h b/src/zm_signal.h index 0302b47ee..89a4b408a 100644 --- a/src/zm_signal.h +++ b/src/zm_signal.h @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef ZM_SIGNAL_H diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 8bbfda4e9..525d7b8ef 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include @@ -302,8 +302,7 @@ void StreamBase::openComms() { Error("Unable to open sock lock file %s: %s", sock_path_lock, strerror(errno) ); lock_fd = 0; - } - else if ( flock(lock_fd, LOCK_EX) != 0 ) + } else if ( flock(lock_fd, LOCK_EX) != 0 ) { Error("Unable to lock sock lock file %s: %s", sock_path_lock, strerror(errno) ); close(lock_fd); @@ -318,6 +317,8 @@ void StreamBase::openComms() if ( sd < 0 ) { Fatal( "Can't create socket: %s", strerror(errno) ); + } else { + Debug(3, "Have socket %d", sd ); } length = snprintf( loc_sock_path, sizeof(loc_sock_path), "%s/zms-%06ds.sock", config.path_socks, connkey ); @@ -332,6 +333,7 @@ void StreamBase::openComms() strncpy( loc_addr.sun_path, loc_sock_path, sizeof(loc_addr.sun_path) ); loc_addr.sun_family = AF_UNIX; + Debug(3, "Binding to %s", loc_sock_path ); if ( bind( sd, (struct sockaddr *)&loc_addr, strlen(loc_addr.sun_path)+sizeof(loc_addr.sun_family)+1 ) < 0 ) { Fatal( "Can't bind: %s", strerror(errno) ); @@ -341,6 +343,7 @@ void StreamBase::openComms() strncpy( rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path) ); rem_addr.sun_family = AF_UNIX; } // end if connKey > 0 + Debug(3, "comms open" ); } void StreamBase::closeComms() diff --git a/src/zm_stream.h b/src/zm_stream.h index c7df53d16..98d96389f 100644 --- a/src/zm_stream.h +++ b/src/zm_stream.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_STREAM_H diff --git a/src/zm_thread.cpp b/src/zm_thread.cpp index 7d84037d9..166c40703 100644 --- a/src/zm_thread.cpp +++ b/src/zm_thread.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm_thread.h" diff --git a/src/zm_thread.h b/src/zm_thread.h index 15c13bb17..8c84d9041 100644 --- a/src/zm_thread.h +++ b/src/zm_thread.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_THREAD_H @@ -224,7 +224,7 @@ protected: #endif bool mStarted; bool mRunning; - int status; // Used in various funcions to get around return a local variable + int status; // Used in various functions to get around return a local variable protected: Thread(); diff --git a/src/zm_threaddata.cpp b/src/zm_threaddata.cpp index cda094213..6b25d5714 100644 --- a/src/zm_threaddata.cpp +++ b/src/zm_threaddata.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // template class ThreadData; diff --git a/src/zm_time.cpp b/src/zm_time.cpp index 08b673469..417ee2b2b 100644 --- a/src/zm_time.cpp +++ b/src/zm_time.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm_time.h" diff --git a/src/zm_time.h b/src/zm_time.h index df79e7fff..de9d2a22f 100644 --- a/src/zm_time.h +++ b/src/zm_time.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_TIME_H diff --git a/src/zm_timer.cpp b/src/zm_timer.cpp index a067308b7..e6d6d16da 100644 --- a/src/zm_timer.cpp +++ b/src/zm_timer.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm_timer.h" diff --git a/src/zm_timer.h b/src/zm_timer.h index 2213d3cd0..221aec222 100644 --- a/src/zm_timer.h +++ b/src/zm_timer.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_TIMER_H diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 73f5c7932..08f8ff820 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "zm.h" @@ -171,7 +171,7 @@ User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) Debug( 1, "Attempting to authenticate user from auth string '%s'", auth ); char sql[ZM_SQL_SML_BUFSIZ] = ""; - snprintf( sql, sizeof(sql), "select Username, Password, Enabled, Stream+0, Events+0, Control+0, Monitors+0, System+0, MonitorIds from Users where Enabled = 1" ); + snprintf( sql, sizeof(sql), "SELECT Username, Password, Enabled, Stream+0, Events+0, Control+0, Monitors+0, System+0, MonitorIds FROM Users WHERE Enabled = 1" ); if ( mysql_query( &dbconn, sql ) ) { @@ -232,7 +232,7 @@ User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) { sprintf( &auth_md5[2*j], "%02x", md5sum[j] ); } - Debug( 1, "Checking auth_key '%s' -> auth_md5 '%s'", auth_key, auth_md5 ); + Debug( 1, "Checking auth_key '%s' -> auth_md5 '%s' == '%s'", auth_key, auth_md5, auth ); if ( !strcmp( auth, auth_md5 ) ) { @@ -246,5 +246,6 @@ User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) #else // HAVE_DECL_MD5 Error( "You need to build with gnutls or openssl installed to use hash based authentication" ); #endif // HAVE_DECL_MD5 + Debug(1, "No user found for auth_key %s", auth ); return( 0 ); } diff --git a/src/zm_user.h b/src/zm_user.h index 09f5a2f0a..648ef21db 100644 --- a/src/zm_user.h +++ b/src/zm_user.h @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "zm.h" diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index 67af64a66..f6ce68089 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // //#include "zm_logger.h" @@ -24,8 +24,16 @@ #include #include #include +#if defined(__arm__) +#include +#endif + +#ifdef HAVE_CURL_CURL_H +#include +#endif unsigned int sseversion = 0; +unsigned int neonversion = 0; std::string trimSet(std::string str, std::string trimset) { // Trim Both leading and trailing sets @@ -234,30 +242,59 @@ int pairsplit(const char* string, const char delim, std::string& name, std::stri return 0; } -/* Sets sse_version */ -void ssedetect() { +/* Detect special hardware features, such as SIMD instruction sets */ +void hwcaps_detect() { + neonversion = 0; + sseversion = 0; #if (defined(__i386__) || defined(__x86_64__)) /* x86 or x86-64 processor */ - uint32_t r_edx, r_ecx; - + uint32_t r_edx, r_ecx, r_ebx; + +#ifdef __x86_64__ __asm__ __volatile__( -#if defined(__i386__) - "pushl %%ebx;\n\t" -#endif + "push %%rbx\n\t" + "mov $0x0,%%ecx\n\t" + "mov $0x7,%%eax\n\t" + "cpuid\n\t" + "push %%rbx\n\t" "mov $0x1,%%eax\n\t" "cpuid\n\t" -#if defined(__i386__) - "popl %%ebx;\n\t" -#endif - : "=d" (r_edx), "=c" (r_ecx) + "pop %%rax\n\t" + "pop %%rbx\n\t" + : "=d" (r_edx), "=c" (r_ecx), "=a" (r_ebx) + : : - : "%eax" -#if !defined(__i386__) - , "%ebx" -#endif ); - - if (r_ecx & 0x00000200) { +#else + __asm__ __volatile__( + "push %%ebx\n\t" + "mov $0x0,%%ecx\n\t" + "mov $0x7,%%eax\n\t" + "cpuid\n\t" + "push %%ebx\n\t" + "mov $0x1,%%eax\n\t" + "cpuid\n\t" + "pop %%eax\n\t" + "pop %%ebx\n\t" + : "=d" (r_edx), "=c" (r_ecx), "=a" (r_ebx) + : + : + ); +#endif + + if (r_ebx & 0x00000020) { + sseversion = 52; /* AVX2 */ + Debug(1,"Detected a x86\\x86-64 processor with AVX2"); + } else if (r_ecx & 0x10000000) { + sseversion = 51; /* AVX */ + Debug(1,"Detected a x86\\x86-64 processor with AVX"); + } else if (r_ecx & 0x00100000) { + sseversion = 42; /* SSE4.2 */ + Debug(1,"Detected a x86\\x86-64 processor with SSE4.2"); + } else if (r_ecx & 0x00080000) { + sseversion = 41; /* SSE4.1 */ + Debug(1,"Detected a x86\\x86-64 processor with SSE4.1"); + } else if (r_ecx & 0x00000200) { sseversion = 35; /* SSSE3 */ Debug(1,"Detected a x86\\x86-64 processor with SSSE3"); } else if (r_ecx & 0x00000001) { @@ -272,12 +309,25 @@ void ssedetect() { } else { sseversion = 0; Debug(1,"Detected a x86\\x86-64 processor"); + } +#elif defined(__arm__) + // ARM processor in 32bit mode + // To see if it supports NEON, we need to get that information from the kernel + unsigned long auxval = getauxval(AT_HWCAP); + if (auxval & HWCAP_ARM_NEON) { + Debug(1,"Detected ARM (AArch32) processor with Neon"); + neonversion = 1; + } else { + Debug(1,"Detected ARM (AArch32) processor"); } - +#elif defined(__aarch64__) + // ARM processor in 64bit mode + // Neon is mandatory, no need to check for it + neonversion = 1; + Debug(1,"Detected ARM (AArch64) processor with Neon"); #else - /* Non x86 or x86-64 processor, SSE2 is not available */ - Debug(1,"Detected a non x86\\x86-64 processor"); - sseversion = 0; + // Unknown processor + Debug(1,"Detected unknown processor architecture"); #endif } @@ -345,3 +395,30 @@ void timespec_diff(struct timespec *start, struct timespec *end, struct timespec } } +char *timeval_to_string( struct timeval tv ) { + time_t nowtime; + struct tm *nowtm; + static char tmbuf[64], buf[64]; + + nowtime = tv.tv_sec; + nowtm = localtime(&nowtime); + strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm); + snprintf(buf, sizeof buf, "%s.%06ld", tmbuf, tv.tv_usec); + return buf; +} + +std::string UriDecode( const std::string &encoded ) { +#ifdef HAVE_LIBCURL + CURL *curl = curl_easy_init(); + int outlength; + char *cres = curl_easy_unescape(curl, encoded.c_str(), encoded.length(), &outlength); + std::string res(cres, cres + outlength); + curl_free(cres); + curl_easy_cleanup(curl); + return res; +#else +Warning("ZM Compiled without LIBCURL. UriDecoding not implemented."); + return encoded; +#endif +} + diff --git a/src/zm_utils.h b/src/zm_utils.h index a6cb2e150..961389611 100644 --- a/src/zm_utils.h +++ b/src/zm_utils.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_UTILS_H @@ -54,10 +54,14 @@ inline int min( int a, int b ) return( a<=b?a:b ); } -void ssedetect(); void* sse2_aligned_memcpy(void* dest, const void* src, size_t bytes); void timespec_diff(struct timespec *start, struct timespec *end, struct timespec *diff); +void hwcaps_detect(); extern unsigned int sseversion; +extern unsigned int neonversion; + +char *timeval_to_string( struct timeval tv ); +std::string UriDecode( const std::string &encoded ); #endif // ZM_UTILS_H diff --git a/src/zm_video.cpp b/src/zm_video.cpp new file mode 100644 index 000000000..ec1371ba3 --- /dev/null +++ b/src/zm_video.cpp @@ -0,0 +1,522 @@ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +#include "zm.h" +#include "zm_video.h" +#include "zm_image.h" +#include "zm_utils.h" +#include "zm_rgb.h" +#include + +VideoWriter::VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder) : +container(p_container), codec(p_codec), path(p_path), width(p_width), height(p_height), colours(p_colours), subpixelorder(p_subpixelorder), frame_count(0) { + Debug(7,"Video object created"); + + /* Parameter checking */ + if(path.empty()) { + Error("Invalid file path"); + } + if(!width || !height) { + Error("Invalid width or height"); + } + +} + +VideoWriter::~VideoWriter() { + Debug(7,"Video object destroyed"); + +} + +int VideoWriter::Reset(const char* new_path) { + /* Common variables reset */ + + /* If there is a new path, use it */ + if(new_path != NULL) { + path = new_path; + } + + /* Reset frame counter */ + frame_count = 0; + + return 0; +} + + +#if ZM_HAVE_VIDEOWRITER_X264MP4 +X264MP4Writer::X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector* p_user_params) : VideoWriter("mp4", "h264", p_path, p_width, p_height, p_colours, p_subpixelorder), bOpen(false), bGotH264AVCInfo(false), bFirstFrame(true) { + + /* Initialize ffmpeg if it hasn't been initialized yet */ + FFMPEGInit(); + + /* Initialize swscale */ + zm_pf = GetFFMPEGPixelFormat(colours,subpixelorder); + if(zm_pf == 0) { + Error("Unable to match ffmpeg pixelformat"); + } + codec_pf = AV_PIX_FMT_YUV420P; + + swscaleobj.SetDefaults(zm_pf, codec_pf, width, height); + + /* Calculate the image sizes. We will need this for parameter checking */ + zm_imgsize = colours * width * height; +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + codec_imgsize = av_image_get_buffer_size( codec_pf, width, height, 1 ); +#else + codec_imgsize = avpicture_get_size( codec_pf, width, height); +#endif + if(!codec_imgsize) { + Error("Failed calculating codec pixel format image size"); + } + + /* If supplied with user parameters to the encoder, copy them */ + if(p_user_params != NULL) { + user_params = *p_user_params; + } + + /* Setup x264 parameters */ + if(x264config() < 0) { + Error("Failed setting x264 parameters"); + } + + /* Allocate x264 input picture */ + x264_picture_alloc(&x264picin, X264_CSP_I420, x264params.i_width, x264params.i_height); + +} + +X264MP4Writer::~X264MP4Writer() { + + /* Free x264 input picture */ + x264_picture_clean(&x264picin); + + if(bOpen) + Close(); + +} + +int X264MP4Writer::Open() { + + /* Open the encoder */ + x264enc = x264_encoder_open(&x264params); + if(x264enc == NULL) { + Error("Failed opening x264 encoder"); + return -1; + } + + // Debug(4,"x264 maximum delayed frames: %d",x264_encoder_maximum_delayed_frames(x264enc)); + + x264_nal_t* nals; + int i_nals; + if(!x264_encoder_headers(x264enc,&nals,&i_nals)) { + Error("Failed getting encoder headers"); + return -2; + } + + /* Search SPS NAL for AVC information */ + for(int i=0;i 0; i-- ) { + x264encodeloop(true); + } + + /* Close the encoder */ + x264_encoder_close(x264enc); + + /* Close MP4 handle */ + MP4Close(mp4h); + + /* Required for proper HTTP streaming */ + MP4Optimize((path + ".incomplete").c_str(), path.c_str()); + + /* Delete the temporary file */ + unlink((path + ".incomplete").c_str()); + + bOpen = false; + + Debug(7, "Video closed. Total frames: %d", frame_count); + + return 0; +} + +int X264MP4Writer::Reset(const char* new_path) { + + /* Close the encoder and file */ + if(bOpen) + Close(); + + /* Reset common variables */ + VideoWriter::Reset(new_path); + + /* Reset local variables */ + bFirstFrame = true; + bGotH264AVCInfo = false; + prevnals.clear(); + prevpayload.clear(); + + /* Reset x264 parameters */ + x264config(); + + /* Open the encoder */ + Open(); + + return 0; +} + +int X264MP4Writer::Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) { + + /* Parameter checking */ + if(data == NULL) { + Error("NULL buffer"); + return -1; + } + + if(data_size != zm_imgsize) { + Error("The data buffer size does not match the expected size. Expected: %d Current: %d", zm_imgsize, data_size); + return -2; + } + + if(!bOpen) { + Warning("The encoder was not initialized, initializing now"); + Open(); + } + + /* Convert the image into the x264 input picture */ + if(swscaleobj.ConvertDefaults(data, data_size, x264picin.img.plane[0], codec_imgsize) < 0) { + Error("Image conversion failed"); + return -3; + } + + /* Set PTS */ + x264picin.i_pts = frame_time; + + /* Do the encoding */ + x264encodeloop(); + + /* Increment frame counter */ + frame_count++; + + return 0; +} + +int X264MP4Writer::Encode(const Image* img, const unsigned int frame_time) { + + if(img->Width() != width) { + Error("Source image width differs. Source: %d Output: %d",img->Width(), width); + return -12; + } + + if(img->Height() != height) { + Error("Source image height differs. Source: %d Output: %d",img->Height(), height); + return -13; + } + + return Encode(img->Buffer(),img->Size(),frame_time); +} + +int X264MP4Writer::x264config() { + /* Sets up the encoder configuration */ + + int x264ret; + + /* Defaults */ + const char* preset = "veryfast"; + const char* tune = "stillimage"; + const char* profile = "main"; + + /* Search the user parameters for preset, tune and profile */ + for(unsigned int i=0; i < user_params.size(); i++) { + if(strcmp(user_params[i].pname, "preset") == 0) { + /* Got preset */ + preset = user_params[i].pvalue; + } else if(strcmp(user_params[i].pname, "tune") == 0) { + /* Got tune */ + tune = user_params[i].pvalue; + } else if(strcmp(user_params[i].pname, "profile") == 0) { + /* Got profile */ + profile = user_params[i].pvalue; + } + } + + /* Set the defaults and preset and tune */ + x264ret = x264_param_default_preset(&x264params, preset, tune); + if(x264ret != 0) { + Error("Failed setting x264 preset %s and tune %s : %d",preset,tune,x264ret); + } + + /* Set the profile */ + x264ret = x264_param_apply_profile(&x264params, profile); + if(x264ret != 0) { + Error("Failed setting x264 profile %s : %d",profile,x264ret); + } + + /* Input format */ + x264params.i_width = width; + x264params.i_height = height; + x264params.i_csp = X264_CSP_I420; + + /* Quality control */ + x264params.rc.i_rc_method = X264_RC_CRF; + x264params.rc.f_rf_constant = 23.0; + + /* Enable b-frames */ + x264params.i_bframe = 16; + x264params.i_bframe_adaptive = 1; + + /* Timebase */ + x264params.i_timebase_num = 1; + x264params.i_timebase_den = 1000; + + /* Enable variable frame rate */ + x264params.b_vfr_input = 1; + + /* Disable annex-b (start codes) */ + x264params.b_annexb = 0; + + /* TODO: Setup error handler */ + //x264params.i_log_level = X264_LOG_DEBUG; + + /* Process user parameters (excluding preset, tune and profile) */ + for(unsigned int i=0; i < user_params.size(); i++) { + /* Skip preset, tune and profile */ + if( (strcmp(user_params[i].pname, "preset") == 0) || (strcmp(user_params[i].pname, "tune") == 0) || (strcmp(user_params[i].pname, "profile") == 0) ) { + continue; + } + + /* Pass the name and value to x264 */ + x264ret = x264_param_parse(&x264params, user_params[i].pname, user_params[i].pvalue); + + /* Error checking */ + if(x264ret != 0) { + if(x264ret == X264_PARAM_BAD_NAME) { + Error("Failed processing x264 user parameter %s=%s : Bad name", user_params[i].pname, user_params[i].pvalue); + } else if(x264ret == X264_PARAM_BAD_VALUE) { + Error("Failed processing x264 user parameter %s=%s : Bad value", user_params[i].pname, user_params[i].pvalue); + } else { + Error("Failed processing x264 user parameter %s=%s : Unknown error (%d)", user_params[i].pname, user_params[i].pvalue, x264ret); + } + } + } + + return 0; +} + +void X264MP4Writer::x264encodeloop(bool bFlush) { + + x264_nal_t* nals; + int i_nals; + int frame_size; + + if(bFlush) { + frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, NULL, &x264picout); + } else { + frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, &x264picin, &x264picout); + } + + if (frame_size > 0 || bFlush) { + Debug(8, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",frame_count, x264picout.i_pts, x264picout.i_dts, frame_size); + + /* Handle the previous frame */ + if(!bFirstFrame) { + + buffer.clear(); + + /* Process the NALs for the previous frame */ + for(unsigned int i=0; i < prevnals.size(); i++) { + Debug(9,"Processing NAL: Type %d Size %d",prevnals[i].i_type,prevnals[i].i_payload); + + switch(prevnals[i].i_type) { + case NAL_PPS: + /* PPS NAL */ + MP4AddH264PictureParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4); + break; + case NAL_SPS: + /* SPS NAL */ + MP4AddH264SequenceParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4); + break; + default: + /* Anything else, hopefully frames, so copy it into the sample */ + buffer.append(prevnals[i].p_payload, prevnals[i].i_payload); + } + } + + /* Calculate frame duration and offset */ + int duration = x264picout.i_dts - prevDTS; + int offset = prevPTS - prevDTS; + + /* Write the sample */ + if(!buffer.empty()) { + if(!MP4WriteSample(mp4h, mp4vtid, buffer.extract(buffer.size()), buffer.size(), duration, offset, prevKeyframe)) { + Error("Failed writing sample"); + } + } + + /* Cleanup */ + prevnals.clear(); + prevpayload.clear(); + + } + + /* Got a frame. Copy this new frame into the previous frame */ + if(frame_size > 0) { + /* Copy the NALs and the payloads */ + for(int i=0;i* vec) { + if(vec == NULL) { + Error("NULL Encoder parameters vector pointer"); + return -1; + } + + if(str == NULL) { + Error("NULL Encoder parameters string"); + return -2; + } + + vec->clear(); + + if(str[0] == 0) { + /* Empty */ + return 0; + } + + std::string line; + std::stringstream ss(str); + size_t valueoffset; + size_t valuelen; + unsigned int lineno = 0; + EncoderParameter_t param; + + while(std::getline(ss, line) ) { + lineno++; + + /* Remove CR if exists */ + if(line.length() >= 1 && line[line.length()-1] == '\r') { + line.erase(line.length()-1); + } + + /* Skip comments and empty lines */ + if(line.empty() || line[0] == '#') { + continue; + } + + valueoffset = line.find('='); + if(valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0) { + Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno); + continue; + } + + + if(valueoffset > (sizeof(param.pname)-1) ) { + Warning("Failed parsing encoder parameters line %d: Name too long", lineno); + continue; + } + + valuelen = line.length() - (valueoffset+1); + + if( valuelen > (sizeof(param.pvalue)-1) ) { + Warning("Failed parsing encoder parameters line %d: Value too long", lineno); + continue; + } + + /* Copy and NULL terminate */ + line.copy(param.pname, valueoffset, 0); + line.copy(param.pvalue, valuelen, valueoffset+1); + param.pname[valueoffset] = 0; + param.pvalue[valuelen] = 0; + + /* Push to the vector */ + vec->push_back(param); + + Debug(7, "Parsed encoder parameter: %s = %s", param.pname, param.pvalue); + } + + Debug(7, "Parsed %d lines", lineno); + + return 0; +} + + diff --git a/src/zm_video.h b/src/zm_video.h new file mode 100644 index 000000000..936adfeb8 --- /dev/null +++ b/src/zm_video.h @@ -0,0 +1,173 @@ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// + +#ifndef ZM_VIDEO_H +#define ZM_VIDEO_H + +#include "zm.h" +#include "zm_rgb.h" +#include "zm_utils.h" +#include "zm_ffmpeg.h" +#include "zm_buffer.h" + +/* +#define HAVE_LIBX264 1 +#define HAVE_LIBMP4V2 1 +#define HAVE_X264_H 1 +#define HAVE_MP4_H 1 +*/ + +#if HAVE_MP4V2_MP4V2_H +#include +#endif +#if HAVE_MP4V2_H +#include +#endif +#if HAVE_MP4_H +#include +#endif + +#if HAVE_X264_H +#ifdef __cplusplus +extern "C" { +#endif +#include +#ifdef __cplusplus +} +#endif +#endif + +/* Structure for user parameters to the encoder */ +struct EncoderParameter_t { + char pname[48]; + char pvalue[48]; + +}; +int ParseEncoderParameters(const char* str, std::vector* vec); + +/* VideoWriter is a generic interface that ZM uses to save events as videos */ +/* It is relatively simple and the functions are pure virtual, so they must be implemented by the deriving class */ + +class VideoWriter { + +protected: + std::string container; + std::string codec; + std::string path; + unsigned int width; + unsigned int height; + unsigned int colours; + unsigned int subpixelorder; + + unsigned int frame_count; + +public: + VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); + virtual ~VideoWriter(); + virtual int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) = 0; + virtual int Encode(const Image* img, const unsigned int frame_time) = 0; + virtual int Open() = 0; + virtual int Close() = 0; + virtual int Reset(const char* new_path = NULL); + + const char* GetContainer() const { + return container.c_str(); + } + const char* GetCodec() const { + return codec.c_str(); + } + const char* GetPath() const { + return path.c_str(); + } + unsigned int GetWidth() const { + return width; + } + unsigned int GetHeight() const { + return height; + } + unsigned int GetColours() const { + return colours; + } + unsigned int GetSubpixelorder () const { + return subpixelorder; + } + unsigned int GetFrameCount() const { + return frame_count; + } +}; + +#if HAVE_LIBX264 && HAVE_LIBMP4V2 && HAVE_LIBAVUTIL && HAVE_LIBSWSCALE +#define ZM_HAVE_VIDEOWRITER_X264MP4 1 +class X264MP4Writer : public VideoWriter { + +protected: + + bool bOpen; + bool bGotH264AVCInfo; + bool bFirstFrame; + + /* SWScale */ + SWScale swscaleobj; + enum _AVPIXELFORMAT zm_pf; + enum _AVPIXELFORMAT codec_pf; + size_t codec_imgsize; + size_t zm_imgsize; + + /* User parameters */ + std::vector user_params; + + /* AVC Information */ + uint8_t x264_profleindication; + uint8_t x264_profilecompat; + uint8_t x264_levelindication; + + /* NALs */ + Buffer buffer; + + /* Previous frame */ + int prevPTS; + int prevDTS; + bool prevKeyframe; + Buffer prevpayload; + std::vector prevnals; + + /* Internal functions */ + int x264config(); + void x264encodeloop(bool bFlush = false); + + /* x264 objects */ + x264_t* x264enc; + x264_param_t x264params; + x264_picture_t x264picin; + x264_picture_t x264picout; + + /* MP4v2 objects */ + MP4FileHandle mp4h; + MP4TrackId mp4vtid; + + +public: + X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector* p_user_params = NULL); + ~X264MP4Writer(); + int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time); + int Encode(const Image* img, const unsigned int frame_time); + int Open(); + int Close(); + int Reset(const char* new_path = NULL); + +}; +#endif // HAVE_LIBX264 && HAVE_LIBMP4V2 && HAVE_LIBAVUTIL && HAVE_LIBSWSCALE + +#endif // ZM_VIDEO_H diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp new file mode 100644 index 000000000..e7ad821d9 --- /dev/null +++ b/src/zm_videostore.cpp @@ -0,0 +1,916 @@ +// ZoneMinder Video Storage Implementation +// Written by Chris Wiggins +// http://chriswiggins.co.nz +// Modification by Steve Gilvarry +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// + +#define __STDC_FORMAT_MACROS 1 + +#include +#include +#include + +#include "zm.h" +#include "zm_videostore.h" + +extern "C" { + #include "libavutil/time.h" +} + +VideoStore::VideoStore(const char *filename_in, const char *format_in, + AVStream *p_video_input_stream, + AVStream *p_audio_input_stream, + int64_t nStartTime, + Monitor * monitor + ) { + + video_input_stream = p_video_input_stream; + audio_input_stream = p_audio_input_stream; + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + video_input_context = avcodec_alloc_context3( NULL ); + avcodec_parameters_to_context( video_input_context, video_input_stream->codecpar ); +#else + video_input_context = video_input_stream->codec; +#endif + + //store inputs in variables local to class + filename = filename_in; + format = format_in; + + Info("Opening video storage stream %s format: %s\n", filename, format); + + ret = avformat_alloc_output_context2(&oc, NULL, NULL, filename); + if ( ret < 0 ) { + Warning("Could not create video storage stream %s as no output context" + " could be assigned based on filename: %s", + filename, + av_make_error_string(ret).c_str() + ); + } else { + Debug(2, "Success allocating output context"); + } + + //Couldn't deduce format from filename, trying from format name + if ( ! oc ) { + avformat_alloc_output_context2(&oc, NULL, format, filename); + if (!oc) { + Fatal("Could not create video storage stream %s as no output context" + " could not be assigned based on filename or format %s", + filename, format); + } + } else { + Debug(2, "Success alocateing output context"); + } + + AVDictionary *pmetadata = NULL; + int dsr = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0); + if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); + + oc->metadata = pmetadata; + output_format = oc->oformat; + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + // Since we are not re-encoding, all we have to do is copy the parameters + video_output_context = avcodec_alloc_context3( NULL ); + + // Copy params from inputstream to context + ret = avcodec_parameters_to_context( video_output_context, video_input_stream->codecpar ); + if ( ret < 0 ) { + Error( "Could not initialize context parameteres"); + return; + } else { + Debug( 2, "Success getting parameters"); + } + + video_output_stream = avformat_new_stream( oc, NULL ); + if ( ! video_output_stream ) { + Fatal("Unable to create video out stream\n"); + } else { + Debug(2, "Success creating video out stream" ); + } + + // Now copy them to the output stream + ret = avcodec_parameters_from_context( video_output_stream->codecpar, video_output_context ); + if ( ret < 0 ) { + Error( "Could not initialize stream parameteres"); + return; + } else { + Debug(2, "Success setting parameters"); + } + + zm_dump_stream_format( oc, 0, 0, 1 ); +#else + video_output_stream = avformat_new_stream(oc, (AVCodec*)video_input_context->codec ); + if ( ! video_output_stream ) { + Fatal("Unable to create video out stream\n"); + } else { + Debug(2, "Success creating video out stream" ); + } + video_output_context = video_output_stream->codec; + ret = avcodec_copy_context( video_output_context, video_input_context ); + if (ret < 0) { + Fatal("Unable to copy input video context to output video context %s\n", + av_make_error_string(ret).c_str()); + } else { + Debug(3, "Success copying context" ); + } +#endif + + // Just copy them from the input, no reason to choose different + video_output_context->time_base = video_input_context->time_base; + video_output_stream->time_base = video_input_stream->time_base; + + Debug(3, "Time bases: VIDEO input stream (%d/%d) input codec: (%d/%d) output stream: (%d/%d) output codec (%d/%d)", + video_input_stream->time_base.num, + video_input_stream->time_base.den, + video_input_context->time_base.num, + video_input_context->time_base.den, + video_output_stream->time_base.num, + video_output_stream->time_base.den, + video_output_context->time_base.num, + video_output_context->time_base.den + ); + + // WHY? + //video_output_context->codec_tag = 0; + if ( ! video_output_context->codec_tag ) { + Debug(2, "No codec_tag"); + if (! oc->oformat->codec_tag + || av_codec_get_id (oc->oformat->codec_tag, video_input_context->codec_tag) == video_output_context->codec_id + || av_codec_get_tag(oc->oformat->codec_tag, video_input_context->codec_id) <= 0) { + Warning("Setting codec tag"); + video_output_context->codec_tag = video_input_context->codec_tag; + } + } + + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { + video_output_context->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + + Monitor::Orientation orientation = monitor->getOrientation(); + Debug(3, "Have orientation" ); + if ( orientation ) { + if ( orientation == Monitor::ROTATE_0 ) { + + } else if ( orientation == Monitor::ROTATE_90 ) { + dsr = av_dict_set( &video_output_stream->metadata, "rotate", "90", 0); + if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); + } else if ( orientation == Monitor::ROTATE_180 ) { + dsr = av_dict_set( &video_output_stream->metadata, "rotate", "180", 0); + if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); + } else if ( orientation == Monitor::ROTATE_270 ) { + dsr = av_dict_set( &video_output_stream->metadata, "rotate", "270", 0); + if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); + } else { + Warning( "Unsupported Orientation(%d)", orientation ); + } + } + + audio_output_codec = NULL; + audio_input_context = NULL; + audio_output_stream = NULL; +#ifdef HAVE_LIBAVRESAMPLE + resample_context = NULL; +#endif + + if ( audio_input_stream ) { + Debug(3, "Have audio stream" ); +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + + audio_input_context = avcodec_alloc_context3( NULL ); + ret = avcodec_parameters_to_context( audio_input_context, audio_input_stream->codecpar ); +#else + audio_input_context = audio_input_stream->codec; +#endif + + if ( audio_input_context->codec_id != AV_CODEC_ID_AAC ) { + static char error_buffer[256]; + avcodec_string(error_buffer, sizeof(error_buffer), audio_input_context, 0 ); + Debug(2, "Got something other than AAC (%s)", error_buffer ); + + + if ( ! setup_resampler() ) { + return; + } + } else { + Debug(3, "Got AAC" ); + + audio_output_stream = avformat_new_stream(oc, (AVCodec*)audio_input_context->codec); + if ( ! audio_output_stream ) { + Error("Unable to create audio out stream\n"); + audio_output_stream = NULL; + } else { + Debug(2, "setting parameters"); + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + audio_output_context = avcodec_alloc_context3( NULL ); + // Copy params from inputstream to context + ret = avcodec_parameters_to_context( audio_output_context, audio_input_stream->codecpar ); +#else + audio_output_context = audio_output_stream->codec; + ret = avcodec_copy_context(audio_output_context, audio_input_context); +#endif + if (ret < 0) { + Error("Unable to copy audio context %s\n", av_make_error_string(ret).c_str()); + audio_output_stream = NULL; + } else { + audio_output_context->codec_tag = 0; + if ( audio_output_context->channels > 1 ) { + Warning("Audio isn't mono, changing it."); + audio_output_context->channels = 1; + } else { + Debug(3, "Audio is mono"); + } + } + } // end if audio_output_stream + } // end if is AAC + + if ( audio_output_stream ) { + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { + audio_output_context->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + } + + } // end if audio_input_stream + + /* open the output file, if needed */ + if (!(output_format->flags & AVFMT_NOFILE)) { + ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE,NULL,NULL); + if (ret < 0) { + Fatal("Could not open output file '%s': %s\n", filename, + av_make_error_string(ret).c_str()); + } + } + + //os->ctx_inited = 1; + //avio_flush(ctx->pb); + //av_dict_free(&opts); + zm_dump_stream_format( oc, 0, 0, 1 ); + if ( audio_output_stream ) + zm_dump_stream_format( oc, 1, 0, 1 ); + + AVDictionary * opts = NULL; + //av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); + //av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); + //av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov+default_base_moof", 0); + if ((ret = avformat_write_header(oc, NULL)) < 0) { + //if ((ret = avformat_write_header(oc, &opts)) < 0) { + Warning("Unable to set movflags to frag_custom+dash+delay_moov"); + /* Write the stream header, if any. */ + ret = avformat_write_header(oc, NULL); + } else if (av_dict_count(opts) != 0) { + Warning("some options not set\n"); + } + if (ret < 0) { + Error("Error occurred when writing output file header to %s: %s\n", + filename, + av_make_error_string(ret).c_str()); + } + if ( opts ) + av_dict_free(&opts); + + video_last_pts = 0; + video_last_dts = 0; + audio_last_pts = 0; + audio_last_dts = 0; + video_previous_pts = 0; + video_previous_dts = 0; + audio_previous_pts = 0; + audio_previous_dts = 0; + +} // VideoStore::VideoStore + + +VideoStore::~VideoStore(){ + if ( audio_output_codec ) { + // Do we need to flush the outputs? I have no idea. + AVPacket pkt; + int got_packet = 0; + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + int64_t size; + + while(1) { +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + ret = avcodec_receive_packet( audio_output_context, &pkt ); +#else + ret = avcodec_encode_audio2( audio_output_context, &pkt, NULL, &got_packet ); +#endif + if (ret < 0) { + Error("ERror encoding audio while flushing"); + break; + } +Debug(1, "Have audio encoder, need to flush it's output" ); + size += pkt.size; + if (!got_packet) { + break; + } +Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts, pkt.duration ); + if (pkt.pts != AV_NOPTS_VALUE) + pkt.pts = av_rescale_q(pkt.pts, audio_output_context->time_base, audio_output_stream->time_base); + if (pkt.dts != AV_NOPTS_VALUE) + pkt.dts = av_rescale_q(pkt.dts, audio_output_context->time_base, audio_output_stream->time_base); + if (pkt.duration > 0) + pkt.duration = av_rescale_q(pkt.duration, audio_output_context->time_base, audio_output_stream->time_base); +Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts, pkt.duration ); + pkt.stream_index = audio_output_stream->index; + av_interleaved_write_frame( oc, &pkt ); + zm_av_packet_unref( &pkt ); + } // while 1 + } + + // Flush Queues + av_interleaved_write_frame( oc, NULL ); + + /* Write the trailer before close */ + if ( int rc = av_write_trailer(oc) ) { + Error("Error writing trailer %s", av_err2str( rc ) ); + } else { + Debug(3, "Sucess Writing trailer"); + } + + // I wonder if we should be closing the file first. + // I also wonder if we really need to be doing all the context allocation/de-allocation constantly, or whether we can just re-use it. Just do a file open/close/writeheader/etc. + // What if we were only doing audio recording? + if ( video_output_stream ) { + avcodec_close(video_output_context); + } + if (audio_output_stream) { + avcodec_close(audio_output_context); +#ifdef HAVE_LIBAVRESAMPLE + if ( resample_context ) { + avresample_close( resample_context ); + avresample_free( &resample_context ); + } +#endif + } + + // WHen will be not using a file ? + if (!(output_format->flags & AVFMT_NOFILE)) { + /* Close the output file. */ + if ( int rc = avio_close(oc->pb) ) { + Error("Error closing avio %s", av_err2str( rc ) ); + } + } else { + Debug(3, "Not closing avio because we are not writing to a file."); + } + + /* free the stream */ + avformat_free_context(oc); +} + +bool VideoStore::setup_resampler() { +#ifdef HAVE_LIBAVRESAMPLE + static char error_buffer[256]; + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + // Newer ffmpeg wants to keep everything separate... so have to lookup our own decoder, can't reuse the one from the camera. + AVCodec *audio_input_codec = avcodec_find_decoder(audio_input_stream->codecpar->codec_id); +#else + AVCodec *audio_input_codec = avcodec_find_decoder(audio_input_context->codec_id); +#endif + ret = avcodec_open2( audio_input_context, audio_input_codec, NULL ); + if ( ret < 0 ) { + Error("Can't open input codec!"); + return false; + } + + audio_output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); + if ( ! audio_output_codec ) { + Error("Could not find codec for AAC"); + return false; + } + Debug(2, "Have audio output codec"); + + //audio_output_context = audio_output_stream->codec; + audio_output_context = avcodec_alloc_context3( audio_output_codec ); + + if ( ! audio_output_context ) { + Error( "could not allocate codec context for AAC\n"); + audio_output_stream = NULL; + return false; + } + + Debug(2, "Have audio_output_context"); + + /* put sample parameters */ + audio_output_context->bit_rate = audio_input_context->bit_rate; + audio_output_context->sample_rate = audio_input_context->sample_rate; + audio_output_context->channels = audio_input_context->channels; + audio_output_context->channel_layout = audio_input_context->channel_layout; + audio_output_context->sample_fmt = audio_input_context->sample_fmt; + audio_output_context->refcounted_frames = 1; + + if ( audio_output_codec->supported_samplerates ) { + int found = 0; + for ( unsigned int i = 0; audio_output_codec->supported_samplerates[i]; i++) { + if ( audio_output_context->sample_rate == audio_output_codec->supported_samplerates[i] ) { + found = 1; + break; + } + } + if ( found ) { + Debug(3, "Sample rate is good"); + } else { + audio_output_context->sample_rate = audio_output_codec->supported_samplerates[0]; + Debug(1, "Sampel rate is no good, setting to (%d)", audio_output_codec->supported_samplerates[0] ); + } + } + + /* check that the encoder supports s16 pcm input */ + if ( ! check_sample_fmt( audio_output_codec, audio_output_context->sample_fmt ) ) { + Debug( 3, "Encoder does not support sample format %s, setting to FLTP", + av_get_sample_fmt_name( audio_output_context->sample_fmt)); + audio_output_context->sample_fmt = AV_SAMPLE_FMT_FLTP; + } + + audio_output_context->time_base = (AVRational){ 1, audio_output_context->sample_rate }; + + + Debug(1, "Audio output bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) layout(%d) frame_size(%d)", + audio_output_context->bit_rate, + audio_output_context->sample_rate, + audio_output_context->channels, + audio_output_context->sample_fmt, + audio_output_context->channel_layout, + audio_output_context->frame_size + ); + + // Now copy them to the output stream + audio_output_stream = avformat_new_stream( oc, audio_output_codec ); + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + ret = avcodec_parameters_from_context( audio_output_stream->codecpar, audio_output_context ); + if ( ret < 0 ) { + Error( "Could not initialize stream parameteres"); + return false; + } +#endif + + AVDictionary *opts = NULL; + av_dict_set( &opts, "strict", "experimental", 0); + ret = avcodec_open2( audio_output_context, audio_output_codec, &opts ); + av_dict_free(&opts); + if ( ret < 0 ) { + av_strerror(ret, error_buffer, sizeof(error_buffer)); + Fatal( "could not open codec (%d) (%s)\n", ret, error_buffer ); + audio_output_codec = NULL; + audio_output_context = NULL; + audio_output_stream = NULL; + return false; + } + + /** Create a new frame to store the audio samples. */ + if (!(input_frame = zm_av_frame_alloc())) { + Error("Could not allocate input frame"); + return false; + } + + /** Create a new frame to store the audio samples. */ + if (!(output_frame = zm_av_frame_alloc())) { + Error("Could not allocate output frame"); + av_frame_free( &input_frame ); + return false; + } + + // Setup the audio resampler + resample_context = avresample_alloc_context(); + if ( ! resample_context ) { + Error( "Could not allocate resample context\n"); + return false; + } + + // Some formats (i.e. WAV) do not produce the proper channel layout + if ( audio_input_context->channel_layout == 0 ) { + Error( "Bad channel layout. Need to set it to mono.\n"); + av_opt_set_int( resample_context, "in_channel_layout", av_get_channel_layout( "mono" ), 0 ); + } else { + av_opt_set_int( resample_context, "in_channel_layout", audio_input_context->channel_layout, 0 ); + } + + av_opt_set_int( resample_context, "in_sample_fmt", audio_input_context->sample_fmt, 0); + av_opt_set_int( resample_context, "in_sample_rate", audio_input_context->sample_rate, 0); + av_opt_set_int( resample_context, "in_channels", audio_input_context->channels,0); + //av_opt_set_int( resample_context, "out_channel_layout", audio_output_context->channel_layout, 0); + av_opt_set_int( resample_context, "out_channel_layout", av_get_channel_layout( "mono" ), 0 ); + av_opt_set_int( resample_context, "out_sample_fmt", audio_output_context->sample_fmt, 0); + av_opt_set_int( resample_context, "out_sample_rate", audio_output_context->sample_rate, 0); + av_opt_set_int( resample_context, "out_channels", audio_output_context->channels, 0); + + ret = avresample_open( resample_context ); + if ( ret < 0 ) { + Error( "Could not open resample context\n"); + return false; + } + +#if 0 + /** + * Allocate as many pointers as there are audio channels. + * Each pointer will later point to the audio samples of the corresponding + * channels (although it may be NULL for interleaved formats). + */ + if (!( converted_input_samples = (uint8_t *)calloc( audio_output_context->channels, sizeof(*converted_input_samples))) ) { + Error( "Could not allocate converted input sample pointers\n"); + return; + } + /** + * Allocate memory for the samples of all channels in one consecutive + * block for convenience. + */ + if ((ret = av_samples_alloc( &converted_input_samples, NULL, + audio_output_context->channels, + audio_output_context->frame_size, + audio_output_context->sample_fmt, 0)) < 0) { + Error( "Could not allocate converted input samples (error '%s')\n", + av_make_error_string(ret).c_str() ); + + av_freep(converted_input_samples); + free(converted_input_samples); + return; + } +#endif + + output_frame->nb_samples = audio_output_context->frame_size; + output_frame->format = audio_output_context->sample_fmt; + output_frame->channel_layout = audio_output_context->channel_layout; + + // The codec gives us the frame size, in samples, we calculate the size of the samples buffer in bytes + unsigned int audioSampleBuffer_size = av_samples_get_buffer_size( NULL, audio_output_context->channels, audio_output_context->frame_size, audio_output_context->sample_fmt, 0 ); + converted_input_samples = (uint8_t*) av_malloc( audioSampleBuffer_size ); + + if ( !converted_input_samples ) { + Error( "Could not allocate converted input sample pointers\n"); + return false; + } + + // Setup the data pointers in the AVFrame + if ( avcodec_fill_audio_frame( + output_frame, + audio_output_context->channels, + audio_output_context->sample_fmt, + (const uint8_t*) converted_input_samples, + audioSampleBuffer_size, 0 ) < 0 ) { + Error( "Could not allocate converted input sample pointers\n"); + return false; + } + + return true; +#else + Error("Not built with libavresample library. Cannot do audio conversion to AAC"); + return false; +#endif +} + + +void VideoStore::dumpPacket( AVPacket *pkt ){ + char b[10240]; + + snprintf(b, sizeof(b), " pts: %" PRId64 ", dts: %" PRId64 ", data: %p, size: %d, sindex: %d, dflags: %04x, s-pos: %" PRId64 ", c-duration: %" PRId64 "\n" + , pkt->pts + , pkt->dts + , pkt->data + , pkt->size + , pkt->stream_index + , pkt->flags + , pkt->pos + , pkt->duration + ); + Debug(1, "%s:%d:DEBUG: %s", __FILE__, __LINE__, b); +} + +int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) { + av_init_packet(&opkt); + + int duration; + + //Scale the PTS of the outgoing packet to be the correct time base + if (ipkt->pts != AV_NOPTS_VALUE) { + + if ( ! video_last_pts ) { + // This is the first packet. + opkt.pts = 0; + Debug(2, "Starting video video_last_pts will become (%d)", ipkt->pts ); + } else { + if ( ipkt->pts < video_last_pts ) { + Debug(1, "Resetting video_last_pts from (%d) to (%d)", video_last_pts, ipkt->pts ); + // wrap around, need to figure out the distance FIXME having this wrong should cause a jump, but then play ok? + opkt.pts = video_previous_pts + av_rescale_q( ipkt->pts, video_input_stream->time_base, video_output_stream->time_base); + } else { + opkt.pts = video_previous_pts + av_rescale_q( ipkt->pts - video_last_pts, video_input_stream->time_base, video_output_stream->time_base); + } + } + Debug(3, "opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, video_last_pts ); + duration = ipkt->pts - video_last_pts; + video_last_pts = ipkt->pts; + } else { + Debug(3, "opkt.pts = undef"); + opkt.pts = AV_NOPTS_VALUE; + } + + //Scale the DTS of the outgoing packet to be the correct time base + + // Just because the input stream wraps, doesn't mean the output needs to. Really, if we are limiting ourselves to 10min segments I can't imagine every wrapping in the output. So need to handle input wrap, without causing output wrap. + if ( ! video_last_dts ) { + // This is the first packet. + opkt.dts = 0; + Debug(1, "Starting video video_last_dts will become (%d)", ipkt->dts ); + video_last_dts = ipkt->dts; + } else { + if ( ipkt->dts == AV_NOPTS_VALUE ) { + // why are we using cur_dts instead of packet.dts? I think cur_dts is in AV_TIME_BASE_Q, but ipkt.dts is in video_input_stream->time_base + if ( video_input_stream->cur_dts < video_last_dts ) { + Debug(1, "Resetting video_last_dts from (%d) to (%d) p.dts was (%d)", video_last_dts, video_input_stream->cur_dts, ipkt->dts ); + opkt.dts = video_previous_dts + av_rescale_q(video_input_stream->cur_dts, AV_TIME_BASE_Q, video_output_stream->time_base); + } else { + opkt.dts = video_previous_dts + av_rescale_q(video_input_stream->cur_dts - video_last_dts, AV_TIME_BASE_Q, video_output_stream->time_base); + } + Debug(3, "opkt.dts = %d from video_input_stream->cur_dts(%d) - previus_dts(%d)", opkt.dts, video_input_stream->cur_dts, video_last_dts ); + video_last_dts = video_input_stream->cur_dts; + } else { + if ( ipkt->dts < video_last_dts ) { + Debug(1, "Resetting video_last_dts from (%d) to (%d)", video_last_dts, ipkt->dts ); + opkt.dts = video_previous_dts + av_rescale_q( ipkt->dts, video_input_stream->time_base, video_output_stream->time_base); + } else { + opkt.dts = video_previous_dts + av_rescale_q( ipkt->dts - video_last_dts, video_input_stream->time_base, video_output_stream->time_base); + } + Debug(3, "opkt.dts = %d from ipkt.dts(%d) - previus_dts(%d)", opkt.dts, ipkt->dts, video_last_dts ); + video_last_dts = ipkt->dts; + } + } + if ( opkt.dts > opkt.pts ) { + Debug( 1, "opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen before presentation.", opkt.dts, opkt.pts ); + opkt.dts = opkt.pts; + } + + if ( ipkt->duration == AV_NOPTS_VALUE ) { + opkt.duration = av_rescale_q( duration, video_input_stream->time_base, video_output_stream->time_base); + } else { + opkt.duration = av_rescale_q(ipkt->duration, video_input_stream->time_base, video_output_stream->time_base); + } + opkt.flags = ipkt->flags; + opkt.pos=-1; + + opkt.data = ipkt->data; + opkt.size = ipkt->size; + + // Some camera have audio on stream 0 and video on stream 1. So when we remove the audio, video stream has to go on 0 + if ( ipkt->stream_index > 0 and ! audio_output_stream ) { + Debug(1,"Setting stream index to 0 instead of %d", ipkt->stream_index ); + opkt.stream_index = 0; + } else { + opkt.stream_index = ipkt->stream_index; + } + + AVPacket safepkt; + memcpy(&safepkt, &opkt, sizeof(AVPacket)); + +Debug(1, "writing video packet pts(%d) dts(%d) duration(%d)", opkt.pts, opkt.dts, opkt.duration ); + if ((opkt.data == NULL)||(opkt.size < 1)) { + Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ ); + dumpPacket( ipkt); + dumpPacket(&opkt); + + } else if ((video_previous_dts > 0) && (video_previous_dts > opkt.dts)) { + Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame", __FILE__, __LINE__, video_previous_dts, opkt.dts); + video_previous_dts = opkt.dts; + dumpPacket(&opkt); + + } else { + + video_previous_dts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance + video_previous_pts = opkt.pts; + ret = av_interleaved_write_frame(oc, &opkt); + if(ret<0){ + // There's nothing we can really do if the frame is rejected, just drop it and get on with the next + Warning("%s:%d: Writing frame [av_interleaved_write_frame()] failed: %s(%d) ", __FILE__, __LINE__, av_make_error_string(ret).c_str(), (ret)); + dumpPacket(&safepkt); + } + } + + zm_av_packet_unref(&opkt); + + return 0; + +} // end int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) + +int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { + Debug(4, "writeAudioFrame"); + + if ( ! audio_output_stream ) { + Debug(1, "Called writeAudioFramePacket when no audio_output_stream"); + return 0;//FIXME -ve return codes do not free packet in ffmpeg_camera at the moment + } + + if ( audio_output_codec ) { +#ifdef HAVE_LIBAVRESAMPLE + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + ret = avcodec_send_packet( audio_input_context, ipkt ); + if ( ret < 0 ) { + Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str()); + return 0; + } + + ret = avcodec_receive_frame( audio_input_context, input_frame ); + if ( ret < 0 ) { + Error("avcodec_receive_frame fail %s", av_make_error_string(ret).c_str()); + return 0; + } + Debug(2, "Frame: samples(%d), format(%d), sample_rate(%d), channel layout(%d) refd(%d)", + input_frame->nb_samples, + input_frame->format, + input_frame->sample_rate, + input_frame->channel_layout, + audio_output_context->refcounted_frames + ); +#else + /** + * Decode the audio frame stored in the packet. + * The input audio stream decoder is used to do this. + * If we are at the end of the file, pass an empty packet to the decoder + * to flush it. + */ + if ((ret = avcodec_decode_audio4(audio_input_context, input_frame, + &data_present, ipkt)) < 0) { + Error( "Could not decode frame (error '%s')\n", + av_make_error_string(ret).c_str()); + dumpPacket( ipkt ); + av_frame_free( &input_frame ); + return 0; + } + if ( ! data_present ) { + Debug(2, "Not ready to transcode a frame yet."); + return 0; + } +#endif + int frame_size = input_frame->nb_samples; + Debug(4, "Frame size: %d", frame_size ); + + // Resample the input into the audioSampleBuffer until we proceed the whole decoded data + if ( (ret = avresample_convert( resample_context, + NULL, + 0, + 0, + input_frame->data, + 0, + input_frame->nb_samples )) < 0 ) { + Error( "Could not resample frame (error '%s')\n", + av_make_error_string(ret).c_str()); + return 0; + } + + if ( avresample_available( resample_context ) < output_frame->nb_samples ) { + Debug(1, "No enough samples yet"); + return 0; + } + + // Read a frame audio data from the resample fifo + if ( avresample_read( resample_context, output_frame->data, output_frame->nb_samples ) != output_frame->nb_samples ) { + Warning( "Error reading resampled audio: " ); + return 0; + } + + av_init_packet(&opkt); + Debug(5, "after init packet" ); + + /** Set a timestamp based on the sample rate for the container. */ + //output_frame->pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base ); + + // convert the packet to the codec timebase from the stream timebase + //Debug(3, "output_frame->pts(%d) best effort(%d)", output_frame->pts, + //av_frame_get_best_effort_timestamp(output_frame) + //); + /** + * Encode the audio frame and store it in the temporary packet. + * The output audio stream encoder is used to do this. + */ +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + if (( ret = avcodec_send_frame( audio_output_context, output_frame ) ) < 0 ) { + Error( "Could not send frame (error '%s')", + av_make_error_string(ret).c_str()); + zm_av_packet_unref(&opkt); + return 0; + } + + if (( ret = avcodec_receive_packet( audio_output_context, &opkt )) < 0 ) { + Error( "Could not recieve packet (error '%s')", + av_make_error_string(ret).c_str()); + zm_av_packet_unref(&opkt); + return 0; + } +#else + if (( ret = avcodec_encode_audio2( audio_output_context, &opkt, output_frame, &data_present )) < 0) { + Error( "Could not encode frame (error '%s')", + av_make_error_string(ret).c_str()); + zm_av_packet_unref(&opkt); + return 0; + } + if ( ! data_present ) { + Debug(2, "Not ready to output a frame yet."); + zm_av_packet_unref(&opkt); + return 0; + } +#endif + +#endif + } else { + av_init_packet(&opkt); + Debug(5, "after init packet" ); + opkt.data = ipkt->data; + opkt.size = ipkt->size; + } + + // PTS is difficult, because of the buffering of the audio packets in the resampler. So we have to do it once we actually have a packet... + + //Scale the PTS of the outgoing packet to be the correct time base + if ( ipkt->pts != AV_NOPTS_VALUE ) { + if ( ! audio_last_pts ) { + opkt.pts = 0; + Debug(1, "No audio_last_pts"); + } else { + if ( audio_last_pts > ipkt->pts ) { + Debug(1, "Resetting audeo_start_pts from (%d) to (%d)", audio_last_pts, ipkt->pts ); + opkt.pts = audio_previous_pts + av_rescale_q(ipkt->pts, audio_input_stream->time_base, audio_output_stream->time_base); + } else { + opkt.pts = audio_previous_pts + av_rescale_q(ipkt->pts - audio_last_pts, audio_input_stream->time_base, audio_output_stream->time_base); + } + Debug(2, "audio opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, audio_last_pts ); + } + audio_last_pts = ipkt->pts; + } else { + Debug(2, "opkt.pts = undef"); + opkt.pts = AV_NOPTS_VALUE; + } + + //Scale the DTS of the outgoing packet to be the correct time base + if ( ! audio_last_dts ) { + opkt.dts = 0; + + } else { + if( ipkt->dts == AV_NOPTS_VALUE ) { + // So if the input has no dts assigned... still need an output dts... so we use cur_dts? + + if ( audio_last_dts > audio_input_stream->cur_dts ) { + Debug(1, "Resetting audio_last_dts from (%d) to cur_dts (%d)", audio_last_dts, audio_input_stream->cur_dts ); + opkt.dts = audio_previous_dts + av_rescale_q( audio_input_stream->cur_dts, AV_TIME_BASE_Q, audio_output_stream->time_base); + } else { + opkt.dts = audio_previous_dts + av_rescale_q( audio_input_stream->cur_dts - audio_last_dts, AV_TIME_BASE_Q, audio_output_stream->time_base); + } + audio_last_dts = audio_input_stream->cur_dts; + Debug(2, "opkt.dts = %d from video_input_stream->cur_dts(%d) - last_dts(%d)", opkt.dts, audio_input_stream->cur_dts, audio_last_dts ); + } else { + if ( audio_last_dts > ipkt->dts ) { + Debug(1, "Resetting audio_last_dts from (%d) to (%d)", audio_last_dts, ipkt->dts ); + opkt.dts = audio_previous_dts + av_rescale_q(ipkt->dts, audio_input_stream->time_base, audio_output_stream->time_base); + } else { + opkt.dts = audio_previous_dts + av_rescale_q(ipkt->dts - audio_last_dts, audio_input_stream->time_base, audio_output_stream->time_base); + } + Debug(2, "opkt.dts = %d from ipkt->dts(%d) - last_dts(%d)", opkt.dts, ipkt->dts, audio_last_dts ); + } + } + audio_last_dts = ipkt->dts; + if ( opkt.dts > opkt.pts ) { + Debug(1,"opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen before presentation.", opkt.dts, opkt.pts ); + opkt.dts = opkt.pts; + } + + // I wonder if we could just use duration instead of all the hoop jumping above? + opkt.duration = av_rescale_q(ipkt->duration, audio_input_stream->time_base, audio_output_stream->time_base); + Debug( 2, "opkt.pts (%d), opkt.dts(%d) opkt.duration = (%d)", opkt.pts, opkt.dts, opkt.duration ); + + // pkt.pos: byte position in stream, -1 if unknown + opkt.pos = -1; + opkt.stream_index = ipkt->stream_index; + Debug(2, "Stream index is %d", opkt.stream_index ); + + AVPacket safepkt; + memcpy(&safepkt, &opkt, sizeof(AVPacket)); + audio_previous_dts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance + audio_previous_pts = opkt.pts; + ret = av_interleaved_write_frame(oc, &opkt); + if(ret!=0){ + Error("Error writing audio frame packet: %s\n", av_make_error_string(ret).c_str()); + dumpPacket(&safepkt); + } else { + Debug(2,"Success writing audio frame" ); + } + zm_av_packet_unref(&opkt); + return 0; +} // end int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) + diff --git a/src/zm_videostore.h b/src/zm_videostore.h new file mode 100644 index 000000000..36ddd7bcf --- /dev/null +++ b/src/zm_videostore.h @@ -0,0 +1,84 @@ +#ifndef ZM_VIDEOSTORE_H +#define ZM_VIDEOSTORE_H + +#include "zm_ffmpeg.h" +extern "C" { +#include "libavutil/audio_fifo.h" + +#ifdef HAVE_LIBAVRESAMPLE +#include "libavresample/avresample.h" +#endif +} + +#if HAVE_LIBAVCODEC + +#include "zm_monitor.h" + +class VideoStore { +private: + unsigned int packets_written; + + AVOutputFormat *output_format; + AVFormatContext *oc; + AVStream *video_output_stream; + AVStream *audio_output_stream; + AVCodecContext *video_output_context; + + AVStream *video_input_stream; + AVStream *audio_input_stream; + + // Move this into the object so that we aren't constantly allocating/deallocating it on the stack + AVPacket opkt; + // we are transcoding + AVFrame *input_frame; + AVFrame *output_frame; + + AVCodecContext *video_input_context; + AVCodecContext *audio_input_context; + int ret; + + // The following are used when encoding the audio stream to AAC + AVCodec *audio_output_codec; + AVCodecContext *audio_output_context; + int data_present; + AVAudioFifo *fifo; + int output_frame_size; +#ifdef HAVE_LIBAVRESAMPLE +AVAudioResampleContext* resample_context; +#endif + uint8_t *converted_input_samples; + + const char *filename; + const char *format; + + bool keyframeMessage; + int keyframeSkipNumber; + + // These are for input + int64_t video_last_pts; + int64_t video_last_dts; + int64_t audio_last_pts; + int64_t audio_last_dts; + + // These are for output, should start at zero. We assume they do not wrap because we just aren't going to save files that big. + int64_t video_previous_pts; + int64_t video_previous_dts; + int64_t audio_previous_pts; + int64_t audio_previous_dts; + + int64_t filter_in_rescale_delta_last; + + bool setup_resampler(); + +public: + VideoStore(const char *filename_in, const char *format_in, AVStream *video_input_stream, AVStream *audio_input_stream, int64_t nStartTime, Monitor * p_monitor ); + ~VideoStore(); + + int writeVideoFramePacket( AVPacket *pkt ); + int writeAudioFramePacket( AVPacket *pkt ); + void dumpPacket( AVPacket *pkt ); +}; + +#endif //havelibav +#endif //zm_videostore_h + diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index f3d1120bc..e92c3d493 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include "zm.h" @@ -23,8 +23,28 @@ #include "zm_image.h" #include "zm_monitor.h" -void Zone::Setup( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Polygon &p_polygon, const Rgb p_alarm_rgb, CheckMethod p_check_method, int p_min_pixel_threshold, int p_max_pixel_threshold, int p_min_alarm_pixels, int p_max_alarm_pixels, const Coord &p_filter_box, int p_min_filter_pixels, int p_max_filter_pixels, int p_min_blob_pixels, int p_max_blob_pixels, int p_min_blobs, int p_max_blobs, int p_overload_frames, int p_extend_alarm_frames ) -{ +void Zone::Setup( + Monitor *p_monitor, + int p_id, + const char *p_label, + ZoneType p_type, + const Polygon &p_polygon, + const Rgb p_alarm_rgb, + CheckMethod p_check_method, + int p_min_pixel_threshold, + int p_max_pixel_threshold, + int p_min_alarm_pixels, + int p_max_alarm_pixels, + const Coord &p_filter_box, + int p_min_filter_pixels, + int p_max_filter_pixels, + int p_min_blob_pixels, + int p_max_blob_pixels, + int p_min_blobs, + int p_max_blobs, + int p_overload_frames, + int p_extend_alarm_frames +) { monitor = p_monitor; id = p_id; @@ -46,11 +66,12 @@ void Zone::Setup( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_ min_blobs = p_min_blobs; max_blobs = p_max_blobs; overload_frames = p_overload_frames; - extend_alarm_frames = p_extend_alarm_frames; + extend_alarm_frames = p_extend_alarm_frames; Debug( 1, "Initialised zone %d/%s - %d - %dx%d - Rgb:%06x, CM:%d, MnAT:%d, MxAT:%d, MnAP:%d, MxAP:%d, FB:%dx%d, MnFP:%d, MxFP:%d, MnBS:%d, MxBS:%d, MnB:%d, MxB:%d, OF: %d, AF: %d", id, label, type, polygon.Width(), polygon.Height(), alarm_rgb, check_method, min_pixel_threshold, max_pixel_threshold, min_alarm_pixels, max_alarm_pixels, filter_box.X(), filter_box.Y(), min_filter_pixels, max_filter_pixels, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs, overload_frames, extend_alarm_frames ); alarmed = false; + was_alarmed = false; pixel_diff = 0; alarm_pixels = 0; alarm_filter_pixels = 0; @@ -64,144 +85,113 @@ void Zone::Setup( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_ overload_count = 0; extend_alarm_count = 0; - pg_image = new Image( monitor->Width(), monitor->Height(), 1, ZM_SUBPIX_ORDER_NONE); + pg_image = new Image( monitor->Width(), monitor->Height(), 1, ZM_SUBPIX_ORDER_NONE ); pg_image->Clear(); pg_image->Fill( 0xff, polygon ); pg_image->Outline( 0xff, polygon ); ranges = new Range[monitor->Height()]; - for ( unsigned int y = 0; y < monitor->Height(); y++) - { + for ( unsigned int y = 0; y < monitor->Height(); y++ ) { ranges[y].lo_x = -1; ranges[y].hi_x = 0; ranges[y].off_x = 0; const uint8_t *ppoly = pg_image->Buffer( 0, y ); - for ( unsigned int x = 0; x < monitor->Width(); x++, ppoly++ ) - { - if ( *ppoly ) - { - if ( ranges[y].lo_x == -1 ) - { + for ( unsigned int x = 0; x < monitor->Width(); x++, ppoly++ ) { + if ( *ppoly ) { + if ( ranges[y].lo_x == -1 ) { ranges[y].lo_x = x; } - if ( (unsigned int)ranges[y].hi_x < x ) - { + if ( (unsigned int)ranges[y].hi_x < x ) { ranges[y].hi_x = x; } } } } - - if ( config.record_diag_images ) - { + + // FIXME: Is this not a problem? If you had two zones for a monitor.. then these would conflict. You would only get 1 dump file + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( ! diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%s/diag-%d-poly.jpg", config.dir_events, monitor->Name(), id); } pg_image->WriteJpeg( diag_path ); } -} +} // end Zone::Setup -Zone::~Zone() -{ +Zone::~Zone() { delete[] label; delete image; delete pg_image; delete[] ranges; } -void Zone::RecordStats( const Event *event ) -{ - static char sql[ZM_SQL_MED_BUFSIZ]; +void Zone::RecordStats( const Event *event ) { + static char sql[ZM_SQL_MED_BUFSIZ]; snprintf( sql, sizeof(sql), "insert into Stats set MonitorId=%d, ZoneId=%d, EventId=%d, FrameId=%d, PixelDiff=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, MinX=%d, MinY=%d, MaxX=%d, MaxY=%d, Score=%d", monitor->Id(), id, event->Id(), event->Frames()+1, pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, min_blob_size, max_blob_size, alarm_box.LoX(), alarm_box.LoY(), alarm_box.HiX(), alarm_box.HiY(), score ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert event stats: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } -} +} // end void Zone::RecordStats( const Event *event ) - -//============================================================================= -bool Zone::CheckOverloadCount() -{ +bool Zone::CheckOverloadCount() { Info("Overloaded count: %d, Overloaded frames: %d", overload_count, overload_frames); - if ( overload_count ) - { - Info( "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); - Debug( 4, "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); - overload_count--; - return( false ); + if ( overload_count ) { + Info( "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); + Debug( 4, "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); + overload_count--; + return( false ); } return true; -} +} // end bool Zone::CheckOverloadCount() -void Zone::SetScore(unsigned int nScore) -{ +void Zone::SetScore(unsigned int nScore) { score = nScore; -} +} // end void Zone::SetScore(unsigned int nScore) - -void Zone::SetAlarmImage(const Image* srcImage) -{ +void Zone::SetAlarmImage(const Image* srcImage) { delete image; image = new Image(*srcImage); -} +} // end void Zone::SetAlarmImage( const Image* srcImage ) -int Zone::GetOverloadCount() -{ +int Zone::GetOverloadCount() { return overload_count; -} +} // end int Zone::GetOverloadCount() -void Zone::SetOverloadCount(int nOverCount) -{ +void Zone::SetOverloadCount(int nOverCount) { overload_count = nOverCount; -} +} // end void Zone::SetOverloadCount(int nOverCount ) -int Zone::GetOverloadFrames() -{ +int Zone::GetOverloadFrames() { return overload_frames; -} +} // end int Zone::GetOverloadFrames -int Zone::GetExtendAlarmCount() -{ +int Zone::GetExtendAlarmCount() { return extend_alarm_count; -} +} // end int Zone::GetExtendAlarmCount() -void Zone::SetExtendAlarmCount(int nExtendAlarmCount) -{ +void Zone::SetExtendAlarmCount(int nExtendAlarmCount) { extend_alarm_count = nExtendAlarmCount; -} +} // end void Zone::SetExtendAlarmCount( int nExtendAlarmCount ) -int Zone::GetExtendAlarmFrames() -{ +int Zone::GetExtendAlarmFrames() { return extend_alarm_frames; -} +} // end int Zone::GetExtendAlarmFrames() -bool Zone::CheckExtendAlarmCount() -{ - Info("ExtendAlarm count: %d, ExtendAlarm frames: %d", extend_alarm_count, extend_alarm_frames); - if ( extend_alarm_count ) - { - Debug( 3, "In extend mode, %d frames of %d remaining", extend_alarm_count, extend_alarm_frames ); - extend_alarm_count--; - return( true ); +bool Zone::CheckExtendAlarmCount() { + Info( "ExtendAlarm count: %d, ExtendAlarm frames: %d", extend_alarm_count, extend_alarm_frames ); + if ( extend_alarm_count ) { + Debug( 3, "In extend mode, %d frames of %d remaining", extend_alarm_count, extend_alarm_frames ); + extend_alarm_count--; + return( true ); } return false; -} +} // end bool Zone::CheckExtendAlarmCount - -//=========================================================================== - - - -bool Zone::CheckAlarms( const Image *delta_image ) -{ +bool Zone::CheckAlarms( const Image *delta_image ) { ResetStats(); - if ( overload_count ) - { + if ( overload_count ) { Info( "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); Debug( 4, "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); overload_count--; @@ -225,33 +215,29 @@ bool Zone::CheckAlarms( const Image *delta_image ) int alarm_mid_x = -1; int alarm_mid_y = -1; - + unsigned int lo_y = polygon.LoY(); unsigned int lo_x = polygon.LoX(); unsigned int hi_x = polygon.HiX(); unsigned int hi_y = polygon.HiY(); Debug( 4, "Checking alarms for zone %d/%s in lines %d -> %d", id, label, lo_y, hi_y ); - - - Debug( 5, "Checking for alarmed pixels" ); + /* if(config.cpu_extensions && sseversion >= 20) { - sse2_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); - } else { - std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); - } */ + sse2_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); + } else { + std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); + } */ std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); - - if ( config.record_diag_images ) - { + + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( ! diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%s/diag-%d-%d.jpg", config.dir_events, monitor->Name(), id, 1 ); } diff_image->WriteJpeg( diag_path ); } - + if ( pixel_diff_count && alarm_pixels ) pixel_diff = pixel_diff_count/alarm_pixels; Debug( 5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d", alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff ); @@ -269,63 +255,52 @@ bool Zone::CheckAlarms( const Image *delta_image ) /* No alarmed pixels */ return (false); } - + score = (100*alarm_pixels)/polygon.Area(); - if(score < 1) + if ( score < 1 ) score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ Debug( 5, "Current score is %d", score ); - - if ( check_method >= FILTERED_PIXELS ) - { + + if ( check_method >= FILTERED_PIXELS ) { int bx = filter_box.X(); int by = filter_box.Y(); int bx1 = bx-1; int by1 = by-1; Debug( 5, "Checking for filtered pixels" ); - if ( bx > 1 || by > 1 ) - { + if ( bx > 1 || by > 1 ) { // Now remove any pixels smaller than our filter size unsigned char *cpdiff; int ldx, hdx, ldy, hdy; bool block; - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { + for ( unsigned int y = lo_y; y <= hi_y; y++ ) { int lo_x = ranges[y].lo_x; int hi_x = ranges[y].hi_x; pdiff = (uint8_t*)diff_image->Buffer( lo_x, y ); - for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) - { - if ( *pdiff == WHITE ) - { + for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) { + if ( *pdiff == WHITE ) { // Check participation in an X block ldx = (x>=(lo_x+bx1))?-bx1:lo_x-x; hdx = (x<=(hi_x-bx1))?0:((hi_x-x)-bx1); ldy = (y>=(lo_y+by1))?-by1:lo_y-y; hdy = (y<=(hi_y-by1))?0:((hi_y-y)-by1); block = false; - for ( int dy = ldy; !block && dy <= hdy; dy++ ) - { - for ( int dx = ldx; !block && dx <= hdx; dx++ ) - { + for ( int dy = ldy; !block && dy <= hdy; dy++ ) { + for ( int dx = ldx; !block && dx <= hdx; dx++ ) { block = true; - for ( int dy2 = 0; block && dy2 < by; dy2++ ) - { - for ( int dx2 = 0; block && dx2 < bx; dx2++ ) - { + for ( int dy2 = 0; block && dy2 < by; dy2++ ) { + for ( int dx2 = 0; block && dx2 < bx; dx2++ ) { cpdiff = diff_buff + (((y+dy+dy2)*diff_width) + (x+dx+dx2)); - if ( !*cpdiff ) - { + if ( !*cpdiff ) { block = false; } } } } } - if ( !block ) - { + if ( !block ) { *pdiff = BLACK; continue; } @@ -333,24 +308,20 @@ bool Zone::CheckAlarms( const Image *delta_image ) } } } - } - else - { + } else { alarm_filter_pixels = alarm_pixels; } - - if ( config.record_diag_images ) - { + + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( !diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", config.dir_events, monitor->Id(), id, 2 ); } diff_image->WriteJpeg( diag_path ); } - + Debug( 5, "Got %d filtered pixels, need %d -> %d", alarm_filter_pixels, min_filter_pixels, max_filter_pixels ); - + if( alarm_filter_pixels ) { if( min_filter_pixels && (alarm_filter_pixels < min_filter_pixels) ) { /* Not enough pixels alarmed */ @@ -364,14 +335,13 @@ bool Zone::CheckAlarms( const Image *delta_image ) /* No filtered pixels */ return (false); } - + score = (100*alarm_filter_pixels)/(polygon.Area()); - if(score < 1) + if ( score < 1 ) score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ Debug( 5, "Current score is %d", score ); - if ( check_method >= BLOBS ) - { + if ( check_method >= BLOBS ) { Debug( 5, "Checking for blob pixels" ); typedef struct { unsigned char tag; int count; int lo_x; int hi_x; int lo_y; int hi_y; } BlobStats; BlobStats blob_stats[256]; @@ -380,44 +350,38 @@ bool Zone::CheckAlarms( const Image *delta_image ) uint8_t last_x, last_y; BlobStats *bsx, *bsy; BlobStats *bsm, *bss; - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { + for ( unsigned int y = lo_y; y <= hi_y; y++ ) { int lo_x = ranges[y].lo_x; int hi_x = ranges[y].hi_x; pdiff = (uint8_t*)diff_image->Buffer( lo_x, y ); - for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) - { - if ( *pdiff == WHITE ) - { + for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) { + if ( *pdiff == WHITE ) { Debug( 9, "Got white pixel at %d,%d (%p)", x, y, pdiff ); //last_x = (x>lo_x)?*(pdiff-1):0; //last_y = (y>lo_y&&x>=last_lo_x&&x<=last_hi_x)?*(pdiff-diff_width):0; - + last_x = 0; if(x > 0) { if((x-1) >= lo_x) { last_x = *(pdiff-1); } } - + last_y = 0; if(y > 0) { if((y-1) >= lo_y && ranges[(y-1)].lo_x <= x && ranges[(y-1)].hi_x >= x) { last_y = *(pdiff-diff_width); } } - - if ( last_x ) - { + + if ( last_x ) { Debug( 9, "Left neighbour is %d", last_x ); bsx = &blob_stats[last_x]; - if ( last_y ) - { + if ( last_y ) { Debug( 9, "Top neighbour is %d", last_y ); bsy = &blob_stats[last_y]; - if ( last_x == last_y ) - { + if ( last_x == last_y ) { Debug( 9, "Matching neighbours, setting to %d", last_x ); // Add to the blob from the x side (either side really) *pdiff = last_x; @@ -425,9 +389,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) bsx->count++; if ( x > bsx->hi_x ) bsx->hi_x = x; if ( (int)y > bsx->hi_y ) bsx->hi_y = y; - } - else - { + } else { // Aggregate blobs bsm = bsx->count>=bsy->count?bsx:bsy; bss = bsm==bsx?bsy:bsx; @@ -437,19 +399,16 @@ bool Zone::CheckAlarms( const Image *delta_image ) Debug( 9, "Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bss->tag, bss->count, bss->lo_x, bss->hi_x, bss->lo_y, bss->hi_y ); // Now change all those pixels to the other setting int changed = 0; - for ( int sy = bss->lo_y; sy <= bss->hi_y; sy++) - { + for ( int sy = bss->lo_y; sy <= bss->hi_y; sy++) { int lo_sx = bss->lo_x>=ranges[sy].lo_x?bss->lo_x:ranges[sy].lo_x; int hi_sx = bss->hi_x<=ranges[sy].hi_x?bss->hi_x:ranges[sy].hi_x; Debug( 9, "Changing %d, %d->%d", sy, lo_sx, hi_sx ); Debug( 9, "Range %d, %d->%d", sy, ranges[sy].lo_x, ranges[sy].hi_x ); spdiff = diff_buff + ((diff_width * sy) + lo_sx); - for ( int sx = lo_sx; sx <= hi_sx; sx++, spdiff++ ) - { + for ( int sx = lo_sx; sx <= hi_sx; sx++, spdiff++ ) { Debug( 9, "Pixel at %d,%d (%p) is %d", sx, sy, spdiff, *spdiff ); - if ( *spdiff == bss->tag ) - { + if ( *spdiff == bss->tag ) { Debug( 9, "Setting pixel" ); *spdiff = bsm->tag; changed++; @@ -458,8 +417,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) } *pdiff = bsm->tag; alarm_blob_pixels++; - if ( !changed ) - { + if ( !changed ) { Info( "Master blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bsm->tag, bsm->count, bsm->lo_x, bsm->hi_x, bsm->lo_y, bsm->hi_y ); Info( "Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bss->tag, bss->count, bss->lo_x, bss->hi_x, bss->lo_y, bss->hi_y ); Error( "No pixels changed, exiting" ); @@ -487,9 +445,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) bss->hi_x = 0; bss->hi_y = 0; } - } - else - { + } else { Debug( 9, "Setting to left neighbour %d", last_x ); // Add to the blob from the x side *pdiff = last_x; @@ -498,14 +454,11 @@ bool Zone::CheckAlarms( const Image *delta_image ) if ( x > bsx->hi_x ) bsx->hi_x = x; if ( (int)y > bsx->hi_y ) bsx->hi_y = y; } - } - else - { - if ( last_y ) - { + } else { + if ( last_y ) { Debug( 9, "Top neighbour is %d", last_y ); Debug( 9, "Setting to top neighbour %d", last_y ); - + // Add to the blob from the y side BlobStats *bsy = &blob_stats[last_y]; @@ -514,28 +467,19 @@ bool Zone::CheckAlarms( const Image *delta_image ) bsy->count++; if ( x > bsy->hi_x ) bsy->hi_x = x; if ( (int)y > bsy->hi_y ) bsy->hi_y = y; - } - else - { + } else { // Create a new blob int i; - for ( i = (WHITE-1); i > 0; i-- ) - { + for ( i = (WHITE-1); i > 0; i-- ) { BlobStats *bs = &blob_stats[i]; // See if we can recycle one first, only if it's at least two rows up - if ( bs->count && bs->hi_y < (int)(y-1) ) - { - if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) - { - if ( config.create_analysis_images || config.record_diag_images ) - { - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) - { + if ( bs->count && bs->hi_y < (int)(y-1) ) { + if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) { + if ( config.create_analysis_images || config.record_diag_images ) { + for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) { spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) - { - if ( *spdiff == bs->tag ) - { + for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) { + if ( *spdiff == bs->tag ) { *spdiff = BLACK; } } @@ -543,7 +487,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) } alarm_blobs--; alarm_blob_pixels -= bs->count; - + Debug( 6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs ); bs->tag = 0; @@ -554,8 +498,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) bs->hi_y = 0; } } - if ( !bs->count ) - { + if ( !bs->count ) { Debug( 9, "Creating new blob %d", i ); *pdiff = i; alarm_blob_pixels++; @@ -569,8 +512,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) break; } } - if ( i == 0 ) - { + if ( i == 0 ) { Warning( "Max blob count reached. Unable to allocate new blobs so terminating. Zone settings may be too sensitive." ); x = hi_x+1; y = hi_y+1; @@ -580,40 +522,30 @@ bool Zone::CheckAlarms( const Image *delta_image ) } } } - if ( config.record_diag_images ) - { + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( !diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", config.dir_events, monitor->Id(), id, 3 ); } diff_image->WriteJpeg( diag_path ); } - if ( !alarm_blobs ) - { + if ( !alarm_blobs ) { return( false ); } - + Debug( 5, "Got %d raw blob pixels, %d raw blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs ); // Now eliminate blobs under the threshold - for ( int i = 1; i < WHITE; i++ ) - { + for ( int i = 1; i < WHITE; i++ ) { BlobStats *bs = &blob_stats[i]; - if ( bs->count ) - { - if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) - { - if ( config.create_analysis_images || config.record_diag_images ) - { - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) - { + if ( bs->count ) { + if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) { + if ( config.create_analysis_images || config.record_diag_images ) { + for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) { spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) - { - if ( *spdiff == bs->tag ) - { + for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) { + if ( *spdiff == bs->tag ) { *spdiff = BLACK; } } @@ -621,7 +553,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) } alarm_blobs--; alarm_blob_pixels -= bs->count; - + Debug( 6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs ); bs->tag = 0; @@ -630,26 +562,22 @@ bool Zone::CheckAlarms( const Image *delta_image ) bs->lo_y = 0; bs->hi_x = 0; bs->hi_y = 0; - } - else - { + } else { Debug( 6, "Preserved blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs ); if ( !min_blob_size || bs->count < min_blob_size ) min_blob_size = bs->count; if ( !max_blob_size || bs->count > max_blob_size ) max_blob_size = bs->count; } } } - if ( config.record_diag_images ) - { + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { + if ( !diag_path[0] ) { snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", config.dir_events, monitor->Id(), id, 4 ); } diff_image->WriteJpeg( diag_path ); } Debug( 5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs ); - + if( alarm_blobs ) { if( min_blobs && (alarm_blobs < min_blobs) ) { /* Not enough pixels alarmed */ @@ -663,9 +591,9 @@ bool Zone::CheckAlarms( const Image *delta_image ) /* No blobs */ return (false); } - + score = (100*alarm_blob_pixels)/(polygon.Area()); - if(score < 1) + if ( score < 1 ) score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ Debug( 5, "Current score is %d", score ); @@ -673,25 +601,19 @@ bool Zone::CheckAlarms( const Image *delta_image ) alarm_hi_x = polygon.LoX()-1; alarm_lo_y = polygon.HiY()+1; alarm_hi_y = polygon.LoY()-1; - for ( int i = 1; i < WHITE; i++ ) - { + + for ( int i = 1; i < WHITE; i++ ) { BlobStats *bs = &blob_stats[i]; - if ( bs->count ) - { - if ( bs->count == max_blob_size ) - { - if ( config.weighted_alarm_centres ) - { + if ( bs->count ) { + if ( bs->count == max_blob_size ) { + if ( config.weighted_alarm_centres ) { unsigned long x_total = 0; unsigned long y_total = 0; - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) - { + for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) { spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) - { - if ( *spdiff == bs->tag ) - { + for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) { + if ( *spdiff == bs->tag ) { x_total += sx; y_total += sy; } @@ -699,9 +621,7 @@ bool Zone::CheckAlarms( const Image *delta_image ) } alarm_mid_x = int(round(x_total/bs->count)); alarm_mid_y = int(round(y_total/bs->count)); - } - else - { + } else { alarm_mid_x = int((bs->hi_x+bs->lo_x+1)/2); alarm_mid_y = int((bs->hi_y+bs->lo_y+1)/2); } @@ -711,115 +631,91 @@ bool Zone::CheckAlarms( const Image *delta_image ) if ( alarm_lo_y > bs->lo_y ) alarm_lo_y = bs->lo_y; if ( alarm_hi_x < bs->hi_x ) alarm_hi_x = bs->hi_x; if ( alarm_hi_y < bs->hi_y ) alarm_hi_y = bs->hi_y; - } - } - } - else - { + } // end if bs->count + } // end for i < WHITE + } else { alarm_mid_x = int((alarm_hi_x+alarm_lo_x+1)/2); alarm_mid_y = int((alarm_hi_y+alarm_lo_y+1)/2); } } - if ( type == INCLUSIVE ) - { + if ( type == INCLUSIVE ) { // score >>= 1; score /= 2; - } - else if ( type == EXCLUSIVE ) - { + } else if ( type == EXCLUSIVE ) { // score <<= 1; score *= 2; - } Debug( 5, "Adjusted score is %d", score ); // Now outline the changed region - if ( score ) - { + if ( score ) { alarm_box = Box( Coord( alarm_lo_x, alarm_lo_y ), Coord( alarm_hi_x, alarm_hi_y ) ); //if ( monitor->followMotion() ) - if ( true ) - { + if ( true ) { alarm_centre = Coord( alarm_mid_x, alarm_mid_y ); - } - else - { + } else { alarm_centre = alarm_box.Centre(); } - if ( (type < PRECLUSIVE) && check_method >= BLOBS && config.create_analysis_images ) - { + if ( (type < PRECLUSIVE) && check_method >= BLOBS && config.create_analysis_images ) { // First mask out anything we don't want - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { + for ( unsigned int y = lo_y; y <= hi_y; y++ ) { pdiff = diff_buff + ((diff_width * y) + lo_x); int lo_x2 = ranges[y].lo_x; int hi_x2 = ranges[y].hi_x; int lo_gap = lo_x2-lo_x; - if ( lo_gap > 0 ) - { - if ( lo_gap == 1 ) - { + if ( lo_gap > 0 ) { + if ( lo_gap == 1 ) { *pdiff++ = BLACK; - } - else - { + } else { memset( pdiff, BLACK, lo_gap ); pdiff += lo_gap; } } ppoly = pg_image->Buffer( lo_x2, y ); - for ( int x = lo_x2; x <= hi_x2; x++, pdiff++, ppoly++ ) - { - if ( !*ppoly ) - { + for ( int x = lo_x2; x <= hi_x2; x++, pdiff++, ppoly++ ) { + if ( !*ppoly ) { *pdiff = BLACK; } } int hi_gap = hi_x-hi_x2; - if ( hi_gap > 0 ) - { - if ( hi_gap == 1 ) - { + if ( hi_gap > 0 ) { + if ( hi_gap == 1 ) { *pdiff = BLACK; - } - else - { + } else { memset( pdiff, BLACK, hi_gap ); } } } - - if( monitor->Colours() == ZM_COLOUR_GRAY8 ) { + + if ( monitor->Colours() == ZM_COLOUR_GRAY8 ) { image = diff_image->HighlightEdges( alarm_rgb, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB, &polygon.Extent() ); } else { image = diff_image->HighlightEdges( alarm_rgb, monitor->Colours(), monitor->SubpixelOrder(), &polygon.Extent() ); } - + // Only need to delete this when 'image' becomes detached and points somewhere else delete diff_image; - } - else - { + } else { delete image; image = 0; } - Debug( 1, "%s: Pixel Diff: %d, Alarm Pixels: %d, Filter Pixels: %d, Blob Pixels: %d, Blobs: %d, Score: %d", Label(), pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, score ); + Debug( 1, "%s: Pixel Diff: %d, Alarm Pixels: %d, Filter Pixels: %d, Blob Pixels: %d, Blobs: %d, Score: %d", + Label(), pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, score ); } return( true ); } -bool Zone::ParsePolygonString( const char *poly_string, Polygon &polygon ) -{ +bool Zone::ParsePolygonString( const char *poly_string, Polygon &polygon ) { Debug( 3, "Parsing polygon string '%s'", poly_string ); char *str_ptr = new char[strlen(poly_string)+1]; @@ -830,27 +726,21 @@ bool Zone::ParsePolygonString( const char *poly_string, Polygon &polygon ) int n_coords = 0; int max_n_coords = strlen(str)/4; Coord *coords = new Coord[max_n_coords]; - while( true ) - { - if ( *str == '\0' ) - { + while( true ) { + if ( *str == '\0' ) { break; } ws = strchr( str, ' ' ); - if ( ws ) - { + if ( ws ) { *ws = '\0'; } char *cp = strchr( str, ',' ); - if ( !cp ) - { + if ( !cp ) { Error( "Bogus coordinate %s found in polygon string", str ); delete[] coords; delete[] str_ptr; return( false ); - } - else - { + } else { *cp = '\0'; char *xp = str; char *yp = cp+1; @@ -888,8 +778,7 @@ bool Zone::ParsePolygonString( const char *poly_string, Polygon &polygon ) return( true ); } -bool Zone::ParseZoneString( const char *zone_string, int &zone_id, int &colour, Polygon &polygon ) -{ +bool Zone::ParseZoneString( const char *zone_string, int &zone_id, int &colour, Polygon &polygon ) { Debug( 3, "Parsing zone string '%s'", zone_string ); char *str_ptr = new char[strlen(zone_string)+1]; @@ -897,14 +786,12 @@ bool Zone::ParseZoneString( const char *zone_string, int &zone_id, int &colour, strcpy( str, zone_string ); char *ws = strchr( str, ' ' ); - if ( !ws ) - { + if ( !ws ) { Debug( 3, "No initial whitespace found in zone string '%s', finishing", str ); } zone_id = strtol( str, 0, 10 ); Debug( 3, "Got zone %d from zone string", zone_id ); - if ( !ws ) - { + if ( !ws ) { delete str_ptr; return( true ); } @@ -913,14 +800,12 @@ bool Zone::ParseZoneString( const char *zone_string, int &zone_id, int &colour, str = ws+1; ws = strchr( str, ' ' ); - if ( !ws ) - { + if ( !ws ) { Debug( 3, "No secondary whitespace found in zone string '%s', finishing", zone_string ); } colour = strtol( str, 0, 16 ); Debug( 3, "Got colour %06x from zone string", colour ); - if ( !ws ) - { + if ( !ws ) { delete str_ptr; return( true ); } @@ -937,19 +822,16 @@ bool Zone::ParseZoneString( const char *zone_string, int &zone_id, int &colour, return( result ); } -int Zone::Load( Monitor *monitor, Zone **&zones ) -{ +int Zone::Load( Monitor *monitor, Zone **&zones ) { static char sql[ZM_SQL_MED_BUFSIZ]; snprintf( sql, sizeof(sql), "select Id,Name,Type+0,Units,Coords,AlarmRGB,CheckMethod+0,MinPixelThreshold,MaxPixelThreshold,MinAlarmPixels,MaxAlarmPixels,FilterX,FilterY,MinFilterPixels,MaxFilterPixels,MinBlobPixels,MaxBlobPixels,MinBlobs,MaxBlobs,OverloadFrames,ExtendAlarmFrames from Zones where MonitorId = %d order by Type, Id", monitor->Id() ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -957,8 +839,7 @@ int Zone::Load( Monitor *monitor, Zone **&zones ) Debug( 1, "Got %d zones for monitor %s", n_zones, monitor->Name() ); delete[] zones; zones = new Zone *[n_zones]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int Id = atoi(dbrow[col++]); @@ -982,7 +863,7 @@ int Zone::Load( Monitor *monitor, Zone **&zones ) int MaxBlobs = dbrow[col]?atoi(dbrow[col]):0; col++; int OverloadFrames = dbrow[col]?atoi(dbrow[col]):0; col++; int ExtendAlarmFrames = dbrow[col]?atoi(dbrow[col]):0; col++; - + /* HTML colour code is actually BGR in memory, we want RGB */ AlarmRGB = rgb_convert(AlarmRGB, ZM_SUBPIX_ORDER_BGR); @@ -990,17 +871,18 @@ int Zone::Load( Monitor *monitor, Zone **&zones ) Polygon polygon; if ( !ParsePolygonString( Coords, polygon ) ) { Error( "Unable to parse polygon string '%s' for zone %d/%s for monitor %s, ignoring", Coords, Id, Name, monitor->Name() ); + n_zones -= 1; continue; } if ( polygon.LoX() < 0 || polygon.HiX() >= (int)monitor->Width() - || polygon.LoY() < 0 || polygon.HiY() >= (int)monitor->Height() ) { + || polygon.LoY() < 0 || polygon.HiY() >= (int)monitor->Height() ) { Error( "Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), ignoring", Id, Name, monitor->Name(), polygon.LoX(), polygon.LoY(), polygon.HiX(), polygon.HiY() ); + n_zones -= 1; continue; } - if ( false && !strcmp( Units, "Percent" ) ) - { + if ( false && !strcmp( Units, "Percent" ) ) { MinAlarmPixels = (MinAlarmPixels*polygon.Area())/100; MaxAlarmPixels = (MaxAlarmPixels*polygon.Area())/100; MinFilterPixels = (MinFilterPixels*polygon.Area())/100; @@ -1009,53 +891,44 @@ int Zone::Load( Monitor *monitor, Zone **&zones ) MaxBlobPixels = (MaxBlobPixels*polygon.Area())/100; } - if ( atoi(dbrow[2]) == Zone::INACTIVE ) - { + if ( atoi(dbrow[2]) == Zone::INACTIVE ) { zones[i] = new Zone( monitor, Id, Name, polygon ); + } else if ( atoi(dbrow[2]) == Zone::PRIVACY ) { + zones[i] = new Zone( monitor, Id, Name, (Zone::ZoneType)Type, polygon ); } - else if ( atoi(dbrow[2]) == Zone::PRIVACY ) - { - zones[i] = new Zone( monitor, Id, Name, (Zone::ZoneType)Type, polygon ); - } - { - zones[i] = new Zone( monitor, Id, Name, (Zone::ZoneType)Type, polygon, AlarmRGB, (Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold, MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ), MinFilterPixels, MaxFilterPixels, MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, OverloadFrames, ExtendAlarmFrames ); - } + zones[i] = new Zone( monitor, Id, Name, (Zone::ZoneType)Type, polygon, AlarmRGB, (Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold, MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ), MinFilterPixels, MaxFilterPixels, MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, OverloadFrames, ExtendAlarmFrames ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } - // Yadda yadda mysql_free_result( result ); return( n_zones ); } -bool Zone::DumpSettings( char *output, bool /*verbose*/ ) -{ +bool Zone::DumpSettings( char *output, bool /*verbose*/ ) { output[0] = 0; sprintf( output+strlen(output), " Id : %d\n", id ); sprintf( output+strlen(output), " Label : %s\n", label ); sprintf( output+strlen(output), " Type: %d - %s\n", type, - type==ACTIVE?"Active":( - type==INCLUSIVE?"Inclusive":( - type==EXCLUSIVE?"Exclusive":( - type==PRECLUSIVE?"Preclusive":( - type==INACTIVE?"Inactive":( - type==PRIVACY?"Privacy":"Unknown" - )))))); + type==ACTIVE?"Active":( + type==INCLUSIVE?"Inclusive":( + type==EXCLUSIVE?"Exclusive":( + type==PRECLUSIVE?"Preclusive":( + type==INACTIVE?"Inactive":( + type==PRIVACY?"Privacy":"Unknown" + )))))); sprintf( output+strlen(output), " Shape : %d points\n", polygon.getNumCoords() ); - for ( int i = 0; i < polygon.getNumCoords(); i++ ) - { + for ( int i = 0; i < polygon.getNumCoords(); i++ ) { sprintf( output+strlen(output), " %i: %d,%d\n", i, polygon.getCoord( i ).X(), polygon.getCoord( i ).Y() ); } sprintf( output+strlen(output), " Alarm RGB : %06x\n", alarm_rgb ); sprintf( output+strlen(output), " Check Method: %d - %s\n", check_method, - check_method==ALARMED_PIXELS?"Alarmed Pixels":( - check_method==FILTERED_PIXELS?"FilteredPixels":( - check_method==BLOBS?"Blobs":"Unknown" - ))); + check_method==ALARMED_PIXELS?"Alarmed Pixels":( + check_method==FILTERED_PIXELS?"FilteredPixels":( + check_method==BLOBS?"Blobs":"Unknown" + ))); sprintf( output+strlen(output), " Min Pixel Threshold : %d\n", min_pixel_threshold ); sprintf( output+strlen(output), " Max Pixel Threshold : %d\n", max_pixel_threshold ); sprintf( output+strlen(output), " Min Alarm Pixels : %d\n", min_alarm_pixels ); @@ -1080,38 +953,33 @@ void Zone::std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsig unsigned int hi_y; unsigned int lo_x; unsigned int hi_x; - + if(max_pixel_threshold) calc_max_pixel_threshold = max_pixel_threshold; - + lo_y = polygon.LoY(); hi_y = polygon.HiY(); - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { + for ( unsigned int y = lo_y; y <= hi_y; y++ ) { lo_x = ranges[y].lo_x; hi_x = ranges[y].hi_x; - + Debug( 7, "Checking line %d from %d -> %d", y, lo_x, hi_x ); pdiff = (uint8_t*)pdiff_image->Buffer( lo_x, y ); ppoly = ppoly_image->Buffer( lo_x, y ); - - for ( unsigned int x = lo_x; x <= hi_x; x++, pdiff++, ppoly++ ) - { - if ( *ppoly && (*pdiff > min_pixel_threshold) && (*pdiff <= calc_max_pixel_threshold) ) - { + + for ( unsigned int x = lo_x; x <= hi_x; x++, pdiff++, ppoly++ ) { + if ( *ppoly && (*pdiff > min_pixel_threshold) && (*pdiff <= calc_max_pixel_threshold) ) { pixelsalarmed++; pixelsdifference += *pdiff; *pdiff = WHITE; - } - else - { + } else { *pdiff = BLACK; } } } - + /* Store the results */ *pixel_count = pixelsalarmed; *pixel_sum = pixelsdifference; - Debug( 7, "STORED"); + Debug( 7, "STORED"); } diff --git a/src/zm_zone.h b/src/zm_zone.h index 9b8a63747..bfd39e9aa 100644 --- a/src/zm_zone.h +++ b/src/zm_zone.h @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #ifndef ZM_ZONE_H @@ -131,14 +131,16 @@ public: inline const Image *AlarmImage() const { return( image ); } inline const Polygon &GetPolygon() const { return( polygon ); } inline bool Alarmed() const { return( alarmed ); } - inline void SetAlarm() { alarmed = true; } - inline void ClearAlarm() { alarmed = false; } + inline bool WasAlarmed() const { return( was_alarmed ); } + inline void SetAlarm() { was_alarmed = alarmed; alarmed = true; } + inline void ClearAlarm() { was_alarmed = alarmed; alarmed = false; } inline Coord GetAlarmCentre() const { return( alarm_centre ); } inline unsigned int Score() const { return( score ); } inline void ResetStats() { alarmed = false; + was_alarmed = false; pixel_diff = 0; alarm_pixels = 0; alarm_filter_pixels = 0; @@ -170,7 +172,6 @@ public: inline const Image *getPgImage() const { return( pg_image ); } inline const Range *getRanges() const { return( ranges ); } - }; #endif // ZM_ZONE_H diff --git a/src/zma.cpp b/src/zma.cpp index 03ffe1726..e59a0e65d 100644 --- a/src/zma.cpp +++ b/src/zma.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // /* @@ -133,7 +133,7 @@ int main( int argc, char *argv[] ) logInit( log_id_string ); - ssedetect(); + hwcaps_detect(); Monitor *monitor = Monitor::Load( id, true, Monitor::ANALYSIS ); @@ -141,11 +141,6 @@ int main( int argc, char *argv[] ) { Info( "In mode %d/%d, warming up", monitor->GetFunction(), monitor->Enabled() ); - if ( config.opt_frame_server ) - { - Event::OpenFrameSocket( monitor->Id() ); - } - zmSetDefaultHupHandler(); zmSetDefaultTermHandler(); zmSetDefaultDieHandler(); @@ -168,7 +163,7 @@ int main( int argc, char *argv[] ) if ( analysis_update_delay ) { cur_time = time( 0 ); - if ( ( cur_time - last_analysis_update_time ) > analysis_update_delay ) + if ( (unsigned int)( cur_time - last_analysis_update_time ) > analysis_update_delay ) { analysis_rate = monitor->GetAnalysisRate(); monitor->UpdateAdaptiveSkip(); @@ -198,6 +193,7 @@ int main( int argc, char *argv[] ) { fprintf( stderr, "Can't find monitor with id of %d\n", id ); } + Image::Deinitialise(); logTerm(); zmDbClose(); return( 0 ); diff --git a/src/zmc.cpp b/src/zmc.cpp index 42b6b7aff..5c4a01da2 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // /* @@ -27,7 +27,6 @@ zmc - The ZoneMinder Capture daemon zmc -d zmc --device - zmc -r -H -P -p zmc -f zmc --file zmc -m @@ -45,7 +44,6 @@ possible, this should run at more or less constant speed. =head1 OPTIONS -d, --device - For local cameras, device to access. e.g /dev/video0 etc - -r -H -P -p - For remote cameras -f, --file - For local images, jpg file to access. -m, --monitor_id - ID of the monitor to analyse -h, --help - Display usage information @@ -73,8 +71,7 @@ possible, this should run at more or less constant speed. #include "zm_signal.h" #include "zm_monitor.h" -void Usage() -{ +void Usage() { fprintf( stderr, "zmc -d or -r -H -P -p or -f or -m \n" ); fprintf( stderr, "Options:\n" ); @@ -83,7 +80,6 @@ void Usage() #else fprintf( stderr, " -d, --device : For local cameras, device to access. E.g /dev/video0 etc\n" ); #endif - fprintf( stderr, " -r -H -P -p : For remote cameras\n" ); fprintf( stderr, " -f, --file : For local images, jpg file to access.\n" ); fprintf( stderr, " -m, --monitor : For sources associated with a single monitor\n" ); fprintf( stderr, " -h, --help : This screen\n" ); @@ -91,8 +87,7 @@ void Usage() exit( 0 ); } -int main( int argc, char *argv[] ) -{ +int main( int argc, char *argv[] ) { self = argv[0]; srand( getpid() * time( 0 ) ); @@ -110,21 +105,19 @@ int main( int argc, char *argv[] ) {"protocol", 1, 0, 'r'}, {"host", 1, 0, 'H'}, {"port", 1, 0, 'P'}, - {"path", 1, 0, 'p'}, - {"file", 1, 0, 'f'}, + {"path", 1, 0, 'p'}, + {"file", 1, 0, 'f'}, {"monitor", 1, 0, 'm'}, {"help", 0, 0, 'h'}, {"version", 0, 0, 'v'}, {0, 0, 0, 0} }; - while (1) - { + while (1) { int option_index = 0; int c = getopt_long (argc, argv, "d:H:P:p:f:m:h:v", long_options, &option_index); - if (c == -1) - { + if (c == -1) { break; } @@ -161,8 +154,7 @@ int main( int argc, char *argv[] ) } } - if (optind < argc) - { + if (optind < argc) { fprintf( stderr, "Extraneous options, " ); while (optind < argc) printf ("%s ", argv[optind++]); @@ -171,37 +163,28 @@ int main( int argc, char *argv[] ) } int modes = ( device[0]?1:0 + host[0]?1:0 + file[0]?1:0 + (monitor_id>0?1:0) ); - if ( modes > 1 ) - { + if ( modes > 1 ) { fprintf( stderr, "Only one of device, host/port/path, file or monitor id allowed\n" ); Usage(); exit( 0 ); } - if ( modes < 1 ) - { + if ( modes < 1 ) { fprintf( stderr, "One of device, host/port/path, file or monitor id must be specified\n" ); Usage(); exit( 0 ); } char log_id_string[32] = ""; - if ( device[0] ) - { + if ( device[0] ) { const char *slash_ptr = strrchr( device, '/' ); snprintf( log_id_string, sizeof(log_id_string), "zmc_d%s", slash_ptr?slash_ptr+1:device ); - } - else if ( host[0] ) - { + } else if ( host[0] ) { snprintf( log_id_string, sizeof(log_id_string), "zmc_h%s", host ); - } - else if ( file[0] ) - { + } else if ( file[0] ) { const char *slash_ptr = strrchr( file, '/' ); snprintf( log_id_string, sizeof(log_id_string), "zmc_f%s", slash_ptr?slash_ptr+1:file ); - } - else - { + } else { snprintf( log_id_string, sizeof(log_id_string), "zmc_m%d", monitor_id ); } @@ -209,40 +192,31 @@ int main( int argc, char *argv[] ) logInit( log_id_string ); - ssedetect(); + hwcaps_detect(); Monitor **monitors = 0; int n_monitors = 0; #if ZM_HAS_V4L - if ( device[0] ) - { + if ( device[0] ) { n_monitors = Monitor::LoadLocalMonitors( device, monitors, Monitor::CAPTURE ); - } - else + } else #endif // ZM_HAS_V4L - if ( host[0] ) - { + if ( host[0] ) { if ( !port ) port = "80"; n_monitors = Monitor::LoadRemoteMonitors( protocol, host, port, path, monitors, Monitor::CAPTURE ); - } - else if ( file[0] ) - { + } else if ( file[0] ) { n_monitors = Monitor::LoadFileMonitors( file, monitors, Monitor::CAPTURE ); - } - else - { + } else { Monitor *monitor = Monitor::Load( monitor_id, true, Monitor::CAPTURE ); - if ( monitor ) - { + if ( monitor ) { monitors = new Monitor *[1]; monitors[0] = monitor; n_monitors = 1; } } - if ( !n_monitors ) - { + if ( !n_monitors ) { Error( "No monitors found" ); exit ( -1 ); } @@ -258,8 +232,7 @@ int main( int argc, char *argv[] ) sigaddset( &block_set, SIGUSR1 ); sigaddset( &block_set, SIGUSR2 ); - if ( monitors[0]->PrimeCapture() < 0 ) - { + if ( monitors[0]->PrimeCapture() < 0 ) { Error( "Failed to prime capture of initial monitor" ); exit( -1 ); } @@ -268,8 +241,7 @@ int main( int argc, char *argv[] ) long *alarm_capture_delays = new long[n_monitors]; long *next_delays = new long[n_monitors]; struct timeval * last_capture_times = new struct timeval[n_monitors]; - for ( int i = 0; i < n_monitors; i++ ) - { + for ( int i = 0; i < n_monitors; i++ ) { last_capture_times[i].tv_sec = last_capture_times[i].tv_usec = 0; capture_delays[i] = monitors[i]->GetCaptureDelay(); alarm_capture_delays[i] = monitors[i]->GetAlarmCaptureDelay(); @@ -278,18 +250,14 @@ int main( int argc, char *argv[] ) int result = 0; struct timeval now; struct DeltaTimeval delta_time; - while( !zm_terminate ) - { + while( !zm_terminate ) { sigprocmask( SIG_BLOCK, &block_set, 0 ); - for ( int i = 0; i < n_monitors; i++ ) - { + for ( int i = 0; i < n_monitors; i++ ) { long min_delay = MAXINT; gettimeofday( &now, NULL ); - for ( int j = 0; j < n_monitors; j++ ) - { - if ( last_capture_times[j].tv_sec ) - { + for ( int j = 0; j < n_monitors; j++ ) { + if ( last_capture_times[j].tv_sec ) { DELTA_TIMEVAL( delta_time, now, last_capture_times[j], DT_PREC_3 ); if ( monitors[i]->GetState() == Monitor::ALARM ) next_delays[j] = alarm_capture_delays[j]-delta_time.delta; @@ -297,58 +265,49 @@ int main( int argc, char *argv[] ) next_delays[j] = capture_delays[j]-delta_time.delta; if ( next_delays[j] < 0 ) next_delays[j] = 0; - } - else - { + } else { next_delays[j] = 0; } - if ( next_delays[j] <= min_delay ) - { + if ( next_delays[j] <= min_delay ) { min_delay = next_delays[j]; } } - if ( next_delays[i] <= min_delay || next_delays[i] <= 0 ) - { - if ( monitors[i]->PreCapture() < 0 ) - { + if ( next_delays[i] <= min_delay || next_delays[i] <= 0 ) { + if ( monitors[i]->PreCapture() < 0 ) { Error( "Failed to pre-capture monitor %d %d (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors ); zm_terminate = true; result = -1; break; } - if ( monitors[i]->Capture() < 0 ) - { + if ( monitors[i]->Capture() < 0 ) { Error( "Failed to capture image from monitor %d %s (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors ); zm_terminate = true; result = -1; break; } - if ( monitors[i]->PostCapture() < 0 ) - { + if ( monitors[i]->PostCapture() < 0 ) { Error( "Failed to post-capture monitor %d %s (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors ); zm_terminate = true; result = -1; break; } - if ( next_delays[i] > 0 ) - { + if ( next_delays[i] > 0 ) { gettimeofday( &now, NULL ); DELTA_TIMEVAL( delta_time, now, last_capture_times[i], DT_PREC_3 ); long sleep_time = next_delays[i]-delta_time.delta; - if ( sleep_time > 0 ) - { + if ( sleep_time > 0 ) { usleep( sleep_time*(DT_MAXGRAN/DT_PREC_3) ); } } gettimeofday( &(last_capture_times[i]), NULL ); - } - } + } // end if next_delay <= min_delay || next_delays[i] <= 0 ) + + } // end foreach n_monitors sigprocmask( SIG_UNBLOCK, &block_set, 0 ); - } - for ( int i = 0; i < n_monitors; i++ ) - { + } // end while ! zm_terminate + for ( int i = 0; i < n_monitors; i++ ) { delete monitors[i]; } delete [] monitors; @@ -357,6 +316,7 @@ int main( int argc, char *argv[] ) delete [] next_delays; delete [] last_capture_times; + Image::Deinitialise(); logTerm(); zmDbClose(); diff --git a/src/zmf.cpp b/src/zmf.cpp deleted file mode 100644 index a739927f1..000000000 --- a/src/zmf.cpp +++ /dev/null @@ -1,350 +0,0 @@ -// -// ZoneMinder Image File Writer Implementation, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -/* - -=head1 NAME - -zmf - The ZoneMinder Frame daemon - -=head1 SYNOPSIS - - zmf -m - zmf --monitor - zmf -h - zmf --help - zmf -v - zmf --version - -=head1 DESCRIPTION - -This is an optional daemon that can run in concert with the Analysis daemon and -whose function it is to actually write captured frames to disk. This frees up -the Analysis daemon to do more analysis (!) and so keep up with the Capture -daemon better. If it isn't running or dies then the Analysis daemon just writes -them itself. - -=head1 OPTIONS - - -m, --monitor_id - ID of the monitor to use - -h, --help - Display usage information - -v, --version - Print the installed version of ZoneMinder - -=cut - -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "zm.h" -#include "zm_db.h" -#include "zm_signal.h" -#include "zm_monitor.h" - -#include "zmf.h" - -int OpenSocket( int monitor_id ) -{ - int sd = socket( AF_UNIX, SOCK_STREAM, 0); - if ( sd < 0 ) - { - Error( "Can't create socket: %s", strerror(errno) ); - return( -1 ); - } - - char sock_path[PATH_MAX] = ""; - snprintf( sock_path, sizeof(sock_path), "%s/zmf-%d.sock", config.path_socks, monitor_id ); - if ( unlink( sock_path ) < 0 ) - { - Warning( "Can't unlink '%s': %s", sock_path, strerror(errno) ); - } - - struct sockaddr_un addr; - - strncpy( addr.sun_path, sock_path, sizeof(addr.sun_path) ); - addr.sun_family = AF_UNIX; - - if ( bind( sd, (struct sockaddr *)&addr, strlen(addr.sun_path)+sizeof(addr.sun_family)) < 0 ) - { - Error( "Can't bind: %s", strerror(errno) ); - exit( -1 ); - } - - if ( listen( sd, SOMAXCONN ) < 0 ) - { - Error( "Can't listen: %s", strerror(errno) ); - return( -1 ); - } - - struct sockaddr_un rem_addr; - socklen_t rem_addr_len = sizeof(rem_addr); - int new_sd = -1; - if ( (new_sd = accept( sd, (struct sockaddr *)&rem_addr, &rem_addr_len )) < 0 ) - { - Error( "Can't accept: %s", strerror(errno) ); - exit( -1 ); - } - close( sd ); - - sd = new_sd; - - Info( "Frame server socket open, awaiting images" ); - return( sd ); -} - -int ReopenSocket( int &sd, int monitor_id ) -{ - close( sd ); - return( sd = OpenSocket( monitor_id ) ); -} - -void Usage() -{ - fprintf( stderr, "zmf -m \n" ); - fprintf( stderr, "Options:\n" ); - fprintf( stderr, " -m, --monitor : Specify which monitor to use\n" ); - fprintf( stderr, " -h, --help : This screen\n" ); - fprintf( stderr, " -v, --version : Report the installed version of ZoneMinder\n" ); - exit( 0 ); -} - -int main( int argc, char *argv[] ) -{ - self = argv[0]; - - srand( getpid() * time( 0 ) ); - - int id = -1; - - static struct option long_options[] = { - {"monitor", 1, 0, 'm'}, - {"help", 0, 0, 'h'}, - {"version", 0, 0, 'v'}, - {0, 0, 0, 0} - }; - - while (1) - { - int option_index = 0; - - int c = getopt_long (argc, argv, "m:h:v", long_options, &option_index); - if (c == -1) - { - break; - } - - switch (c) - { - case 'm': - id = atoi(optarg); - break; - case 'h': - case '?': - Usage(); - break; - case 'v': - std::cout << ZM_VERSION << "\n"; - exit(0); - default: - //fprintf( stderr, "?? getopt returned character code 0%o ??\n", c ); - break; - } - } - - if (optind < argc) - { - fprintf( stderr, "Extraneous options, " ); - while (optind < argc) - printf ("%s ", argv[optind++]); - printf ("\n"); - Usage(); - } - - if ( id < 0 ) - { - fprintf( stderr, "Bogus monitor %d\n", id ); - Usage(); - exit( 0 ); - } - - char log_id_string[16]; - snprintf( log_id_string, sizeof(log_id_string), "m%d", id ); - - zmLoadConfig(); - - logInit( "zmf" ); - - ssedetect(); - - Monitor *monitor = Monitor::Load( id, false, Monitor::QUERY ); - - if ( !monitor ) - { - fprintf( stderr, "Can't find monitor with id of %d\n", id ); - exit( -1 ); - } - - char capt_path[PATH_MAX]; - char anal_path[PATH_MAX]; - snprintf( capt_path, sizeof(capt_path), "%s/%d/%%s/%%0%dd-capture.jpg", config.dir_events, monitor->Id(), config.event_image_digits ); - snprintf( anal_path, sizeof(anal_path), "%s/%d/%%s/%%0%dd-analyse.jpg", config.dir_events, monitor->Id(), config.event_image_digits ); - zmSetDefaultTermHandler(); - zmSetDefaultDieHandler(); - - sigset_t block_set; - sigemptyset( &block_set ); - - int sd = OpenSocket( monitor->Id() ); - - FrameHeader frame_header = { 0, 0, false, 0 }; - //unsigned char *image_data = 0; - - fd_set rfds; - - struct timeval timeout; - timeout.tv_sec = 1; - timeout.tv_usec = 0; - while( 1 ) - { - struct timeval temp_timeout = timeout; - - FD_ZERO(&rfds); - FD_SET(sd, &rfds); - int n_found = select( sd+1, &rfds, NULL, NULL, &temp_timeout ); - if( n_found == 0 ) - { - Debug( 1, "Select timed out" ); - continue; - } - else if ( n_found < 0) - { - Error( "Select error: %s", strerror(errno) ); - ReopenSocket( sd, monitor->Id() ); - continue; - } - - sigprocmask( SIG_BLOCK, &block_set, 0 ); - - int n_bytes = read( sd, &frame_header, sizeof(frame_header) ); - if ( n_bytes != sizeof(frame_header) ) - { - if ( n_bytes < 0 ) - { - Error( "Can't read frame header: %s", strerror(errno) ); - } - else if ( n_bytes > 0 ) - { - Error( "Incomplete read of frame header, %d bytes only", n_bytes ); - } - else - { - Warning( "Socket closed at remote end" ); - } - ReopenSocket( sd, monitor->Id() ); - continue; - } - Debug( 1, "Read frame header, expecting %ld bytes of image", frame_header.image_length ); - static unsigned char image_data[ZM_MAX_IMAGE_SIZE]; - - // Read for pipe and loop until bytes expected have been read or an error occurs - int bytes_read = 0; - do - { - n_bytes = read( sd, image_data+bytes_read, frame_header.image_length-bytes_read ); - if (n_bytes < 0) break; // break on error - if (n_bytes < (int)frame_header.image_length) - { - // print some informational messages - if (bytes_read == 0) - { - Debug(4,"Image read : Short read %d bytes of %d expected bytes",n_bytes,frame_header.image_length); - } - else if (bytes_read+n_bytes == (int)frame_header.image_length) - { - Debug(5,"Image read : Read rest of short read: %d bytes read total of %d bytes",n_bytes,frame_header.image_length); - } - else - { - Debug(6,"Image read : continuing, read %d bytes (%d so far)", n_bytes, bytes_read+n_bytes); - } - } - bytes_read+= n_bytes; - } while (n_bytes>0 && (bytes_read < (ssize_t)frame_header.image_length) ); - - // Print errors if there was a problem - if ( n_bytes < 1 ) - { - Error( "Only read %d bytes of %d\n", bytes_read, frame_header.image_length); - if ( n_bytes < 0 ) - { - Error( "Can't read frame image data: %s", strerror(errno) ); - } - else - { - Warning( "Socket closed at remote end" ); - } - ReopenSocket( sd, monitor->Id() ); - continue; - } - - static char subpath[PATH_MAX] = ""; - if ( config.use_deep_storage ) - { - struct tm *time = localtime( &frame_header.event_time ); - snprintf( subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec ); - } - else - { - snprintf( subpath, sizeof(subpath), "%ld", frame_header.event_id ); - } - - static char path[PATH_MAX] = ""; - snprintf( path, sizeof(path), frame_header.alarm_frame?anal_path:capt_path, subpath, frame_header.frame_id ); - Debug( 1, "Got image, writing to %s", path ); - - FILE *fd = 0; - if ( (fd = fopen( path, "w" )) < 0 ) - { - Error( "Can't fopen '%s': %s", path, strerror(errno) ); - exit( -1 ); - } - if ( 0 == fwrite( image_data, frame_header.image_length, 1, fd ) ) - { - Error( "Can't fwrite image data: %s", strerror(errno) ); - exit( -1 ); - } - fclose( fd ); - - sigprocmask( SIG_UNBLOCK, &block_set, 0 ); - } - logTerm(); - zmDbClose(); -} diff --git a/src/zmf.h b/src/zmf.h deleted file mode 100644 index 211577309..000000000 --- a/src/zmf.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ZoneMinder Image File Write Class Interface, $Date$, $Revision$ -// Copyright (C) 2001-2008 Philip Coombes -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// - -#ifndef ZMFILE_H -#define ZMFILE_H - -struct FrameHeader -{ - unsigned long event_id; - time_t event_time; - unsigned long frame_id; - bool alarm_frame; - unsigned long image_length; -}; - -#endif // ZMFILE_H diff --git a/src/zms.cpp b/src/zms.cpp index e40fcdf95..67f0e3cab 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // #include @@ -69,8 +69,8 @@ int main( int argc, const char *argv[] ) unsigned int bitrate = 100000; unsigned int ttl = 0; EventStream::StreamMode replay = EventStream::MODE_SINGLE; - char username[64] = ""; - char password[64] = ""; + std::string username; + std::string password; char auth[64] = ""; unsigned int connkey = 0; unsigned int playback_buffer = 0; @@ -91,7 +91,7 @@ int main( int argc, const char *argv[] ) logInit( "zms" ); - ssedetect(); + hwcaps_detect(); zmSetDefaultTermHandler(); zmSetDefaultDieHandler(); @@ -164,7 +164,7 @@ int main( int argc, const char *argv[] ) { if ( !strcmp( name, "user" ) ) { - strncpy( username, value, sizeof(username) ); + username = value; } } else @@ -180,11 +180,11 @@ int main( int argc, const char *argv[] ) { if ( !strcmp( name, "user" ) ) { - strncpy( username, value, sizeof(username) ); + username = UriDecode( value ); } if ( !strcmp( name, "pass" ) ) { - strncpy( password, value, sizeof(password) ); + password = UriDecode( value ); } } } @@ -198,9 +198,9 @@ int main( int argc, const char *argv[] ) if ( strcmp( config.auth_relay, "none" ) == 0 ) { - if ( *username ) + if ( username.length() ) { - user = zmLoadUser( username ); + user = zmLoadUser( username.c_str() ); } } else @@ -214,9 +214,9 @@ int main( int argc, const char *argv[] ) } //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) { - if ( *username && *password ) + if ( username.length() && password.length() ) { - user = zmLoadUser( username, password ); + user = zmLoadUser( username.c_str(), password.c_str() ); } } } diff --git a/src/zmstreamer.cpp b/src/zmstreamer.cpp deleted file mode 100644 index b33c61246..000000000 --- a/src/zmstreamer.cpp +++ /dev/null @@ -1,252 +0,0 @@ -// -// ZoneMinder Streamer, $Date: 2010-10-14 23:21:00 +0200 (Thu, 14 Oct 2010) $ -// Copyright (C) 2001-2010 Philip Coombes, Chris Kistner -// -// This program is based on revision 3143 of -// http://svn.zoneminder.com/svn/zm/trunk/src/zms.cpp -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -/* - -=head1 NAME - -zmstreamer - eyeZM video streamer - -=head1 SYNOPSIS - - zmstreamer -e - zmstreamer -o - zmstreamer -u - zmstreamer -f - zmstreamer -s - zmstreamer -b - zmstreamer -m - zmstreamer -d - zmstreamer -i - zmstreamer -? - zmstreamer -h - zmstreamer -v - -=head1 DESCRIPTION - -*DEPRECIATED* The xml skin and all files associated with the xml skin are now -depreciated. Please use the ZoneMinder API instead. - -This binary works in conjunction with the XML skin to stream video to iPhones -running the eyeZm app. - -=head1 OPTIONS - - -e - Specify output mode: mpeg/jpg/zip/single/raw. - -o - Specify output format. - -u - Specify buffer size in ms. - -f - Specify maximum framerate. - -s - Specify scale. - -b - Specify bitrate. - -m - Specify monitor id. - -d - 0 = off, 1 = no streaming, 2 = with streaming. - -i, -?, -h - Display usage information - -v - Print the installed version of ZoneMinder - -=cut - -*/ - -#include -#include - -#include -#include - -#include "zm.h" -#include "zm_db.h" -#include "zm_user.h" -#include "zm_signal.h" -#include "zm_monitor.h" -#include "zm_stream.h" - -// Possible command-line options -#define OPTIONS "e:o:u:f:s:b:m:d:i:?:h:v" - -// Default ZMS values -#define ZMS_DEFAULT_DEBUG 0 -#define ZMS_DEFAULT_ID 1 -#define ZMS_DEFAULT_BITRATE 100000 -#define ZMS_DEFAULT_SCALE 100 -#define ZMS_DEFAULT_MODE "mpeg" -#define ZMS_DEFAULT_FORMAT "asf" -#define ZMS_DEFAULT_FPS 25.0 -#define ZMS_DEFAULT_BUFFER 1000 - -int main(int argc, char** argv) { - self = argv[0]; - // Set initial values to the default values - int debug = ZMS_DEFAULT_DEBUG; - int id = ZMS_DEFAULT_ID; - int bitrate = ZMS_DEFAULT_BITRATE; - int scale = ZMS_DEFAULT_SCALE; - char mode[32]; - sprintf(mode, "%s", ZMS_DEFAULT_MODE); - char format[32]; - sprintf(format, "%s", ZMS_DEFAULT_FORMAT); - double maxfps = ZMS_DEFAULT_FPS; - int buffer = ZMS_DEFAULT_BUFFER; - - // Parse command-line options - int arg; - while ((arg = getopt(argc, argv, OPTIONS)) != -1) { - switch (arg) { - case 'e': - sprintf(mode, "%s", optarg); - break; - case 'o': - sprintf(format, "%s", optarg); - break; - case 'u': - buffer = atoi(optarg); - break; - case 'f': - maxfps = atof(optarg); - break; - case 's': - scale = atoi(optarg); - break; - case 'b': - bitrate = atoi(optarg); - break; - case 'm': - id = atoi(optarg); - break; - case 'd': - debug = atoi(optarg); - break; - case 'h': - case 'i': - case '?': - printf("-e : Specify output mode: mpeg/jpg/zip/single/raw. Default = %s\n", ZMS_DEFAULT_MODE); - printf("-o : Specify output format. Default = %s\n", ZMS_DEFAULT_FORMAT); - printf("-u : Specify buffer size in ms. Default = %d\n", ZMS_DEFAULT_BUFFER); - printf("-f : Specify maximum framerate. Default = %lf\n", ZMS_DEFAULT_FPS); - printf("-s : Specify scale. Default = %d\n", ZMS_DEFAULT_SCALE); - printf("-b : Specify bitrate. Default = %d\n", ZMS_DEFAULT_BITRATE); - printf("-m : Specify monitor id. Default = %d\n", ZMS_DEFAULT_ID); - printf("-d : 0 = off, 1 = no streaming, 2 = with streaming. Default = 0\n"); - printf("-i or -? or -h: This information\n"); - printf("-v : This installed version of ZoneMinder\n"); - return EXIT_SUCCESS; - case 'v': - std::cout << ZM_VERSION << "\n"; - exit(0); - } - } - - // Set stream type - StreamBase::StreamType streamtype; - if (!strcasecmp("raw", mode)) - streamtype = MonitorStream::STREAM_RAW; - else if (!strcasecmp("mpeg", mode)) - streamtype = MonitorStream::STREAM_MPEG; - else if (!strcasecmp("jpg", mode)) - streamtype = MonitorStream::STREAM_JPEG; - else if (!strcasecmp("single", mode)) - streamtype = MonitorStream::STREAM_SINGLE; - else if (!strcasecmp("zip", mode)) - streamtype = MonitorStream::STREAM_ZIP; - else - streamtype = MonitorStream::STREAM_MPEG; - - if (debug) { - // Show stream parameters - printf("Stream parameters:\n"); - switch (streamtype) { - case MonitorStream::STREAM_MPEG: - printf("Output mode (-e) = %s\n", "mpeg"); - printf("Output format (-o) = %s\n", format); - break; - default: - printf("Output mode (-e) = %s\n", mode); - } - printf("Buffer size (-u) = %d ms\n", buffer); - printf("Maximum FPS (-f) = %lf FPS\n", maxfps); - printf("Scale (-s) = %d%%\n", scale); - printf("Bitrate (-b) = %d bps\n", bitrate); - printf("Monitor Id (-m) = %d\n", id); - } - - if (debug) { - // Set ZM debugger to print to stdout - printf("Setting up ZoneMinder debugger to print to stdout..."); - setenv("ZM_DBG_PRINT", "1", 1); - printf("Done.\n"); - } - - // Loading ZM configurations - printf("Loading ZoneMinder configurations..."); - zmLoadConfig(); - printf("Done.\n"); - - logInit("zmstreamer"); - - ssedetect(); - - // Setting stream parameters - MonitorStream stream; - stream.setStreamScale(scale); // default = 100 (scale) - stream.setStreamReplayRate(100); // default = 100 (rate) - stream.setStreamMaxFPS(maxfps); // default = 10 (maxfps) - if (debug) stream.setStreamTTL(1); - else stream.setStreamTTL(0); // default = 0 (ttl) - stream.setStreamQueue(0); // default = 0 (connkey) - stream.setStreamBuffer(buffer); // default = 0 (buffer) - stream.setStreamStart(id); // default = 0 (monitor_id) - stream.setStreamType(streamtype); - if (streamtype == MonitorStream::STREAM_MPEG) { -#if HAVE_LIBAVCODEC - if (debug) printf("HAVE_LIBAVCODEC is set\n"); - stream.setStreamFormat(format); // default = "" (format) - stream.setStreamBitrate(bitrate); // default = 100000 (bitrate) -#else - fprintf(stderr, "MPEG streaming is disabled.\nYou should configure with the --with-ffmpeg option and rebuild to use this functionality.\n"); - logTerm(); - zmDbClose(); - return EXIT_FAILURE; -#endif - } - - if (debug != 1) { - if (debug) printf("Running stream..."); - - // Output headers - fprintf(stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION); - time_t now = time(0); - char date_string[64]; - strftime(date_string, sizeof (date_string) - 1, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now)); - fprintf(stdout, "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"); - fprintf(stdout, "Last-Modified: %s\r\n", date_string); - fprintf(stdout, "Cache-Control: no-store, no-cache, must-revalidate\r\n"); - fprintf(stdout, "Cache-Control: post-check=0, pre-check=0\r\n"); - fprintf(stdout, "Pragma: no-cache\r\n"); - - // Run stream - stream.runStream(); - } - if (debug) printf("Done.\n"); - - logTerm(); - zmDbClose(); - - return (EXIT_SUCCESS); -} diff --git a/src/zmu.cpp b/src/zmu.cpp index 0c5a5a41e..c3a795ad4 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -14,7 +14,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // /* @@ -95,8 +95,7 @@ Options for use with monitors: #include "zm_monitor.h" #include "zm_local_camera.h" -void Usage( int status=-1 ) -{ +void Usage( int status=-1 ) { fprintf( stderr, "zmu <-d device_path> [-v] [function] [-U -P]\n" ); fprintf( stderr, "zmu <-m monitor_id> [-v] [function] [-U -P]\n" ); fprintf( stderr, "General options:\n" ); @@ -167,48 +166,38 @@ typedef enum { ZMU_LIST = 0x10000000, } Function; -bool ValidateAccess( User *user, int mon_id, int function ) -{ +bool ValidateAccess( User *user, int mon_id, int function ) { bool allowed = true; - if ( function & (ZMU_STATE|ZMU_IMAGE|ZMU_TIME|ZMU_READ_IDX|ZMU_WRITE_IDX|ZMU_FPS) ) - { + if ( function & (ZMU_STATE|ZMU_IMAGE|ZMU_TIME|ZMU_READ_IDX|ZMU_WRITE_IDX|ZMU_FPS) ) { if ( user->getStream() < User::PERM_VIEW ) allowed = false; } - if ( function & ZMU_EVENT ) - { + if ( function & ZMU_EVENT ) { if ( user->getEvents() < User::PERM_VIEW ) allowed = false; } - if ( function & (ZMU_ZONES|ZMU_QUERY|ZMU_LIST) ) - { + if ( function & (ZMU_ZONES|ZMU_QUERY|ZMU_LIST) ) { if ( user->getMonitors() < User::PERM_VIEW ) allowed = false; } - if ( function & (ZMU_ALARM|ZMU_NOALARM|ZMU_CANCEL|ZMU_RELOAD|ZMU_ENABLE|ZMU_DISABLE|ZMU_SUSPEND|ZMU_RESUME|ZMU_BRIGHTNESS|ZMU_CONTRAST|ZMU_HUE|ZMU_COLOUR) ) - { + if ( function & (ZMU_ALARM|ZMU_NOALARM|ZMU_CANCEL|ZMU_RELOAD|ZMU_ENABLE|ZMU_DISABLE|ZMU_SUSPEND|ZMU_RESUME|ZMU_BRIGHTNESS|ZMU_CONTRAST|ZMU_HUE|ZMU_COLOUR) ) { if ( user->getMonitors() < User::PERM_EDIT ) allowed = false; } - if ( mon_id > 0 ) - { - if ( !user->canAccess( mon_id ) ) - { + if ( mon_id > 0 ) { + if ( !user->canAccess( mon_id ) ) { allowed = false; } } - if ( !allowed ) - { + if ( !allowed ) { fprintf( stderr, "Error, insufficient privileges for requested action\n" ); exit( -1 ); } return( allowed ); } -int main( int argc, char *argv[] ) -{ - if ( access(ZM_CONFIG, R_OK) != 0 ) - { +int main( int argc, char *argv[] ) { + if ( access(ZM_CONFIG, R_OK) != 0 ) { fprintf( stderr, "Can't open %s: %s\n", ZM_CONFIG, strerror(errno) ); exit( -1 ); } @@ -274,18 +263,15 @@ int main( int argc, char *argv[] ) int v4lVersion = 1; #endif // ZM_HAS_V4L2/1 #endif // ZM_HAS_V4L - while (1) - { + while (1) { int option_index = 0; int c = getopt_long (argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::U:P:A:V:", long_options, &option_index); - if (c == -1) - { + if (c == -1) { break; } - switch (c) - { + switch (c) { case 'd': if ( optarg ) device = optarg; @@ -405,8 +391,7 @@ int main( int argc, char *argv[] ) } } - if (optind < argc) - { + if (optind < argc) { fprintf( stderr, "Extraneous options, " ); while (optind < argc) fprintf( stderr, "%s ", argv[optind++]); @@ -414,13 +399,11 @@ int main( int argc, char *argv[] ) Usage(); } - if ( device && !(function&ZMU_QUERY) ) - { + if ( device && !(function&ZMU_QUERY) ) { fprintf( stderr, "Error, -d option cannot be used with this option\n" ); Usage(); } - if ( scale != -1 && !(function&ZMU_IMAGE) ) - { + if ( scale != -1 && !(function&ZMU_IMAGE) ) { fprintf( stderr, "Error, -S option cannot be used with this option\n" ); Usage(); } @@ -435,46 +418,36 @@ int main( int argc, char *argv[] ) User *user = 0; - if ( config.opt_use_auth ) - { - if ( strcmp( config.auth_relay, "none" ) == 0 ) - { - if ( !username ) - { + if ( config.opt_use_auth ) { + if ( strcmp( config.auth_relay, "none" ) == 0 ) { + if ( !username ) { fprintf( stderr, "Error, username must be supplied\n" ); exit( -1 ); } - if ( username ) - { + if ( username ) { user = zmLoadUser( username ); } - } - else - { - if ( !(username && password) && !auth ) - { + } else { + if ( !(username && password) && !auth ) { fprintf( stderr, "Error, username and password or auth string must be supplied\n" ); exit( -1 ); } //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) { - if ( auth ) - { + if ( auth ) { user = zmLoadAuthUser( auth, false ); } } //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) { - if ( username && password ) - { + if ( username && password ) { user = zmLoadUser( username, password ); } } } - if ( !user ) - { + if ( !user ) { fprintf( stderr, "Error, unable to authenticate user\n" ); exit( -1 ); } @@ -482,13 +455,10 @@ int main( int argc, char *argv[] ) } - if ( mon_id > 0 ) - { + if ( mon_id > 0 ) { Monitor *monitor = Monitor::Load( mon_id, function&(ZMU_QUERY|ZMU_ZONES), Monitor::QUERY ); - if ( monitor ) - { - if ( verbose ) - { + if ( monitor ) { + if ( verbose ) { printf( "Monitor %d(%s)\n", monitor->Id(), monitor->Name() ); } if ( ! monitor->connect() ) { @@ -498,23 +468,19 @@ int main( int argc, char *argv[] ) char separator = ' '; bool have_output = false; - if ( function & ZMU_STATE ) - { + if ( function & ZMU_STATE ) { Monitor::State state = monitor->GetState(); if ( verbose ) printf( "Current state: %s\n", state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle") ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%d", state ); have_output = true; } } - if ( function & ZMU_TIME ) - { + if ( function & ZMU_TIME ) { struct timeval timestamp = monitor->GetTimestamp( image_idx ); - if ( verbose ) - { + if ( verbose ) { char timestamp_str[64] = "None"; if ( timestamp.tv_sec ) strftime( timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime( ×tamp.tv_sec ) ); @@ -522,62 +488,50 @@ int main( int argc, char *argv[] ) printf( "Time of last image capture: %s.%02ld\n", timestamp_str, timestamp.tv_usec/10000 ); else printf( "Time of image %d capture: %s.%02ld\n", image_idx, timestamp_str, timestamp.tv_usec/10000 ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); printf( "%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000 ); have_output = true; } } - if ( function & ZMU_READ_IDX ) - { + if ( function & ZMU_READ_IDX ) { if ( verbose ) printf( "Last read index: %d\n", monitor->GetLastReadIndex() ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%d", monitor->GetLastReadIndex() ); have_output = true; } } - if ( function & ZMU_WRITE_IDX ) - { + if ( function & ZMU_WRITE_IDX ) { if ( verbose ) printf( "Last write index: %d\n", monitor->GetLastWriteIndex() ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%d", monitor->GetLastWriteIndex() ); have_output = true; } } - if ( function & ZMU_EVENT ) - { + if ( function & ZMU_EVENT ) { if ( verbose ) printf( "Last event id: %d\n", monitor->GetLastEvent() ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%d", monitor->GetLastEvent() ); have_output = true; } } - if ( function & ZMU_FPS ) - { + if ( function & ZMU_FPS ) { if ( verbose ) printf( "Current capture rate: %.2f frames per second\n", monitor->GetFPS() ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%.2f", monitor->GetFPS() ); have_output = true; } } - if ( function & ZMU_IMAGE ) - { - if ( verbose ) - { + if ( function & ZMU_IMAGE ) { + if ( verbose ) { if ( image_idx == -1 ) printf( "Dumping last image captured to Monitor%d.jpg", monitor->Id() ); else @@ -588,77 +542,63 @@ int main( int argc, char *argv[] ) } monitor->GetImage( image_idx, scale>0?scale:100 ); } - if ( function & ZMU_ZONES ) - { + if ( function & ZMU_ZONES ) { if ( verbose ) printf( "Dumping zone image to Zones%d.jpg\n", monitor->Id() ); monitor->DumpZoneImage( zoneString ); } - if ( function & ZMU_ALARM ) - { + if ( function & ZMU_ALARM ) { if ( verbose ) printf( "Forcing alarm on\n" ); monitor->ForceAlarmOn( config.forced_alarm_score, "Forced Web" ); } - if ( function & ZMU_NOALARM ) - { + if ( function & ZMU_NOALARM ) { if ( verbose ) printf( "Forcing alarm off\n" ); monitor->ForceAlarmOff(); } - if ( function & ZMU_CANCEL ) - { + if ( function & ZMU_CANCEL ) { if ( verbose ) printf( "Cancelling forced alarm on/off\n" ); monitor->CancelForced(); } - if ( function & ZMU_RELOAD ) - { + if ( function & ZMU_RELOAD ) { if ( verbose ) printf( "Reloading monitor settings\n" ); monitor->actionReload(); } - if ( function & ZMU_ENABLE ) - { + if ( function & ZMU_ENABLE ) { if ( verbose ) printf( "Enabling event generation\n" ); monitor->actionEnable(); } - if ( function & ZMU_DISABLE ) - { + if ( function & ZMU_DISABLE ) { if ( verbose ) printf( "Disabling event generation\n" ); monitor->actionDisable(); } - if ( function & ZMU_SUSPEND ) - { + if ( function & ZMU_SUSPEND ) { if ( verbose ) printf( "Suspending event generation\n" ); monitor->actionSuspend(); } - if ( function & ZMU_RESUME ) - { + if ( function & ZMU_RESUME ) { if ( verbose ) printf( "Resuming event generation\n" ); monitor->actionResume(); } - if ( function & ZMU_QUERY ) - { + if ( function & ZMU_QUERY ) { char monString[16382] = ""; monitor->DumpSettings( monString, verbose ); printf( "%s\n", monString ); } - if ( function & ZMU_BRIGHTNESS ) - { - if ( verbose ) - { + if ( function & ZMU_BRIGHTNESS ) { + if ( verbose ) { if ( brightness >= 0 ) printf( "New brightness: %d\n", monitor->actionBrightness( brightness ) ); else printf( "Current brightness: %d\n", monitor->actionBrightness() ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); if ( brightness >= 0 ) printf( "%d", monitor->actionBrightness( brightness ) ); @@ -667,17 +607,13 @@ int main( int argc, char *argv[] ) have_output = true; } } - if ( function & ZMU_CONTRAST ) - { - if ( verbose ) - { + if ( function & ZMU_CONTRAST ) { + if ( verbose ) { if ( contrast >= 0 ) printf( "New brightness: %d\n", monitor->actionContrast( contrast ) ); else printf( "Current contrast: %d\n", monitor->actionContrast() ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); if ( contrast >= 0 ) printf( "%d", monitor->actionContrast( contrast ) ); @@ -686,17 +622,13 @@ int main( int argc, char *argv[] ) have_output = true; } } - if ( function & ZMU_HUE ) - { - if ( verbose ) - { + if ( function & ZMU_HUE ) { + if ( verbose ) { if ( hue >= 0 ) printf( "New hue: %d\n", monitor->actionHue( hue ) ); else printf( "Current hue: %d\n", monitor->actionHue() ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); if ( hue >= 0 ) printf( "%d", monitor->actionHue( hue ) ); @@ -705,17 +637,13 @@ int main( int argc, char *argv[] ) have_output = true; } } - if ( function & ZMU_COLOUR ) - { - if ( verbose ) - { + if ( function & ZMU_COLOUR ) { + if ( verbose ) { if ( colour >= 0 ) printf( "New colour: %d\n", monitor->actionColour( colour ) ); else printf( "Current colour: %d\n", monitor->actionColour() ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); if ( colour >= 0 ) printf( "%d", monitor->actionColour( colour ) ); @@ -724,26 +652,19 @@ int main( int argc, char *argv[] ) have_output = true; } } - if ( have_output ) - { + if ( have_output ) { printf( "\n" ); } - if ( !function ) - { + if ( !function ) { Usage(); } delete monitor; - } - else - { + } else { fprintf( stderr, "Error, invalid monitor id %d\n", mon_id ); exit( -1 ); } - } - else - { - if ( function & ZMU_QUERY ) - { + } else { + if ( function & ZMU_QUERY ) { #if ZM_HAS_V4L char vidString[0x10000] = ""; bool ok = LocalCamera::GetCurrentSettings( device, vidString, v4lVersion, verbose ); @@ -755,24 +676,20 @@ int main( int argc, char *argv[] ) #endif // ZM_HAS_V4L } - if ( function & ZMU_LIST ) - { + if ( function & ZMU_LIST ) { std::string sql = "select Id, Function+0 from Monitors"; - if ( !verbose ) - { + if ( !verbose ) { sql += "where Function != 'None'"; } sql += " order by Id asc"; - if ( mysql_query( &dbconn, sql.c_str() ) ) - { + if ( mysql_query( &dbconn, sql.c_str() ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -780,17 +697,13 @@ int main( int argc, char *argv[] ) Debug( 1, "Got %d monitors", n_monitors ); printf( "%4s%5s%6s%9s%14s%6s%6s%8s%8s\n", "Id", "Func", "State", "TrgState", "LastImgTim", "RdIdx", "WrIdx", "LastEvt", "FrmRate" ); - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int mon_id = atoi(dbrow[0]); int function = atoi(dbrow[1]); - if ( !user || user->canAccess( mon_id ) ) - { - if ( function > 1 ) - { + if ( !user || user->canAccess( mon_id ) ) { + if ( function > 1 ) { Monitor *monitor = Monitor::Load( mon_id, false, Monitor::QUERY ); - if ( monitor && monitor->connect() ) - { + if ( monitor && monitor->connect() ) { struct timeval tv = monitor->GetTimestamp(); printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", monitor->Id(), @@ -805,9 +718,7 @@ int main( int argc, char *argv[] ) ); delete monitor; } - } - else - { + } else { struct timeval tv = { 0, 0 }; printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", mon_id, diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 49c91acfc..a825d0b62 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -8,88 +8,299 @@ exit; fi +for i in "$@" +do +case $i in + -b=*|--branch=*) + BRANCH="${i#*=}" + shift # past argument=value + ;; + -d=*|--distro=*) + DISTRO="${i#*=}" + shift # past argument=value + ;; + -i=*|--interactive=*) + INTERACTIVE="${i#*=}" + shift # past argument=value + ;; + -r=*|--release=*) + RELEASE="${i#*=}" + shift + ;; + -s=*|--snapshot=*) + SNAPSHOT="${i#*=}" + shift # past argument=value + ;; + -t=*|--type=*) + TYPE="${i#*=}" + shift # past argument=value + ;; + -u=*|--urgency=*) + URGENCY="${i#*=}" + shift # past argument=value + ;; + -f=*|--fork=*) + GITHUB_FORK="${i#*=}" + shift # past argument=value + ;; + -v=*|--version=*) + PACKAGE_VERSION="${i#*=}" + shift + ;; + --default) + DEFAULT=YES + shift # past argument with no value + ;; + *) + # unknown option + read -p "Unknown option $i, continue? (Y|n)" + [[ $REPLY == [yY] ]] && { echo "continuing..."; } || exit 1; + ;; +esac +done DATE=`date -R` -DISTRO=$1 -SNAPSHOT=$2 -if [ "$SNAPSHOT" == "stable" ]; then -SNAPSHOT=""; -fi; - - -TYPE=$3 if [ "$TYPE" == "" ]; then -TYPE="source"; + echo "Defaulting to source build" + TYPE="source"; +else + echo "Doing $TYPE build" fi; -BRANCH=$4 - -if [ ! -d 'zoneminder_release' ]; then - git clone https://github.com/ZoneMinder/ZoneMinder.git zoneminder_release -fi; -if [ "$BRANCH" != "" ]; then - cd zoneminder_release - if [ "$BRANCH" == "stable" ]; then - BRANCH=$(git describe --tags $(git rev-list --tags --max-count=1)); - echo "Latest stable branch is $BRANCH"; - - fi - git checkout $BRANCH - cd ../ -fi; -VERSION=`cat zoneminder_release/version` -if [ $VERSION == "" ]; then - exit 1; -fi; -echo "Doing $TYPE release zoneminder_$VERSION-$DISTRO-$SNAPSHOT"; -mv zoneminder_release zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig -cd zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig -git submodule init -git submodule update --init --recursive -if [ $DISTRO == "trusty" ]; then -ln -sf distros/ubuntu1204 debian +if [ "$DISTRO" == "" ]; then + DISTRO=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; + echo "Defaulting to $DISTRO for distribution"; else -ln -sf distros/ubuntu1604 debian + echo "Building for $DISTRO"; fi; -# Auto-install all ZoneMinder's depedencies using the Debian control file -sudo apt-get install devscripts equivs -sudo mk-build-deps -ir ./debian/control - -if [ -z `hostname -d` ] ; then - AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`.local>" +# Release is a special mode... it uploads to the release ppa and cannot have a snapshot +if [ "$RELEASE" != "" ]; then + if [ "$SNAPSHOT" != "" ]; then + echo "Releases cannot have a snapshot.... exiting." + exit 0; + fi + if [ "$GITHUB_FORK" != "" ] && [ "$GITHUB_FORK" != "ZoneMinder" ]; then + echo "Releases cannot have a fork ($GITHUB_FORK).... exiting." + exit 0; + else + GITHUB_FORK="ZoneMinder"; + fi + BRANCH="release-$RELEASE" else - AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`>" + if [ "$GITHUB_FORK" == "" ]; then + echo "Defaulting to ZoneMinder upstream git" + GITHUB_FORK="ZoneMinder" + fi; + if [ "$SNAPSHOT" == "stable" ]; then + if [ "$BRANCH" == "" ]; then + BRANCH=$(git describe --tags $(git rev-list --tags --max-count=1)); + echo "Latest stable branch is $BRANCH"; + fi; + else + if [ "$BRANCH" == "" ]; then + echo "Defaulting to master branch"; + BRANCH="master"; + fi; + if [ "$SNAPSHOT" == "NOW" ]; then + SNAPSHOT=`date +%Y%m%d%H%M%S`; + fi; + fi; fi -cat < debian/changelog -zoneminder ($VERSION-$DISTRO-$SNAPSHOT) $DISTRO; urgency=medium - * +# Instead of cloning from github each time, if we have a fork lying around, update it and pull from there instead. +if [ ! -d "${GITHUB_FORK}_zoneminder_release" ]; then + if [ -d "${GITHUB_FORK}_ZoneMinder.git" ]; then + echo "Using local clone ${GITHUB_FORK}_ZoneMinder.git to pull from." + cd "${GITHUB_FORK}_ZoneMinder.git" + echo "git pull..." + git pull + echo "git checkout $BRANCH" + git checkout $BRANCH + echo "git pull..." + git pull + cd ../ + echo "git clone ${GITHUB_FORK}_ZoneMinder.git ${GITHUB_FORK}_zoneminder_release" + git clone "${GITHUB_FORK}_ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release" + else + echo "git clone https://github.com/$GITHUB_FORK/ZoneMinder.git ${GITHUB_FORK}_zoneminder_release" + git clone "https://github.com/$GITHUB_FORK/ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release" + fi +else + echo "release dir already exists. Please remove it." + exit 0; +fi; + +cd "${GITHUB_FORK}_zoneminder_release" + git checkout $BRANCH +cd ../ + +VERSION=`cat ${GITHUB_FORK}_zoneminder_release/version` + +if [ $VERSION == "" ]; then + exit 1; +fi; +if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then + VERSION="$VERSION~$SNAPSHOT"; +fi; + +DIRECTORY="zoneminder_$VERSION"; +echo "Doing $TYPE release $DIRECTORY"; +mv "${GITHUB_FORK}_zoneminder_release" "$DIRECTORY.orig"; +if [ $? -ne 0 ]; then + echo "Error status code is: $?" + echo "Setting up build dir failed."; + exit $?; +fi; +cd "$DIRECTORY.orig"; + +git submodule init +git submodule update --init --recursive +if [ "$DISTRO" == "trusty" ] || [ "$DISTRO" == "precise" ]; then + mv distros/ubuntu1204 debian +else + if [ "$DISTRO" == "wheezy" ]; then + mv distros/debian debian + else + mv distros/ubuntu1604 debian + fi; +fi; + +if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then + AUTHOR="$DEBFULLNAME <$DEBEMAIL>" +else + if [ -z `hostname -d` ] ; then + AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`.local>" + else + AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`>" + fi +fi + +if [ "$URGENCY" = "" ]; then + URGENCY="medium" +fi; + +if [ "$SNAPSHOT" == "stable" ]; then +cat < debian/changelog +zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY + + * Release $VERSION -- $AUTHOR $DATE EOF -#rm -rf .git -#rm .gitignore -#cd ../ -#tar zcf zoneminder_$VERSION-$DISTRO.orig.tar.gz zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig -#cd zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig -if [ $TYPE == "binary" ]; then - debuild +cat < debian/NEWS +zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY + + * Release $VERSION + + -- $AUTHOR $DATE +EOF else - if [ $TYPE == "local" ]; then - debuild -i -us -uc -b - else - debuild -S -sa - fi; +cat < debian/changelog +zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY + + * + + -- $AUTHOR $DATE +EOF +cat < debian/changelog +zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY + + * + + -- $AUTHOR $DATE +EOF +fi; + +rm -rf .git +rm .gitignore +cd ../ +tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig +cd $DIRECTORY.orig + +if [ $TYPE == "binary" ]; then + # Auto-install all ZoneMinder's depedencies using the Debian control file + sudo apt-get install devscripts equivs + sudo mk-build-deps -ir ./debian/control + echo "Status: $?" + DEBUILD=debuild +else + if [ $TYPE == "local" ]; then + # Auto-install all ZoneMinder's depedencies using the Debian control file + sudo apt-get install devscripts equivs + sudo mk-build-deps -ir ./debian/control + echo "Status: $?" + DEBUILD="debuild -i -us -uc -b" + else + # Source build, don't need build depends. + DEBUILD="debuild -S -sa" + fi; +fi; +if [ "$DEBSIGN_KEYID" != "" ]; then + DEBUILD="$DEBUILD -k$DEBSIGN_KEYID" +fi +$DEBUILD +if [ $? -ne 0 ]; then +echo "Error status code is: $?" + echo "Build failed."; + exit $?; fi; cd ../ +if [ "$INTERACTIVE" != "no" ]; then + read -p "Do you want to keep the checked out version of Zoneminder (incase you want to modify it later) [y/N]" + [[ $REPLY == [yY] ]] && { mv "$DIRECTORY.orig" zoneminder_release; echo "The checked out copy is preserved in zoneminder_release"; } || { rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted"; } + echo "Done!" +else + rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted"; +fi -read -p "Do you want to keep the checked out version of Zoneminder (incase you want to modify it later) [y/N]" -[[ $REPLY == [yY] ]] && { mv zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig zoneminder_release; echo "The checked out copy is preserved in zoneminder_release"; } || { rm -fr zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig; echo "The checked out copy has been deleted"; } -echo "Done!" - +if [ $TYPE == "binary" ]; then + if [ "$INTERACTIVE" != "no" ]; then + read -p "Not doing dput since it's a binary release. Do you want to install it? (Y/N)" + if [[ $REPLY == [yY] ]]; then + sudo dpkg -i $DIRECTORY*.deb + else + echo $REPLY; + fi; + if [ "$DISTRO" == "jessie" ]; then + read -p "Do you want to upload this binary to zmrepo? (y/N)" + if [[ $REPLY == [yY] ]]; then + if [ "$RELEASE" != "" ]; then + scp "zoneminder_${VERSION}-${DISTRO}*" "zmrepo@zmrepo.connortechnology.com:debian/stable/mini-dinstall/incoming/" + else + if [ "$BRANCH" == "" ]; then + scp "zoneminder_${VERSION}-${DISTRO}*" "zmrepo@zmrepo.connortechnology.com:debian/master/mini-dinstall/incoming/" + else + scp "$DIRECTORY-${DISTRO}*" "zmrepo@zmrepo.connortechnology.com:debian/${BRANCH}/mini-dinstall/incoming/" + fi; + fi; + fi; + fi; + fi; +else + SC="zoneminder_${VERSION}-${DISTRO}${PACKAGE_VERSION}_source.changes"; + PPA=""; + if [ "$RELEASE" != "" ]; then + PPA="ppa:iconnor/zoneminder"; + else + if [ "$BRANCH" == "" ]; then + PPA="ppa:iconnor/zoneminder-master"; + else + PPA="ppa:iconnor/zoneminder-$BRANCH"; + fi; + fi; + + dput="Y"; + if [ "$INTERACTIVE" != "no" ]; then + echo "Ready to dput $SC to $PPA ? Y/N..."; + read dput + fi + if [ "$dput" == [Yy] ]; then + dput $PPA $SC + fi; +fi; diff --git a/utils/docker/README.md b/utils/docker/README.md index 4ca100b66..741039b3d 100644 --- a/utils/docker/README.md +++ b/utils/docker/README.md @@ -15,7 +15,7 @@ This is still a bit of a work in progress. 2. Build ZoneMinder container ```sudo docker build -t yourname/zoneminder github.com/ZoneMinder/ZoneMinder``` 3. Run it -```CID=$(sudo docker run -d -p 222:22 -p 8080:80 -name zoneminder yourname/zoneminder)``` +```CID=$(sudo docker run -d -p 222:22 -p 8080:80 --name zoneminder yourname/zoneminder)``` 4. Use it -- you can now SSH to port 222 on your host as user root with password root. You can also browse to your host on port 8080 to access the zoneminder web interface diff --git a/utils/docker/apache-vhost b/utils/docker/apache-vhost deleted file mode 100644 index 5f59c01b2..000000000 --- a/utils/docker/apache-vhost +++ /dev/null @@ -1,12 +0,0 @@ - - DocumentRoot /var/www/zm - DirectoryIndex index.php - - ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ - - AllowOverride None - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - require all granted - - - diff --git a/utils/docker/setup.sh b/utils/docker/setup.sh new file mode 100755 index 000000000..d1572d494 --- /dev/null +++ b/utils/docker/setup.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +setup_mysql_first_time(){ + if [ "$(ls /var/lib/mysql)" ]; then + return + fi + + # Set MySQL in the volume + rm -rf /var/lib/mysql/* + chown -R mysql:mysql /var/lib/mysql + mysqld --initialize-insecure + + # Start MySQL + # For Xenial the following won't start mysqld + #/usr/bin/mysqld_safe & + # Use this instead: + service mysql start + + # Give MySQL time to wake up + SECONDS_LEFT=120 + while true; do + sleep 1 + mysqladmin ping + if [ $? -eq 0 ];then + break; # Success + fi + let SECONDS_LEFT=SECONDS_LEFT-1 + + # If we have waited >120 seconds, give up + # ZM should never have a database that large! + # if $COUNTER -lt 120 + if [ $SECONDS_LEFT -eq 0 ];then + return -1; + fi + done + + # Create the ZoneMinder database + mysql -u root < db/zm_create.sql + + # Add the ZoneMinder DB user + mysql -u root -e "grant insert,select,update,delete,lock tables,alter on zm.* to 'zmuser'@'localhost' identified by 'zmpass';" + + # Shut down mysql cleanly: + kill $(cat /var/run/mysqld/mysqld.pid) + sleep 5 +} + +setup_mysql() { + # To configure MySQL if no container did it before + setup_mysql_first_time + + # Add configuration to avoid SQL error when adding monitor + echo "sql_mode=NO_ENGINE_SUBSTITUTION" >> /etc/mysql/mysql.conf.d/mysqld.cnf +} + +setup_php() { + # Activate CGI + a2enmod cgi + + # Activate modrewrite + a2enmod rewrite + + # Setting timezone + sed -i "s#;date.timezone =#date.timezone = $PHP_TIMEZONE#" /etc/php/7.0/apache2/php.ini + + # Settings rights for volume + chown -R www-data:www-data /var/lib/zoneminder/events + chown -R www-data:www-data /var/lib/zoneminder/images +} + + +setup_mysql +setup_php + +exit 0 diff --git a/utils/docker/start.sh b/utils/docker/start.sh old mode 100644 new mode 100755 index 0772f7b9b..599b6fcb2 --- a/utils/docker/start.sh +++ b/utils/docker/start.sh @@ -1,13 +1,17 @@ #!/bin/bash # Prepare proper amount of shared memory -# For H.264 cameras it may be necessary to increase the amout of shared memory +# For H.264 cameras it may be necessary to increase the amount of shared memory # to 2048 megabytes. umount /dev/shm mount -t tmpfs -o rw,nosuid,nodev,noexec,relatime,size=512M tmpfs /dev/shm # Start MySQL -/usr/bin/mysqld_safe & +test -e /var/run/mysqld || install -m 755 -o mysql -g root -d /var/run/mysqld +su - mysql -s /bin/sh -c "/usr/bin/mysqld_safe > /dev/null 2>&1 &" + +# Ensure we shut down mysql cleanly later: +trap close_mysql SIGTERM # Give MySQL time to wake up SECONDS_LEFT=120 @@ -27,23 +31,18 @@ while true; do fi done -# Create the ZoneMinder database -mysql -u root < db/zm_create.sql - -# Add the ZoneMinder DB user -mysql -u root -e "grant insert,select,update,delete,lock tables,alter on zm.* to 'zmuser'@'localhost' identified by 'zmpass';" - -# Activate CGI -a2enmod cgi - -# Activate modrewrite -a2enmod rewrite - # Restart apache service apache2 restart # Start ZoneMinder -/usr/local/bin/zmpkg.pl start +/usr/local/bin/zmpkg.pl start && echo "Zone Minder started" -# Start SSHD -/usr/sbin/sshd -D +while : +do + sleep 3600 +done + +function close_mysql { + kill $(cat /var/run/mysqld/mysqld.pid) + sleep 5 +} diff --git a/utils/mk_bigfont.pl b/utils/mk_bigfont.pl index ff2b1c30c..41ebb34bf 100755 --- a/utils/mk_bigfont.pl +++ b/utils/mk_bigfont.pl @@ -17,7 +17,7 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== diff --git a/utils/packpack/deploy_rsa b/utils/packpack/deploy_rsa new file mode 100644 index 000000000..4c4d552bb --- /dev/null +++ b/utils/packpack/deploy_rsa @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC6tO+Kuj85uS4Z7ZmyAjmeAJbKbuv+pddPncgY/0/cSKfLbDo5f+TFM9K5AUlegcALbccUA9jt5p615KTgN0ZyGiCDcNs6/1DcDh4Nb/KgHsApVLZHZYpd7bnP5yadIqXroXw9VI2PWhUTasb97t4xvEepcsnepdfeGFegX7jyeRqucdEsZZa8kiSgU9hIdJyTeQXQQY5odYqABvR6ea7ff8iD2pdzjXIFHiA9527fXoPuUJo5rwIWOwtwstFG5xCT6ZPBgi2nECSZoRdG7zqkm2gqhAaNiR8PR5Qr0CbeYa4LWYl0v33CwLxFuyiP9AOqdFB+vF9c67do4E0yJ+heUNuOUbp3ePT1jJDOhQapAjkmyEb4A+RMNc9SEXmJh10nUiJ2zGGb9a3FyreopHfjdzKiZdk+uSV18kdG7KSiRvHep+sEK7xMepAQ3OiAprwOH2D2EoXC08TLehaeXmsOAuMGz0JvJKBXMGAow2SEa38v42PJwKsjn0qo72IjN1h/9ICgM/o+4oNHLHsu5GK1dornAt9OE6g1be1jXdij07QaSV831NiweOHqRTPpl2eglWfoyMjPuzebiyoAqb4tNBnMOk5BTaxx7ZZGXmdHyH9kKFAVj+WBkRHNlDhaMB2/QzhHUs0PRVRUexBKgog+1xnPE5gTie6NNnDB04NcDQ== zmrepo@zmrepo.zoneminder.com diff --git a/utils/packpack/deploy_rsa.enc b/utils/packpack/deploy_rsa.enc new file mode 100644 index 000000000..baaf90711 Binary files /dev/null and b/utils/packpack/deploy_rsa.enc differ diff --git a/utils/packpack/installzm.sh b/utils/packpack/installzm.sh new file mode 100755 index 000000000..31e54866f --- /dev/null +++ b/utils/packpack/installzm.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# No longer needed. This was incorporated into startpackpack.sh + +# Required, so that Travis marks the build as failed if any of the steps below fail +set -ev + +# Install and test the zoneminder package (only) for Ubuntu Trusty +if [ ${OS} == "ubuntu" ] && [ ${DIST} == "trusty" ]; then + sudo gdebi --non-interactive build/zoneminder_*amd64.deb + sudo chmod 644 /etc/zm/zm.conf + mysql -uzmuser -pzmpass zm < db/test.monitor.sql + sudo /usr/bin/zmpkg.pl start + sudo /usr/bin/zmfilter.pl -f purgewhenfull +fi + diff --git a/utils/packpack/packpack-rpm.patch b/utils/packpack/packpack-rpm.patch new file mode 100644 index 000000000..6d6b0e7f3 --- /dev/null +++ b/utils/packpack/packpack-rpm.patch @@ -0,0 +1,10 @@ +--- a/packpack/pack/rpm.mk 2017-05-10 09:53:38.797616947 -0500 ++++ b/packpack/pack/rpm.mk 2017-05-10 09:59:26.744409073 -0500 +@@ -26,7 +26,6 @@ + -e 's/Source0:\([ ]*\).*/Source0: $(TARBALL)/' \ + -e 's/%setup.*/%setup -q -n $(PRODUCT)-$(VERSION)/' \ + -re 's/(%autosetup.*)( -n \S*)(.*)/\1\3/' \ +- -e '0,/%autosetup.*/ s/%autosetup.*/%autosetup -n $(PRODUCT)-$(VERSION)/' \ + -e '/%changelog/a\* $(THEDATE) $(CHANGELOG_NAME) <$(CHANGELOG_EMAIL)> - $(VERSION)-$(RELEASE)\n\- $(CHANGELOG_TEXT)\n' \ + -i $@.tmp + grep -F "Version: $(VERSION)" $@.tmp && \ diff --git a/utils/packpack/redhat_package.mk b/utils/packpack/redhat_package.mk new file mode 100644 index 000000000..c15e03528 --- /dev/null +++ b/utils/packpack/redhat_package.mk @@ -0,0 +1,8 @@ +.PHONY: redhat_package +.NOTPARALLEL: redhat_package + +redhat_package: redhat_bootstrap package + +redhat_bootstrap: + sudo yum install -y --nogpgcheck build/zmrepo.noarch.rpm + diff --git a/utils/packpack/rsync_xfer.sh b/utils/packpack/rsync_xfer.sh new file mode 100755 index 000000000..e981f646f --- /dev/null +++ b/utils/packpack/rsync_xfer.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# Check to see if this script has access to all the commands it needs +for CMD in sshfs rsync find fusermount mkdir; do + type $CMD 2>&1 > /dev/null + + if [ $? -ne 0 ]; then + echo + echo "ERROR: The script cannot find the required command \"${CMD}\"." + echo + exit 1 + fi +done + +# We only want to deploy packages during cron events +# See https://docs.travis-ci.com/user/cron-jobs/ +if [ "${TRAVIS_EVENT_TYPE}" == "cron" ]; then + + if [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then + targetfolder="debian/master/mini-dinstall/incoming" + else + targetfolder="travis" + fi + + echo + echo "Target subfolder set to $targetfolder" + echo + + mkdir -p ./zmrepo + ssh_mntchk="$(sshfs zmrepo@zmrepo.zoneminder.com:./ ./zmrepo -o workaround=rename,reconnect)" + + if [ -z "$ssh_mntchk" ]; then + echo + echo "Remote filesystem mounted successfully." + echo "Begin transfering files..." + echo + + # Don't keep packages older than 5 days + find ./zmrepo/$targetfolder/ -maxdepth 1 -type f -mtime +5 -delete + rsync -vzh --ignore-errors build/* zmrepo/$targetfolder/ + fusermount -zu zmrepo + else + echo + echo "ERROR: Attempt to mount zmrepo.zoneminder.com failed!" + echo "sshfs gave the following error message:" + echo \"$ssh_mntchk\" + echo + exit 99 + fi +fi diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh new file mode 100755 index 000000000..3cabf3a59 --- /dev/null +++ b/utils/packpack/startpackpack.sh @@ -0,0 +1,307 @@ +#!/bin/bash +# packpack setup file for the ZoneMinder project +# Written by Andrew Bauer + +############### +# SUBROUTINES # +############### + +# General sanity checks +checksanity () { + # Check to see if this script has access to all the commands it needs + for CMD in set echo curl repoquery git ln mkdir rmdir cat patch; do + type $CMD 2>&1 > /dev/null + + if [ $? -ne 0 ]; then + echo + echo "ERROR: The script cannot find the required command \"${CMD}\"." + echo + exit 1 + fi + done + + # Verify OS & DIST environment variables have been set before calling this script + if [ -z "${OS}" ] || [ -z "${DIST}" ]; then + echo "ERROR: both OS and DIST environment variables must be set" + exit 1 + fi + + if [ -z "${ARCH}" ]; then + ARCH="x86_64" + fi + + if [[ "${ARCH}" != "x86_64" && "${ARCH}" != "i386" && "${ARCH}" != "armhf" ]]; then + echo + echo "ERROR: Unsupported architecture specified \"${ARCH}\"." + echo + exit 1 + fi +} + +# Create key variables used to assemble the package name +createvars () { + # We need today's date in year/month/day format + thedate=$(date +%Y%m%d) + + # We need the (short) commit hash of the latest commit (rpm packaging only) + shorthash=$(git describe --long --always | awk -F - '{print $3}') + + # Grab the ZoneMinder version from the contents of the version file + versionfile=$(cat version) + + # git the latest (short) commit hash of the version file + versionhash=$(git log -n1 --pretty=format:%h version) + + # Number of commits since the version file was last changed + numcommits=$(git rev-list ${versionhash}..HEAD --count) +} + +# Check key variables before calling packpack +checkvars () { + + for var in $thedate $shorthash $versionfile $versionhash $numcommits; do + if [ -z ${var} ]; then + echo + echo "FATAL: This script was unable to determine one or more key variables. Cannot continue." + echo + echo "VARIABLE DUMP" + echo "-------------" + echo + echo "thedate: ${thedate}" + echo "shorthash: ${shorthash}" + echo "versionfile: ${versionfile}" + echo "versionhash: ${versionhash}" + echo "numcommits: ${numcommits}" + echo + exit 98 + fi + done +} + +# Steps common to all builds +commonprep () { + mkdir -p build + if [ -e "packpack/Makefile" ]; then + echo "Checking packpack github repo for changes..." + git -C packpack pull origin master + else + echo "Cloning packpack github repo..." + git clone https://github.com/packpack/packpack.git packpack + fi + + # Patch packpack + patch --dry-run --silent -f -p1 < utils/packpack/packpack-rpm.patch + if [ $? -eq 0 ]; then + patch -p1 < utils/packpack/packpack-rpm.patch + fi + + # The rpm specfile requires we download the tarball and manually move it into place + # Might as well do this for Debian as well, rather than git submodule init + CRUDVER="3.0.10" + if [ -e "build/crud-${CRUDVER}.tar.gz" ]; then + echo "Found existing Crud ${CRUDVER} tarball..." + else + echo "Retrieving Crud ${CRUDVER} submodule..." + curl -L https://github.com/FriendsOfCake/crud/archive/v${CRUDVER}.tar.gz > build/crud-${CRUDVER}.tar.gz + if [ $? -ne 0 ]; then + echo "ERROR: Crud tarball retreival failed..." + exit 1 + fi + fi +} + +# Uncompress the Crud tarball and move it into place +movecrud () { + if [ -e "web/api/app/Plugin/Crud/LICENSE.txt" ]; then + echo "Crud plugin already installed..." + else + echo "Unpacking Crud plugin..." + tar -xzf build/crud-${CRUDVER}.tar.gz + rmdir web/api/app/Plugin/Crud + mv -f crud-${CRUDVER} web/api/app/Plugin/Crud + fi +} + +# previsouly part of installzm.sh +# install the trusty deb and test zoneminder +installtrusty () { + + # Check we've got gdebi installed + type gdebi 2>&1 > /dev/null + + if [ $? -ne 0 ]; then + echo + echo "ERROR: The script cannot find the required command \"gdebi\"." + echo + exit 1 + fi + + # Install and test the zoneminder package (only) for Ubuntu Trusty + pkgname="build/zoneminder_${VERSION}-${RELEASE}_amd64.deb" + + if [ -e $pkgname ]; then + sudo gdebi --non-interactive $pkgname + mysql -uzmuser -pzmpass zm < db/test.monitor.sql + sudo /usr/bin/zmpkg.pl start + sudo /usr/bin/zmfilter.pl -f purgewhenfull + else + echo + echo "ERROR: The script cannot find the package $pkgname" + echo "Check the Travis log for a build failure." + echo + exit 99 + fi +} + +# This sets the naming convention for the rpm packages +setrpmpkgname () { + + createvars + + # Set VERSION to the contents of the version file e.g. 1.31.0 + # Set RELEASE to 1.{number of commits}.{today's date}git{short hash of HEAD} e.g. 1.82.20170605gitg7ae0b4a + export VERSION="$versionfile" + export RELEASE="1.${numcommits}.${thedate}git${shorthash}" + + checkvars + + echo + echo "Packpack VERSION has been set to: ${VERSION}" + echo "Packpack RELEASE has been set to: ${RELEASE}" + echo + +} + +# This sets the naming convention for the deb packages +setdebpkgname () { + + createvars + + # Set VERSION to {zm version}~{today's date}.{number of commits} e.g. 1.31.0~20170605.82 + # Set RELEASE to the packpack DIST variable e.g. Trusty + export VERSION="${versionfile}~${thedate}.${numcommits}" + export RELEASE="${DIST}" + + checkvars + + echo + echo "Packpack VERSION has been set to: ${VERSION}" + echo "Packpack RELEASE has been set to: ${RELEASE}" + echo + +} + +# This adds an entry to the rpm specfile changelog +setrpmchangelog () { + + export CHANGELOG_NAME="Andrew Bauer" + export CHANGELOG_EMAIL="zonexpertconsulting@outlook.com" + export CHANGELOG_TEXT="Automated, development snapshot of git ${shorthash}" + +} + + +# This adds an entry to the debian changelog +setdebchangelog () { +DATE=`date -R` +cat < debian/changelog +zoneminder ($VERSION-${DIST}-1) unstable; urgency=low + * + -- Isaac Connor $DATE +EOF +} + +################ +# MAIN PROGRAM # +################ + +checksanity + +# We don't want to build packages for all supported distros after every commit +# Only build all packages when executed via cron +# See https://docs.travis-ci.com/user/cron-jobs/ +if [ "${TRAVIS_EVENT_TYPE}" == "cron" ] || [ "${TRAVIS}" != "true" ]; then + commonprep + + # Steps common to Redhat distros + if [ "${OS}" == "el" ] || [ "${OS}" == "fedora" ]; then + echo "Begin Redhat build..." + + setrpmpkgname + + ln -sfT distros/redhat rpm + + # The rpm specfile requires the Crud submodule folder to be empty + rm -rf web/api/app/Plugin/Crud + mkdir web/api/app/Plugin/Crud + + if [ "${OS}" == "el" ]; then + zmrepodistro=${OS} + else + zmrepodistro="f" + fi + + # Let repoquery determine the full url and filename of the zmrepo rpm we are interested in + result=`repoquery --repofrompath=zmpackpack,https://zmrepo.zoneminder.com/${zmrepodistro}/"${DIST}"/x86_64/ --repoid=zmpackpack --qf="%{location}" zmrepo 2> /dev/null` + + if [ -n "$result" ] && [ $? -eq 0 ]; then + echo "Retrieving ZMREPO rpm..." + curl $result > build/zmrepo.noarch.rpm + else + echo "ERROR: Failed to retrieve zmrepo rpm..." + exit 1 + fi + + setrpmchangelog + + echo "Starting packpack..." + packpack/packpack -f utils/packpack/redhat_package.mk redhat_package + + # Steps common to Debian based distros + elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then + echo "Begin ${OS} ${DIST} build..." + + setdebpkgname + movecrud + + if [ "${DIST}" == "trusty" ] || [ "${DIST}" == "precise" ]; then + ln -sfT distros/ubuntu1204 debian + elif [ "${DIST}" == "wheezy" ]; then + ln -sfT distros/debian debian + else + ln -sfT distros/ubuntu1604 debian + fi + + setdebchangelog + + echo "Starting packpack..." + packpack/packpack + + if [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "trusty" ] && [ "${ARCH}" == "x86_64" ] && [ "${TRAVIS}" == "true" ]; then + installtrusty + fi + fi + +# We were not triggered via cron so just build and test trusty +elif [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "trusty" ] && [ "${ARCH}" == "x86_64" ]; then + echo "Begin Ubuntu Trusty build..." + + commonprep + setdebpkgname + movecrud + + ln -sfT distros/ubuntu1204 debian + + setdebchangelog + + echo "Starting packpack..." + packpack/packpack + + # If we are running inside Travis then attempt to install the deb we just built + if [ "${TRAVIS}" == "true" ]; then + installtrusty + fi +fi + +exit 0 + diff --git a/utils/zmeditconfigdata.sh b/utils/zmeditconfigdata.sh index 71b73bb6e..c95569f34 100755 --- a/utils/zmeditconfigdata.sh +++ b/utils/zmeditconfigdata.sh @@ -53,10 +53,10 @@ escaped="$(echo $temp | sed 's/\//\\\//g')" } # Assign variables once they are properly escaped -escape $1 -variable=$escaped -escape $2 -default=$escaped +escape "$1" +variable="$escaped" +escape "$2" +default="$escaped" # Set the path to ConfigData if [ -n "$3" ]; then @@ -82,7 +82,7 @@ fi # Don't stare too closely. You will burn your eyes out. sed -i '/.*'${variable}'.*/{ $!{ N - s/\(.*'${variable}'.*\n.*\)\"\(.*\)\"/\1\"'${default}'\"/ + s/\(.*'${variable}'.*\n.*\)\"\(.*\)\"/\1\"'"${default}"'\"/ t yes P D diff --git a/version b/version index 5e57fb895..34aae156b 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.29.0 +1.31.0 diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100644 index 000000000..217a0e30c --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,13 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +indent_style = tab +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.bat] +end_of_line = crlf diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 000000000..90d971d4b --- /dev/null +++ b/web/.gitignore @@ -0,0 +1,22 @@ +# User specific & automatically generated files # +################################################# +/app/Config/database.php +/app/tmp +/lib/Cake/Console/Templates/skel/tmp/ +/plugins +/vendors +/build +/dist +/tags +/app/webroot/events + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +ehthumbs.db +Thumbs.db diff --git a/web/.htaccess b/web/.htaccess new file mode 100644 index 000000000..f23dbaf66 --- /dev/null +++ b/web/.htaccess @@ -0,0 +1,5 @@ + + RewriteEngine on + RewriteRule ^$ app/webroot/ [L] + RewriteRule (.*) app/webroot/$1 [L] + \ No newline at end of file diff --git a/web/.travis.yml b/web/.travis.yml new file mode 100644 index 000000000..5283442c4 --- /dev/null +++ b/web/.travis.yml @@ -0,0 +1,116 @@ +language: php + +php: + - 5.2 + - 5.3 + - 5.4 + +env: + - DB=mysql + - DB=pgsql + - DB=sqlite + +matrix: + include: + - php: 5.4 + env: + - PHPCS=1 + +before_script: + - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test;'; fi" + - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test2;'; fi" + - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test3;'; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE DATABASE cakephp_test;' -U postgres; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE SCHEMA test2;' -U postgres -d cakephp_test; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE SCHEMA test3;' -U postgres -d cakephp_test; fi" + - chmod -R 777 ./app/tmp + - sudo apt-get install lighttpd + - pear channel-discover pear.cakephp.org + - pear install --alldeps cakephp/CakePHP_CodeSniffer + - phpenv rehash + - set +H + - echo " array( + 'datasource' => 'Database/Mysql', + 'host' => '0.0.0.0', + 'login' => 'travis' + ), + 'pgsql' => array( + 'datasource' => 'Database/Postgres', + 'host' => '127.0.0.1', + 'login' => 'postgres', + 'database' => 'cakephp_test', + 'schema' => array( + 'default' => 'public', + 'test' => 'public', + 'test2' => 'test2', + 'test_database_three' => 'test3' + ) + ), + 'sqlite' => array( + 'datasource' => 'Database/Sqlite', + 'database' => array( + 'default' => ':memory:', + 'test' => ':memory:', + 'test2' => '/tmp/cakephp_test2.db', + 'test_database_three' => '/tmp/cakephp_test3.db' + ), + ) + ); + public \$default = array( + 'persistent' => false, + 'host' => '', + 'login' => '', + 'password' => '', + 'database' => 'cakephp_test', + 'prefix' => '' + ); + public \$test = array( + 'persistent' => false, + 'host' => '', + 'login' => '', + 'password' => '', + 'database' => 'cakephp_test', + 'prefix' => '' + ); + public \$test2 = array( + 'persistent' => false, + 'host' => '', + 'login' => '', + 'password' => '', + 'database' => 'cakephp_test2', + 'prefix' => '' + ); + public \$test_database_three = array( + 'persistent' => false, + 'host' => '', + 'login' => '', + 'password' => '', + 'database' => 'cakephp_test3', + 'prefix' => '' + ); + public function __construct() { + \$db = 'mysql'; + if (!empty(\$_SERVER['DB'])) { + \$db = \$_SERVER['DB']; + } + foreach (array('default', 'test', 'test2', 'test_database_three') as \$source) { + \$config = array_merge(\$this->{\$source}, \$this->identities[\$db]); + if (is_array(\$config['database'])) { + \$config['database'] = \$config['database'][\$source]; + } + if (!empty(\$config['schema']) && is_array(\$config['schema'])) { + \$config['schema'] = \$config['schema'][\$source]; + } + \$this->{\$source} = \$config; + } + } + }" > app/Config/database.php + +script: + - sh -c "if [ '$PHPCS' != '1' ]; then ./lib/Cake/Console/cake test core AllTests --stderr; else phpcs -p --extensions=php --standard=CakePHP ./lib/Cake; fi" + +notifications: + email: false \ No newline at end of file diff --git a/web/CMakeLists.txt b/web/CMakeLists.txt index b9e75a7e8..e9b156068 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -9,7 +9,7 @@ add_subdirectory(tools/mootools) configure_file(includes/config.php.in "${CMAKE_CURRENT_BINARY_DIR}/includes/config.php" @ONLY) # Install the web files -install(DIRECTORY api ajax css graphics includes js lang skins tools views DESTINATION "${ZM_WEBDIR}" PATTERN "*.in" EXCLUDE PATTERN "*Make*" EXCLUDE PATTERN "*cmake*" EXCLUDE) +install(DIRECTORY api ajax css fonts graphics includes js lang skins tools views DESTINATION "${ZM_WEBDIR}" PATTERN "*.in" EXCLUDE PATTERN "*Make*" EXCLUDE PATTERN "*cmake*" EXCLUDE) install(FILES index.php DESTINATION "${ZM_WEBDIR}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/includes/config.php" DESTINATION "${ZM_WEBDIR}/includes") @@ -18,6 +18,7 @@ if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/core.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/database.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/app/Config/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/app/Config") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/api/lib/Cake/bootstrap.php" DESTINATION "${ZM_WEBDIR}/api/lib/Cake") endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR)) # Install the mootools symlinks (if its not in the source directory) diff --git a/web/ajax/event.php b/web/ajax/event.php index 1953acab0..661a2f1ce 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -1,113 +1,120 @@ $videoFile ) ); - else - ajaxError( "Video Generation Failed" ); - } - $ok = true; - break; + switch ( $_REQUEST['action'] ) { + case 'video' : + { + if ( empty($_REQUEST['videoFormat']) ) { + ajaxError( 'Video Generation Failure, no format given' ); + } elseif ( empty($_REQUEST['rate']) ) { + ajaxError( 'Video Generation Failure, no rate given' ); + } elseif ( empty($_REQUEST['scale']) ) { + ajaxError( 'Video Generation Failure, no scale given' ); + } else { + $sql = 'SELECT E.*,M.Name AS MonitorName,M.DefaultRate,M.DefaultScale FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE E.Id = ?'.monitorLimitSql(); + if ( !($event = dbFetchOne( $sql, NULL, array( $_REQUEST['id'] ) )) ) + ajaxError( "Video Generation Failure, can't load event" ); + else + if ( $videoFile = createVideo( $event, $_REQUEST['videoFormat'], $_REQUEST['rate'], $_REQUEST['scale'], !empty($_REQUEST['overwrite']) ) ) + ajaxResponse( array( 'response'=>$videoFile ) ); + else + ajaxError( "Video Generation Failed" ); } - case 'deleteVideo' : - { - unlink( $videoFiles[$_REQUEST['id']] ); - unset( $videoFiles[$_REQUEST['id']] ); - ajaxResponse(); - break; - } - case "export" : - { - require_once( ZM_SKIN_PATH.'/includes/export_functions.php' ); + $ok = true; + break; + } + case 'deleteVideo' : + { + unlink( $videoFiles[$_REQUEST['id']] ); + unset( $videoFiles[$_REQUEST['id']] ); + ajaxResponse(); + break; + } + case 'export' : + { + require_once( ZM_SKIN_PATH.'/includes/export_functions.php' ); - if ( !empty($_REQUEST['exportDetail']) ) - $exportDetail = $_SESSION['export']['detail'] = $_REQUEST['exportDetail']; - else - $exportDetail = false; - if ( !empty($_REQUEST['exportFrames']) ) - $exportFrames = $_SESSION['export']['frames'] = $_REQUEST['exportFrames']; - else - $exportFrames = false; - if ( !empty($_REQUEST['exportImages']) ) - $exportImages = $_SESSION['export']['images'] = $_REQUEST['exportImages']; - else - $exportImages = false; - if ( !empty($_REQUEST['exportVideo']) ) - $exportVideo = $_SESSION['export']['video'] = $_REQUEST['exportVideo']; - else - $exportVideo = false; - if ( !empty($_REQUEST['exportMisc']) ) - $exportMisc = $_SESSION['export']['misc'] = $_REQUEST['exportMisc']; - else - $exportMisc = false; - if ( !empty($_REQUEST['exportFormat']) ) - $exportFormat = $_SESSION['export']['format'] = $_REQUEST['exportFormat']; - else - $exportFormat = ''; +# We use session vars in here, so we need to restart the session because we stopped it in index.php to improve concurrency. + session_start(); - $exportIds = !empty($_REQUEST['eids'])?$_REQUEST['eids']:$_REQUEST['id']; - if ( $exportFile = exportEvents( $exportIds, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc, $exportFormat ) ) - ajaxResponse( array( 'exportFile'=>$exportFile ) ); - else - ajaxError( "Export Failed" ); - break; - } - } + if ( !empty($_REQUEST['exportDetail']) ) + $exportDetail = $_SESSION['export']['detail'] = $_REQUEST['exportDetail']; + else + $exportDetail = false; + if ( !empty($_REQUEST['exportFrames']) ) + $exportFrames = $_SESSION['export']['frames'] = $_REQUEST['exportFrames']; + else + $exportFrames = false; + if ( !empty($_REQUEST['exportImages']) ) + $exportImages = $_SESSION['export']['images'] = $_REQUEST['exportImages']; + else + $exportImages = false; + if ( !empty($_REQUEST['exportVideo']) ) + $exportVideo = $_SESSION['export']['video'] = $_REQUEST['exportVideo']; + else + $exportVideo = false; + if ( !empty($_REQUEST['exportMisc']) ) + $exportMisc = $_SESSION['export']['misc'] = $_REQUEST['exportMisc']; + else + $exportMisc = false; + if ( !empty($_REQUEST['exportFormat']) ) + $exportFormat = $_SESSION['export']['format'] = $_REQUEST['exportFormat']; + else + $exportFormat = ''; + + session_write_close(); + + $exportIds = !empty($_REQUEST['eids'])?$_REQUEST['eids']:$_REQUEST['id']; + if ( $exportFile = exportEvents( $exportIds, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc, $exportFormat ) ) + ajaxResponse( array( 'exportFile'=>$exportFile ) ); + else + ajaxError( 'Export Failed' ); + break; + } + } } -if ( canEdit( 'Events' ) ) -{ - switch ( $_REQUEST['action'] ) - { - case "rename" : - { - if ( !empty($_REQUEST['eventName']) ) - dbQuery( 'UPDATE Events SET Name = ? WHERE Id = ?', array( $_REQUEST['eventName'], $_REQUEST['id'] ) ); - else - ajaxError( "No new event name supplied" ); - ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>true ) ); - break; +if ( canEdit( 'Events' ) ) { + switch ( $_REQUEST['action'] ) { + case 'rename' : + { + if ( !empty($_REQUEST['eventName']) ) + dbQuery( 'UPDATE Events SET Name = ? WHERE Id = ?', array( $_REQUEST['eventName'], $_REQUEST['id'] ) ); + else + ajaxError( "No new event name supplied" ); + ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>true ) ); + break; + } + case 'eventdetail' : + { + dbQuery( 'UPDATE Events SET Cause = ?, Notes = ? WHERE Id = ?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['id'] ) ); + ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>true ) ); + break; + } + case 'archive' : + case 'unarchive' : + { + $archiveVal = ($_REQUEST['action'] == 'archive')?1:0; + dbQuery( 'UPDATE Events SET Archived = ? WHERE Id = ?', array( $archiveVal, $_REQUEST['id']) ); + ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>false ) ); + break; + } + case 'delete' : + { + $Event = new Event( $_REQUEST['id'] ); + if ( ! $Event->Id() ) { + ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true, 'message'=> 'Event not found.' ) ); + } else { + $Event->delete(); + ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true ) ); } - case "eventdetail" : - { - dbQuery( 'UPDATE Events SET Cause = ?, Notes = ? WHERE Id = ?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['id'] ) ); - ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>true ) ); - break; - } - case "archive" : - case "unarchive" : - { - $archiveVal = ($_REQUEST['action'] == "archive")?1:0; - dbQuery( 'UPDATE Events SET Archived = ? WHERE Id = ?', array( $archiveVal, $_REQUEST['id']) ); - ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>false ) ); - break; - } - case "delete" : - { - deleteEvent( $_REQUEST['id'] ); - ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true ) ); - break; - } - } + break; + } + } } ajaxError( 'Unrecognised action or insufficient permissions' ); diff --git a/web/ajax/log.php b/web/ajax/log.php index a250c952d..c38a6d5d0 100644 --- a/web/ajax/log.php +++ b/web/ajax/log.php @@ -1,5 +1,9 @@ Id()] = $server; - } + $servers = Server::find_all(); + $servers_by_Id = array(); +# There is probably a better way to do this. + foreach ( $servers as $server ) { + $servers_by_Id[$server->Id()] = $server; + } $minTime = isset($_POST['minTime'])?$_POST['minTime']:NULL; $maxTime = isset($_POST['maxTime'])?$_POST['maxTime']:NULL; - $limit = isset($_POST['limit'])?$_POST['limit']:100; - $filter = isset($_POST['filter'])?$_POST['filter']:array(); - $sortField = isset($_POST['sortField'])?$_POST['sortField']:'TimeKey'; + $limit = 100; + if ( isset($_POST['limit']) ) { + if ( ( !is_integer( $_POST['limit'] ) and !ctype_digit($_POST['limit']) ) ) { + Error("Invalid value for limit " . $_POST['limit'] ); + } else { + $limit = $_POST['limit']; + } + } + $sortField = 'TimeKey'; + if ( isset($_POST['sortField']) ) { + if ( ! in_array( $_POST['sortField'], $filterFields ) and ( $_POST['sortField'] != 'TimeKey' ) ) { + Error("Invalid sort field " . $_POST['sortField'] ); + } else { + $sortField = $_POST['sortField']; + } + } $sortOrder = (isset($_POST['sortOrder']) and $_POST['sortOrder']) == 'asc' ? 'asc':'desc'; + $filter = isset($_POST['filter'])?$_POST['filter']:array(); - $filterFields = array( 'Component', 'ServerId', 'Pid', 'Level', 'File', 'Line' ); - - $total = dbFetchOne( "SELECT count(*) AS Total FROM Logs", 'Total' ); + $total = dbFetchOne( 'SELECT count(*) AS Total FROM Logs', 'Total' ); $sql = 'SELECT * FROM Logs'; $where = array(); - $values = array(); + $values = array(); if ( $minTime ) { - $where[] = "TimeKey > ?"; - $values[] = $minTime; + $where[] = "TimeKey > ?"; + $values[] = $minTime; } elseif ( $maxTime ) { - $where[] = "TimeKey < ?"; - $values[] = $maxTime; - } + $where[] = "TimeKey < ?"; + $values[] = $maxTime; + } + foreach ( $filter as $field=>$value ) { - if ( $field == 'Level' ){ - $where[] = $field." <= ?"; - $values[] = $value; - } else { - $where[] = $field." = ?"; - $values[] = $value; - } - } + if ( ! in_array( $field, $filterFields ) ) { + Error("$field is not in valid filter fields"); + continue; + } + if ( $field == 'Level' ){ + $where[] = $field." <= ?"; + $values[] = $value; + } else { + $where[] = $field." = ?"; + $values[] = $value; + } + } if ( count($where) ) - $sql.= ' WHERE '.join( ' AND ', $where ); + $sql.= ' WHERE '.join( ' AND ', $where ); $sql .= " order by ".$sortField." ".$sortOrder." limit ".$limit; $logs = array(); foreach ( dbFetchAll( $sql, NULL, $values ) as $log ) { $log['DateTime'] = preg_replace( '/^\d+/', strftime( "%Y-%m-%d %H:%M:%S", intval($log['TimeKey']) ), $log['TimeKey'] ); - $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; + $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; $logs[] = $log; } $options = array(); $where = array(); - $values = array(); + $values = array(); foreach( $filter as $field=>$value ) { if ( $field == 'Level' ) { $where[$field] = $field." <= ?"; - $values[$field] = $value; + $values[$field] = $value; } else { $where[$field] = $field." = ?"; - $values[$field] = $value; - } - } + $values[$field] = $value; + } + } foreach( $filterFields as $field ) { $sql = "SELECT DISTINCT $field FROM Logs WHERE NOT isnull($field)"; $fieldWhere = array_diff_key( $where, array( $field=>true ) ); - $fieldValues = array_diff_key( $values, array( $field=>true ) ); + $fieldValues = array_diff_key( $values, array( $field=>true ) ); if ( count($fieldWhere) ) $sql.= " AND ".join( ' AND ', $fieldWhere ); $sql.= " ORDER BY $field ASC"; @@ -108,7 +129,7 @@ switch ( $_REQUEST['task'] ) { foreach( dbFetchAll( $sql, $field, array_values($fieldValues) ) as $value ) $options['ServerId'][$value] = ( $value and isset($servers_by_Id[$value]) ) ? $servers_by_Id[$value]->Name() : ''; - + } else { @@ -147,44 +168,51 @@ switch ( $_REQUEST['task'] ) } //$limit = isset($_POST['limit'])?$_POST['limit']:1000; $filter = isset($_POST['filter'])?$_POST['filter']:array(); - $sortField = isset($_POST['sortField'])?$_POST['sortField']:'TimeKey'; - $sortOrder = isset($_POST['sortOrder'])?$_POST['sortOrder']:'asc'; + $sortField = 'TimeKey'; + if ( isset($_POST['sortField']) ) { + if ( ! in_array( $_POST['sortField'], $filterFields ) and ( $_POST['sortField'] != 'TimeKey' ) ) { + Error("Invalid sort field " . $_POST['sortField'] ); + } else { + $sortField = $_POST['sortField']; + } + } + $sortOrder = (isset($_POST['sortOrder']) and $_POST['sortOrder']) == 'asc' ? 'asc':'desc'; - $servers = Server::find_all(); - $servers_by_Id = array(); - # There is probably a better way to do this. - foreach ( $servers as $server ) { - $servers_by_Id[$server->Id()] = $server; - } + $servers = Server::find_all(); + $servers_by_Id = array(); + # There is probably a better way to do this. + foreach ( $servers as $server ) { + $servers_by_Id[$server->Id()] = $server; + } $sql = "select * from Logs"; $where = array(); - $values = array(); + $values = array(); if ( $minTime ) { preg_match( '/(.+)(\.\d+)/', $minTime, $matches ); $minTime = strtotime($matches[1]).$matches[2]; $where[] = "TimeKey >= ?"; - $values[] = $minTime; + $values[] = $minTime; } if ( $maxTime ) { preg_match( '/(.+)(\.\d+)/', $maxTime, $matches ); $maxTime = strtotime($matches[1]).$matches[2]; $where[] = "TimeKey <= ?"; - $values[] = $maxTime; + $values[] = $maxTime; } foreach ( $filter as $field=>$value ) { if ( $value != '' ) { if ( $field == 'Level' ) { $where[] = $field." <= ?"; - $values[] = $value; + $values[] = $value; } else { $where[] = $field." = ?'"; - $values[] = $value; - } - } - } + $values[] = $value; + } + } + } if ( count($where) ) $sql.= " where ".join( " and ", $where ); $sql .= " order by ".$sortField." ".$sortOrder; @@ -216,7 +244,7 @@ switch ( $_REQUEST['task'] ) foreach ( dbFetchAll( $sql, NULL, $values ) as $log ) { $log['DateTime'] = preg_replace( '/^\d+/', strftime( "%Y-%m-%d %H:%M:%S", intval($log['TimeKey']) ), $log['TimeKey'] ); - $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; + $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; $logs[] = $log; } switch( $format ) @@ -234,20 +262,20 @@ switch ( $_REQUEST['task'] ) } case 'tsv' : { - # This line doesn't need fprintf, it could use fwrite +# This line doesn't need fprintf, it could use fwrite fprintf( $exportFP, join( "\t", - translate('DateTime'), - translate('Component'), - translate('Server'), - translate('Pid'), - translate('Level'), - translate('Message'), - translate('File'), - translate('Line') - )."\n" ); + translate('DateTime'), + translate('Component'), + translate('Server'), + translate('Pid'), + translate('Level'), + translate('Message'), + translate('File'), + translate('Line') + )."\n" ); foreach ( $logs as $log ) { - fprintf( $exportFP, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n", $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line'] ); + fprintf( $exportFP, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n", $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line'] ); } break; } @@ -394,7 +422,7 @@ tr.log-dbg td { } $exportFile = "zm-log.$exportExt"; - $exportPath = "temp/zm-log-$exportKey.$exportExt"; + $exportPath = ZM_PATH_SWAP."/zm-log-$exportKey.$exportExt"; header( "Pragma: public" ); header( "Expires: 0" ); diff --git a/web/ajax/status.php b/web/ajax/status.php index f7ed3d099..fbd91cf08 100644 --- a/web/ajax/status.php +++ b/web/ajax/status.php @@ -2,415 +2,395 @@ $statusData = array( "system" => array( - "permission" => "System", - "table" => "Monitors", - "limit" => 1, - "elements" => array( - "MonitorCount" => array( "sql" => "count(*)" ), - "ActiveMonitorCount" => array( "sql" => "count(if(Function != 'None',1,NULL))" ), - "State" => array( "func" => "daemonCheck()?".translate('Running').":".translate('Stopped') ), - "Load" => array( "func" => "getLoad()" ), - "Disk" => array( "func" => "getDiskPercent()" ), + "permission" => "System", + "table" => "Monitors", + "limit" => 1, + "elements" => array( + "MonitorCount" => array( "sql" => "count(*)" ), + "ActiveMonitorCount" => array( "sql" => "count(if(Function != 'None',1,NULL))" ), + "State" => array( "func" => "daemonCheck()?".translate('Running').":".translate('Stopped') ), + "Load" => array( "func" => "getLoad()" ), + "Disk" => array( "func" => "getDiskPercent()" ), ), - ), + ), "monitor" => array( - "permission" => "Monitors", - "table" => "Monitors", - "limit" => 1, - "selector" => "Monitors.Id", - "elements" => array( - "Id" => array( "sql" => "Monitors.Id" ), - "Name" => array( "sql" => "Monitors.Name" ), - "Type" => true, - "Function" => true, - "Enabled" => true, - "LinkedMonitors" => true, - "Triggers" => true, - "Device" => true, - "Channel" => true, - "Format" => true, - "Host" => true, - "Port" => true, - "Path" => true, - "Width" => array( "sql" => "Monitors.Width" ), - "Height" => array( "sql" => "Monitors.Height" ), - "Palette" => true, - "Orientation" => true, - "Brightness" => true, - "Contrast" => true, - "Hue" => true, - "Colour" => true, - "EventPrefix" => true, - "LabelFormat" => true, - "LabelX" => true, - "LabelY" => true, - "LabelSize" => true, - "ImageBufferCount" => true, - "WarmupCount" => true, - "PreEventCount" => true, - "PostEventCount" => true, - "AlarmFrameCount" => true, - "SectionLength" => true, - "FrameSkip" => true, - "MotionFrameSkip" => true, - "MaxFPS" => true, - "AlarmMaxFPS" => true, - "FPSReportInterval" => true, - "RefBlendPerc" => true, - "Controllable" => true, - "ControlId" => true, - "ControlDevice" => true, - "ControlAddress" => true, - "AutoStopTimeout" => true, - "TrackMotion" => true, - "TrackDelay" => true, - "ReturnLocation" => true, - "ReturnDelay" => true, - "DefaultView" => true, - "DefaultRate" => true, - "DefaultScale" => true, - "WebColour" => true, - "Sequence" => true, - "MinEventId" => array( "sql" => "min(Events.Id)", "table" => "Events", "join" => "Events.MonitorId = Monitors.Id", "group" => "Events.MonitorId" ), - "MaxEventId" => array( "sql" => "max(Events.Id)", "table" => "Events", "join" => "Events.MonitorId = Monitors.Id", "group" => "Events.MonitorId" ), - "TotalEvents" => array( "sql" => "count(Events.Id)", "table" => "Events", "join" => "Events.MonitorId = Monitors.Id", "group" => "Events.MonitorId" ), - "Status" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -s" ), - "FrameRate" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -f" ), + "permission" => "Monitors", + "table" => "Monitors", + "limit" => 1, + "selector" => "Monitors.Id", + "elements" => array( + "Id" => array( "sql" => "Monitors.Id" ), + "Name" => array( "sql" => "Monitors.Name" ), + "Type" => true, + "Function" => true, + "Enabled" => true, + "LinkedMonitors" => true, + "Triggers" => true, + "Device" => true, + "Channel" => true, + "Format" => true, + "Host" => true, + "Port" => true, + "Path" => true, + "Width" => array( "sql" => "Monitors.Width" ), + "Height" => array( "sql" => "Monitors.Height" ), + "Palette" => true, + "Orientation" => true, + "Brightness" => true, + "Contrast" => true, + "Hue" => true, + "Colour" => true, + "EventPrefix" => true, + "LabelFormat" => true, + "LabelX" => true, + "LabelY" => true, + "LabelSize" => true, + "ImageBufferCount" => true, + "WarmupCount" => true, + "PreEventCount" => true, + "PostEventCount" => true, + "AlarmFrameCount" => true, + "SectionLength" => true, + "FrameSkip" => true, + "MotionFrameSkip" => true, + "MaxFPS" => true, + "AlarmMaxFPS" => true, + "FPSReportInterval" => true, + "RefBlendPerc" => true, + "Controllable" => true, + "ControlId" => true, + "ControlDevice" => true, + "ControlAddress" => true, + "AutoStopTimeout" => true, + "TrackMotion" => true, + "TrackDelay" => true, + "ReturnLocation" => true, + "ReturnDelay" => true, + "DefaultView" => true, + "DefaultRate" => true, + "DefaultScale" => true, + "WebColour" => true, + "Sequence" => true, + "MinEventId" => array( "sql" => "(SELECT min(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), + "MaxEventId" => array( "sql" => "(SELECT max(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), + "TotalEvents" => array( "sql" => "(SELECT count(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), + "Status" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -s" ), + "FrameRate" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -f" ), ), - ), - "events" => array( - "permission" => "Events", - "table" => "Events", - "selector" => "Events.MonitorId", - "elements" => array( - "Id" => true, - "Name" => true, - "Cause" => true, - "Notes" => true, - "StartTime" => true, - "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), - "EndTime" => true, - "Width" => true, - "Height" => true, - "Length" => true, - "Frames" => true, - "AlarmFrames" => true, - "TotScore" => true, - "AvgScore" => true, - "MaxScore" => true, - ), - ), - "event" => array( - "permission" => "Events", - "table" => "Events", - "limit" => 1, - "selector" => "Events.Id", - "elements" => array( - "Id" => array( "sql" => "Events.Id" ), - "MonitorId" => true, - "Name" => true, - "Cause" => true, - "StartTime" => true, - "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), - "EndTime" => true, - "Width" => true, - "Height" => true, - "Length" => true, - "Frames" => true, - "AlarmFrames" => true, - "TotScore" => true, - "AvgScore" => true, - "MaxScore" => true, - "Archived" => true, - "Videoed" => true, - "Uploaded" => true, - "Emailed" => true, - "Messaged" => true, - "Executed" => true, - "Notes" => true, - "MinFrameId" => array( "sql" => "min(Frames.FrameId)", "table" => "Frames", "join" => "Events.Id = Frames.EventId", "group" => "Frames.EventId" ), - "MaxFrameId" => array( "sql" => "max(Frames.FrameId)", "table" => "Frames", "join" => "Events.Id = Frames.EventId", "group" => "Frames.EventId" ), - "MinFrameDelta" => array( "sql" => "min(Frames.Delta)", "table" => "Frames", "join" => "Events.Id = Frames.EventId", "group" => "Frames.EventId" ), - "MaxFrameDelta" => array( "sql" => "max(Frames.Delta)", "table" => "Frames", "join" => "Events.Id = Frames.EventId", "group" => "Frames.EventId" ), - //"Path" => array( "postFunc" => "getEventPath" ), - ), - ), - "frame" => array( - "permission" => "Events", - "table" => "Frames", - "limit" => 1, - "selector" => array( array( "table" => "Events", "join" => "Events.Id = Frames.EventId", "selector"=>"Events.Id" ), "Frames.FrameId" ), - "elements" => array( - //"Id" => array( "sql" => "Frames.FrameId" ), - "FrameId" => true, - "EventId" => true, - "Type" => true, - "TimeStamp" => true, - "TimeStampShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), - "Delta" => true, - "Score" => true, - //"Image" => array( "postFunc" => "getFrameImage" ), - ), - ), - "frameimage" => array( - "permission" => "Events", - "func" => "getFrameImage()" - ), - "nearframe" => array( - "permission" => "Events", - "func" => "getNearFrame()" - ), - "nearevents" => array( - "permission" => "Events", - "func" => "getNearEvents()" - ) -); + ), + "events" => array( + "permission" => "Events", + "table" => "Events", + "selector" => "Events.MonitorId", + "elements" => array( + "Id" => true, + "Name" => true, + "Cause" => true, + "Notes" => true, + "StartTime" => true, + "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), + "EndTime" => true, + "Width" => true, + "Height" => true, + "Length" => true, + "Frames" => true, + "AlarmFrames" => true, + "TotScore" => true, + "AvgScore" => true, + "MaxScore" => true, + ), + ), + "event" => array( + "permission" => "Events", + "table" => "Events", + "limit" => 1, + "selector" => "Events.Id", + "elements" => array( + "Id" => array( "sql" => "Events.Id" ), + "MonitorId" => true, + "Name" => true, + "Cause" => true, + "StartTime" => true, + "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), + "EndTime" => true, + "Width" => true, + "Height" => true, + "Length" => true, + "Frames" => true, + "DefaultVideo" => true, + "AlarmFrames" => true, + "TotScore" => true, + "AvgScore" => true, + "MaxScore" => true, + "Archived" => true, + "Videoed" => true, + "Uploaded" => true, + "Emailed" => true, + "Messaged" => true, + "Executed" => true, + "Notes" => true, + "MinFrameId" => array( "sql" => "(SELECT min(Frames.FrameId) FROM Frames WHERE EventId=Events.Id)" ), + "MaxFrameId" => array( "sql" => "(SELECT max(Frames.FrameId) FROM Frames WHERE Events.Id = Frames.EventId)" ), + "MinFrameDelta" => array( "sql" => "(SELECT min(Frames.Delta) FROM Frames WHERE Events.Id = Frames.EventId)" ), + "MaxFrameDelta" => array( "sql" => "(SELECT max(Frames.Delta) FROM Frames WHERE Events.Id = Frames.EventId)" ), + //"Path" => array( "postFunc" => "getEventPath" ), + ), + ), + "frame" => array( + "permission" => "Events", + "table" => "Frames", + "limit" => 1, + "selector" => array( array( "table" => "Events", "join" => "Events.Id = Frames.EventId", "selector"=>"Events.Id" ), "Frames.FrameId" ), + "elements" => array( + //"Id" => array( "sql" => "Frames.FrameId" ), + "FrameId" => true, + "EventId" => true, + "Type" => true, + "TimeStamp" => true, + "TimeStampShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), + "Delta" => true, + "Score" => true, + //"Image" => array( "postFunc" => "getFrameImage" ), + ), + ), + "frameimage" => array( + "permission" => "Events", + "func" => "getFrameImage()" + ), + "nearframe" => array( + "permission" => "Events", + "func" => "getNearFrame()" + ), + "nearevents" => array( + "permission" => "Events", + "func" => "getNearEvents()" + ) + ); -function collectData() -{ - global $statusData; +function collectData() { + global $statusData; - $entitySpec = &$statusData[strtolower(validJsStr($_REQUEST['entity']))]; - #print_r( $entitySpec ); - if ( !canView( $entitySpec['permission'] ) ) - ajaxError( 'Unrecognised action or insufficient permissions' ); + $entitySpec = &$statusData[strtolower(validJsStr($_REQUEST['entity']))]; +#print_r( $entitySpec ); + if ( !canView( $entitySpec['permission'] ) ) + ajaxError( 'Unrecognised action or insufficient permissions' ); - if ( !empty($entitySpec['func']) ) - { - $data = eval( "return( ".$entitySpec['func']." );" ); + if ( !empty($entitySpec['func']) ) { + $data = eval( "return( ".$entitySpec['func']." );" ); + } else { + $data = array(); + $postFuncs = array(); + + $fieldSql = array(); + $joinSql = array(); + $groupSql = array(); + + $elements = &$entitySpec['elements']; + $lc_elements = array_change_key_case( $elements ); + + $id = false; + if ( isset($_REQUEST['id']) ) + if ( !is_array($_REQUEST['id']) ) + $id = array( validJsStr($_REQUEST['id']) ); + else + $id = array_values( $_REQUEST['id'] ); + + if ( !isset($_REQUEST['element']) ) + $_REQUEST['element'] = array_keys( $elements ); + else if ( !is_array($_REQUEST['element']) ) + $_REQUEST['element'] = array( validJsStr($_REQUEST['element']) ); + + if ( isset($entitySpec['selector']) ) { + if ( !is_array($entitySpec['selector']) ) + $entitySpec['selector'] = array( $entitySpec['selector'] ); + foreach( $entitySpec['selector'] as $selector ) + if ( is_array( $selector ) && isset($selector['table']) && isset($selector['join']) ) + $joinSql[] = "left join ".$selector['table']." on ".$selector['join']; } - else - { - $data = array(); - $postFuncs = array(); - $fieldSql = array(); - $joinSql = array(); - $groupSql = array(); - - $elements = &$entitySpec['elements']; - $lc_elements = array_change_key_case( $elements ); - - $id = false; - if ( isset($_REQUEST['id']) ) - if ( !is_array($_REQUEST['id']) ) - $id = array( validJsStr($_REQUEST['id']) ); - else - $id = array_values( $_REQUEST['id'] ); - - if ( !isset($_REQUEST['element']) ) - $_REQUEST['element'] = array_keys( $elements ); - else if ( !is_array($_REQUEST['element']) ) - $_REQUEST['element'] = array( validJsStr($_REQUEST['element']) ); - - if ( isset($entitySpec['selector']) ) - { - if ( !is_array($entitySpec['selector']) ) - $entitySpec['selector'] = array( $entitySpec['selector'] ); - foreach( $entitySpec['selector'] as $selector ) - if ( is_array( $selector ) && isset($selector['table']) && isset($selector['join']) ) - $joinSql[] = "left join ".$selector['table']." on ".$selector['join']; + foreach ( $_REQUEST['element'] as $element ) { + if ( !($elementData = $lc_elements[strtolower($element)]) ) + ajaxError( "Bad ".validJsStr($_REQUEST['entity'])." element ".$element ); + if ( isset($elementData['func']) ) + $data[$element] = eval( "return( ".$elementData['func']." );" ); + else if ( isset($elementData['postFunc']) ) + $postFuncs[$element] = $elementData['postFunc']; + else if ( isset($elementData['zmu']) ) + $data[$element] = exec( escapeshellcmd( getZmuCommand( " ".$elementData['zmu'] ) ) ); + else { + if ( isset($elementData['sql']) ) + $fieldSql[] = $elementData['sql']." as ".$element; + else + $fieldSql[] = $element; + if ( isset($elementData['table']) && isset($elementData['join']) ) { + $joinSql[] = "left join ".$elementData['table']." on ".$elementData['join']; } - - foreach ( $_REQUEST['element'] as $element ) - { - if ( !($elementData = $lc_elements[strtolower($element)]) ) - ajaxError( "Bad ".validJsStr($_REQUEST['entity'])." element ".$element ); - if ( isset($elementData['func']) ) - $data[$element] = eval( "return( ".$elementData['func']." );" ); - else if ( isset($elementData['postFunc']) ) - $postFuncs[$element] = $elementData['postFunc']; - else if ( isset($elementData['zmu']) ) - $data[$element] = exec( escapeshellcmd( getZmuCommand( " ".$elementData['zmu'] ) ) ); - else - { - if ( isset($elementData['sql']) ) - $fieldSql[] = $elementData['sql']." as ".$element; - else - $fieldSql[] = $element; - if ( isset($elementData['table']) && isset($elementData['join']) ) - { - $joinSql[] = "left join ".$elementData['table']." on ".$elementData['join']; - } - if ( isset($elementData['group']) ) - { - $groupSql[] = $elementData['group']; - } - } - } - - if ( count($fieldSql) ) - { - $sql = "select ".join( ", ", $fieldSql )." from ".$entitySpec['table']; - if ( $joinSql ) - $sql .= " ".join( " ", array_unique( $joinSql ) ); - if ( $id && !empty($entitySpec['selector']) ) - { - $index = 0; - $where = array(); - $values = array(); - foreach( $entitySpec['selector'] as $selector ) { - if ( is_array( $selector ) ) { - $where[] = $selector['selector'].' = ?'; - $values[] = validInt($id[$index]); - } else { - $where[] = $selector.' = ?'; - $values[] = validInt($id[$index]); - } - $index++; - } - $sql .= " where ".join( " and ", $where ); - } - if ( $groupSql ) - $sql .= " group by ".join( ",", array_unique( $groupSql ) ); - if ( !empty($_REQUEST['sort']) ) - $sql .= " order by ".$_REQUEST['sort']; - if ( !empty($entitySpec['limit']) ) - $limit = $entitySpec['limit']; - elseif ( !empty($_REQUEST['count']) ) - $limit = validInt($_REQUEST['count']); - $limit_offset=""; - if ( !empty($_REQUEST['offset']) ) - $limit_offset = validInt($_REQUEST['offset']) . ", "; - if ( !empty( $limit ) ) - $sql .= " limit ".$limit_offset.$limit; - if ( isset($limit) && $limit == 1 ) { - if ( $sqlData = dbFetchOne( $sql, NULL, $values ) ) { - foreach ( $postFuncs as $element=>$func ) - $sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' ); - $data = array_merge( $data, $sqlData ); - } - } else { - $count = 0; - foreach( dbFetchAll( $sql, NULL, $values ) as $sqlData ) { - foreach ( $postFuncs as $element=>$func ) - $sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' ); - $data[] = $sqlData; - if ( isset($limi) && ++$count >= $limit ) - break; - } - } + if ( isset($elementData['group']) ) { + $groupSql[] = $elementData['group']; } + } } - #print_r( $data ); - return( $data ); + + if ( count($fieldSql) ) { + $sql = "select ".join( ", ", $fieldSql )." from ".$entitySpec['table']; + if ( $joinSql ) + $sql .= " ".join( " ", array_unique( $joinSql ) ); + if ( $id && !empty($entitySpec['selector']) ) { + $index = 0; + $where = array(); + $values = array(); + foreach( $entitySpec['selector'] as $selector ) { + if ( is_array( $selector ) ) { + $where[] = $selector['selector'].' = ?'; + $values[] = validInt($id[$index]); + } else { + $where[] = $selector.' = ?'; + $values[] = validInt($id[$index]); + } + $index++; + } + $sql .= " where ".join( " and ", $where ); + } + if ( $groupSql ) + $sql .= " GROUP BY ".join( ",", array_unique( $groupSql ) ); + if ( !empty($_REQUEST['sort']) ) + $sql .= " order by ".$_REQUEST['sort']; + if ( !empty($entitySpec['limit']) ) + $limit = $entitySpec['limit']; + elseif ( !empty($_REQUEST['count']) ) + $limit = validInt($_REQUEST['count']); + $limit_offset=""; + if ( !empty($_REQUEST['offset']) ) + $limit_offset = validInt($_REQUEST['offset']) . ", "; + if ( !empty( $limit ) ) + $sql .= " limit ".$limit_offset.$limit; + if ( isset($limit) && $limit == 1 ) { + if ( $sqlData = dbFetchOne( $sql, NULL, $values ) ) { + foreach ( $postFuncs as $element=>$func ) + $sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' ); + $data = array_merge( $data, $sqlData ); + } + } else { + $count = 0; + foreach( dbFetchAll( $sql, NULL, $values ) as $sqlData ) { + foreach ( $postFuncs as $element=>$func ) + $sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' ); + $data[] = $sqlData; + if ( isset($limi) && ++$count >= $limit ) + break; + } + } + } + } +#print_r( $data ); + return( $data ); } $data = collectData(); -if ( !isset($_REQUEST['layout']) ) -{ - $_REQUEST['layout'] = "json"; +if ( !isset($_REQUEST['layout']) ) { + $_REQUEST['layout'] = "json"; } -switch( $_REQUEST['layout'] ) -{ - case 'xml NOT CURRENTLY SUPPORTED' : + +switch( $_REQUEST['layout'] ) { + case 'xml NOT CURRENTLY SUPPORTED' : { - header("Content-type: application/xml" ); - echo( ''."\n" ); - echo "<".strtolower($_REQUEST['entity']).">\n"; - foreach ( $data as $key=>$value ) - { - $key = strtolower( $key ); - echo "<$key>".htmlentities($value)."\n"; - } - echo "\n"; - break; + header("Content-type: application/xml" ); + echo( ''."\n" ); + echo "<".strtolower($_REQUEST['entity']).">\n"; + foreach ( $data as $key=>$value ) { + $key = strtolower( $key ); + echo "<$key>".htmlentities($value)."\n"; + } + echo "\n"; + break; } - case 'json' : + case 'json' : { - $response = array( strtolower(validJsStr($_REQUEST['entity'])) => $data ); - if ( isset($_REQUEST['loopback']) ) - $response['loopback'] = validJsStr($_REQUEST['loopback']); - ajaxResponse( $response ); - break; + $response = array( strtolower(validJsStr($_REQUEST['entity'])) => $data ); + if ( isset($_REQUEST['loopback']) ) + $response['loopback'] = validJsStr($_REQUEST['loopback']); + ajaxResponse( $response ); + break; } - case 'text' : + case 'text' : { - header("Content-type: text/plain" ); - echo join( " ", array_values( $data ) ); - break; + header("Content-type: text/plain" ); + echo join( " ", array_values( $data ) ); + break; } } -function getFrameImage() -{ - $eventId = $_REQUEST['id'][0]; - $frameId = $_REQUEST['id'][1]; +function getFrameImage() { + $eventId = $_REQUEST['id'][0]; + $frameId = $_REQUEST['id'][1]; - $sql = 'select * from Frames where EventId = ? and FrameId = ?'; - if ( !($frame = dbFetchOne( $sql, NULL, array( $eventId, $frameId ) )) ) - { - $frame = array(); - $frame['EventId'] = $eventId; - $frame['FrameId'] = $frameId; - $frame['Type'] = "Virtual"; - } - $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $frame['EventId'] ) ); - $frame['Image'] = getImageSrc( $event, $frame, SCALE_BASE ); - return( $frame ); + $sql = 'select * from Frames where EventId = ? and FrameId = ?'; + if ( !($frame = dbFetchOne( $sql, NULL, array( $eventId, $frameId ) )) ) { + $frame = array(); + $frame['EventId'] = $eventId; + $frame['FrameId'] = $frameId; + $frame['Type'] = "Virtual"; + } + $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $frame['EventId'] ) ); + $frame['Image'] = getImageSrc( $event, $frame, SCALE_BASE ); + return( $frame ); } -function getNearFrame() -{ - $eventId = $_REQUEST['id'][0]; - $frameId = $_REQUEST['id'][1]; +function getNearFrame() { + $eventId = $_REQUEST['id'][0]; + $frameId = $_REQUEST['id'][1]; - $sql = 'select FrameId from Frames where EventId = ? and FrameId <= ? order by FrameId desc limit 1'; - if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) - { - $sql = 'select * from Frames where EventId = ? and FrameId > ? order by FrameId asc limit 1'; - if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) - { - return( array() ); - } + $sql = 'select FrameId from Frames where EventId = ? and FrameId <= ? order by FrameId desc limit 1'; + if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) { + $sql = 'select * from Frames where EventId = ? and FrameId > ? order by FrameId asc limit 1'; + if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) { + return( array() ); } - $_REQUEST['entity'] = "frame"; - $_REQUEST['id'][1] = $nearFrameId; - return( collectData() ); + } + $_REQUEST['entity'] = "frame"; + $_REQUEST['id'][1] = $nearFrameId; + return( collectData() ); } -function getNearEvents() -{ - global $user, $sortColumn, $sortOrder; +function getNearEvents() { + global $user, $sortColumn, $sortOrder; - $eventId = $_REQUEST['id']; - $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $eventId ) ); + $eventId = $_REQUEST['id']; + $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $eventId ) ); - parseFilter( $_REQUEST['filter'] ); - parseSort(); + parseFilter( $_REQUEST['filter'] ); + parseSort(); - if ( $user['MonitorIds'] ) - $midSql = " and MonitorId in (".join( ",", preg_split( '/["\'\s]*,["\'\s]*/', $user['MonitorIds'] ) ).")"; - else - $midSql = ''; + if ( $user['MonitorIds'] ) + $midSql = " and MonitorId in (".join( ",", preg_split( '/["\'\s]*,["\'\s]*/', $user['MonitorIds'] ) ).")"; + else + $midSql = ''; - $sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where ".dbEscape($sortColumn)." ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn ".($sortOrder=='asc'?'desc':'asc'); - $result = dbQuery( $sql ); - while ( $id = dbFetchNext( $result, 'Id' ) ) - { - if ( $id == $eventId ) - { - $prevId = dbFetchNext( $result, 'Id' ); - break; - } + $sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where ".dbEscape($sortColumn)." ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn ".($sortOrder=='asc'?'desc':'asc'); + $result = dbQuery( $sql ); + while ( $id = dbFetchNext( $result, 'Id' ) ) { + if ( $id == $eventId ) { + $prevEvent = dbFetchNext( $result ); + break; } + } - $sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn $sortOrder"; - $result = dbQuery( $sql ); - while ( $id = dbFetchNext( $result, 'Id' ) ) - { - if ( $id == $eventId ) - { - $nextId = dbFetchNext( $result, 'Id' ); - break; - } + $sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn $sortOrder"; + $result = dbQuery( $sql ); + while ( $id = dbFetchNext( $result, 'Id' ) ) { + if ( $id == $eventId ) { + $nextEvent = dbFetchNext( $result ); + break; } + } - $result = array( 'EventId'=>$eventId ); - $result['PrevEventId'] = empty($prevId)?0:$prevId; - $result['NextEventId'] = empty($nextId)?0:$nextId; - return( $result ); + $result = array( 'EventId'=>$eventId ); + $result['PrevEventId'] = empty($prevEvent)?0:$prevEvent['Id']; + $result['NextEventId'] = empty($nextEvent)?0:$nextEvent['Id']; + $result['PrevEventDefVideoPath'] = empty($prevEvent)?0:(getEventDefaultVideoPath($prevEvent)); + $result['NextEventDefVideoPath'] = empty($nextEvent)?0:(getEventDefaultVideoPath($nextEvent)); + return( $result ); } ?> diff --git a/web/ajax/stream.php b/web/ajax/stream.php index 5e9798011..4b39c0ac0 100644 --- a/web/ajax/stream.php +++ b/web/ajax/stream.php @@ -21,23 +21,23 @@ if ( !@socket_bind( $socket, $locSockFile ) ) switch ( $_REQUEST['command'] ) { case CMD_VARPLAY : - Debug( "Varplaying to ".$_REQUEST['rate'] ); + Logger::Debug( "Varplaying to ".$_REQUEST['rate'] ); $msg = pack( "lcn", MSG_CMD, $_REQUEST['command'], $_REQUEST['rate']+32768 ); break; case CMD_ZOOMIN : - Debug( "Zooming to ".$_REQUEST['x'].",".$_REQUEST['y'] ); + Logger::Debug( "Zooming to ".$_REQUEST['x'].",".$_REQUEST['y'] ); $msg = pack( "lcnn", MSG_CMD, $_REQUEST['command'], $_REQUEST['x'], $_REQUEST['y'] ); break; case CMD_PAN : - Debug( "Panning to ".$_REQUEST['x'].",".$_REQUEST['y'] ); + Logger::Debug( "Panning to ".$_REQUEST['x'].",".$_REQUEST['y'] ); $msg = pack( "lcnn", MSG_CMD, $_REQUEST['command'], $_REQUEST['x'], $_REQUEST['y'] ); break; case CMD_SCALE : - Debug( "Scaling to ".$_REQUEST['scale'] ); + Logger::Debug( "Scaling to ".$_REQUEST['scale'] ); $msg = pack( "lcn", MSG_CMD, $_REQUEST['command'], $_REQUEST['scale'] ); break; case CMD_SEEK : - Debug( "Seeking to ".$_REQUEST['offset'] ); + Logger::Debug( "Seeking to ".$_REQUEST['offset'] ); $msg = pack( "lcN", MSG_CMD, $_REQUEST['command'], $_REQUEST['offset'] ); break; default : @@ -47,12 +47,16 @@ switch ( $_REQUEST['command'] ) $remSockFile = ZM_PATH_SOCKS.'/zms-'.sprintf("%06d",$_REQUEST['connkey']).'s.sock'; $max_socket_tries = 10; -while ( !file_exists($remSockFile) && $max_socket_tries-- ) //sometimes we are too fast for our own good, if it hasn't been setup yet give it a second. - usleep(200000); +while ( !file_exists($remSockFile) && $max_socket_tries-- ) { //sometimes we are too fast for our own good, if it hasn't been setup yet give it a second. + usleep(200000); +} -if ( !@socket_sendto( $socket, $msg, strlen($msg), 0, $remSockFile ) ) -{ +if ( !file_exists($remSockFile) ) { + ajaxError("Socket $remSockFile does not exist. This file is created by zms, and since it does not exist, either zms did not run, or zms exited early. Please check your zms logs and ensure that CGI is enabled in apache and check that the PATH_ZMS is set correctly. Make sure that ZM is actually recording. If you are trying to view a live stream and the capture process (zmc) is not running then zms will exit. Please go to http://zoneminder.readthedocs.io/en/latest/faq.html#why-can-t-i-see-streamed-images-when-i-can-see-stills-in-the-zone-window-etc for more information."); +} else { + if ( !@socket_sendto( $socket, $msg, strlen($msg), 0, $remSockFile ) ) { ajaxError( "socket_sendto( $remSockFile ) failed: ".socket_strerror(socket_last_error()) ); + } } $rSockets = array( $socket ); diff --git a/web/api/CMakeLists.txt b/web/api/CMakeLists.txt index 3d865527b..d2a39ef13 100644 --- a/web/api/CMakeLists.txt +++ b/web/api/CMakeLists.txt @@ -11,5 +11,8 @@ configure_file(app/Config/database.php.default "${CMAKE_CURRENT_BINARY_DIR}/app/ # Configure core.php configure_file(app/Config/core.php.default "${CMAKE_CURRENT_BINARY_DIR}/app/Config/core.php" @ONLY) -# Configure bootstrap.php +# Configure app/Config/bootstrap.php configure_file(app/Config/bootstrap.php.in "${CMAKE_CURRENT_BINARY_DIR}/app/Config/bootstrap.php" @ONLY) + +# Configure lib/Cake/bootstrap.php +configure_file(lib/Cake/bootstrap.php.in "${CMAKE_CURRENT_BINARY_DIR}/lib/Cake/bootstrap.php" @ONLY) diff --git a/web/api/app/Config/bootstrap.php.in b/web/api/app/Config/bootstrap.php.in index 7210fd35c..ac3bcb62c 100644 --- a/web/api/app/Config/bootstrap.php.in +++ b/web/api/app/Config/bootstrap.php.in @@ -23,7 +23,7 @@ */ // Setup a 'default' cache configuration for use in the application. -Cache::config('default', array('engine' => 'File')); +Cache::config('default', array('engine' => 'Apc')); /** * The settings below can be used to set additional paths to models, views and controllers. @@ -100,12 +100,16 @@ App::uses('CakeLog', 'Log'); CakeLog::config('debug', array( 'engine' => 'File', 'types' => array('notice', 'info', 'debug'), - 'file' => 'debug', + 'file' => 'cake_debug', )); CakeLog::config('error', array( 'engine' => 'File', 'types' => array('warning', 'error', 'critical', 'alert', 'emergency'), - 'file' => 'error', + 'file' => 'cake_error', +)); +CakeLog::config('custom_path', array( + 'engine' => 'File', + 'path' => '@ZM_LOGDIR@' )); Configure::write('ZM_CONFIG', '@ZM_CONFIG@'); diff --git a/web/api/app/Config/core.php.default b/web/api/app/Config/core.php.default index 43736a61f..cffb43c77 100644 --- a/web/api/app/Config/core.php.default +++ b/web/api/app/Config/core.php.default @@ -31,7 +31,7 @@ * In production mode, flash messages redirect after a time interval. * In development mode, you need to click the flash message to continue. */ - Configure::write('debug', 2); + Configure::write('debug', 0); /** * Configure the Error handler used to handle errors for your application. By default @@ -352,7 +352,7 @@ * Please check the comments in bootstrap.php for more info on the cache engines available * and their settings. */ -$engine = 'File'; +$engine = 'Apc'; // In development mode, caches should expire quickly. $duration = '+999 days'; diff --git a/web/api/app/Config/database.php.default b/web/api/app/Config/database.php.default index be4e2a902..55f2bc958 100644 --- a/web/api/app/Config/database.php.default +++ b/web/api/app/Config/database.php.default @@ -71,7 +71,7 @@ class DATABASE_CONFIG { 'password' => ZM_DB_PASS, 'database' => ZM_DB_NAME, 'prefix' => '', - //'encoding' => 'utf8', + 'encoding' => 'utf8', ); public $test = array( @@ -88,7 +88,7 @@ class DATABASE_CONFIG { public function __construct() { if (strpos(ZM_DB_HOST, ':')): $array = explode(':', ZM_DB_HOST, 2); - if (is_numeric($array[1])): + if (ctype_digit($array[1])): $this->default['host'] = $array[0]; $this->default['port'] = $array[1]; else: diff --git a/web/api/app/Console/Command/Task/empty b/web/api/app/Console/Command/Task/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/Console/Templates/empty b/web/api/app/Console/Templates/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/Controller/AppController.php b/web/api/app/Controller/AppController.php index 3ba9cb551..aa5533bc9 100644 --- a/web/api/app/Controller/AppController.php +++ b/web/api/app/Controller/AppController.php @@ -69,62 +69,89 @@ class AppController extends Controller { public function beforeFilter() { $this->loadModel('Config'); - $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_API')); - $config = $this->Config->find('first', $options); - $zmOptApi = $config['Config']['Value']; + $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_API')); + $config = $this->Config->find('first', $options); + $zmOptApi = $config['Config']['Value']; - if ($zmOptApi !='1') - { - throw new UnauthorizedException(__('API Disabled')); - return; + if ($zmOptApi !='1') { + throw new UnauthorizedException(__('API Disabled')); + return; } - $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH')); - $config = $this->Config->find('first', $options); - $zmOptAuth = $config['Config']['Value']; - if (!$this->Session->Read('user.Username') && ($zmOptAuth=='1')) - { - throw new UnauthorizedException(__('Not Authenticated')); - return; - } - else - { - $this->loadModel('User'); - $loggedinUser = $this->Session->Read('user.Username'); - $isEnabled = $this->Session->Read('user.Enabled'); - // this will likely never happen as if its - // not enabled, login will fail and Not Auth will be returned - // however, keeping this here for now - if ($isEnabled != "1" && $zmOptAuth=="1") - { - throw new UnauthorizedException(__('User is not enabled')); - return; - } + $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH')); + $config = $this->Config->find('first', $options); + $zmOptAuth = $config['Config']['Value']; - if ($zmOptAuth=='1') - { - $options = array ('conditions' => array ('User.Username' => $loggedinUser)); - $userMonitors = $this->User->find('first', $options); - $this->Session->Write('allowedMonitors',$userMonitors['User']['MonitorIds']); - $this->Session->Write('streamPermission',$userMonitors['User']['Stream']); - $this->Session->Write('eventPermission',$userMonitors['User']['Events']); - $this->Session->Write('controlPermission',$userMonitors['User']['Control']); - $this->Session->Write('systemPermission',$userMonitors['User']['System']); - $this->Session->Write('monitorPermission',$userMonitors['User']['Monitors']); - } - else // if auth is not on, you can do everything - { - //$userMonitors = $this->User->find('first', $options); - $this->Session->Write('allowedMonitors',''); - $this->Session->Write('streamPermission','View'); - $this->Session->Write('eventPermission','Edit'); - $this->Session->Write('controlPermission','Edit'); - $this->Session->Write('systemPermission','Edit'); - $this->Session->Write('monitorPermission','Edit'); - } - } - - + if ( $zmOptAuth=='1' ) { + $this->loadModel('User'); + if ( isset($_REQUEST['user']) and isset($_REQUEST['pass']) ) { + $user = $this->User->find('first', array ('conditions' => array ( + 'User.Username' => $_REQUEST['user'], + 'User.Password' => $_REQUEST['pass'], + )) ); + if ( ! $user ) { + throw new UnauthorizedException(__('User not found')); + return; + } else { + $this->Session->Write( 'user.Username', $user['User']['Username'] ); + $this->Session->Write( 'user.Enabled', $user['User']['Enabled'] ); + } + } + + if ( isset($_REQUEST['auth']) ) { + require_once "../../../includes/functions.php"; + + // Define some defines required by getAuthUser in functions.php + $defines = array('ZM_AUTH_HASH_IPS', 'ZM_AUTH_HASH_SECRET', 'ZM_AUTH_RELAY', 'ZM_OPT_USE_AUTH'); + $configQuery = array( + 'conditions' => array('OR' => array('Name' => $defines)), + 'fields' => array('Name', 'Value') + ); + $config = $this->Config->find('list', $configQuery); + + foreach ($defines as $define) { + define($define, $config[$define]); + } + + $user = getAuthUser($_REQUEST['auth']); + if ( ! $user ) { + throw new UnauthorizedException(__('User not found')); + return; + } else { + $this->Session->Write( 'user.Username', $user['Username'] ); + $this->Session->Write( 'user.Enabled', $user['Enabled'] ); + } + } + + if( ! $this->Session->Read('user.Username') ) { + throw new UnauthorizedException(__('Not Authenticated')); + return; + } else if ( ! $this->Session->Read('user.Enabled') ) { + throw new UnauthorizedException(__('User is not enabled')); + return; + } + + $options = array ('conditions' => array ('User.Username' => $this->Session->Read('user.Username'))); + $userMonitors = $this->User->find('first', $options); + $this->Session->Write('allowedMonitors',$userMonitors['User']['MonitorIds']); + $this->Session->Write('streamPermission',$userMonitors['User']['Stream']); + $this->Session->Write('eventPermission',$userMonitors['User']['Events']); + $this->Session->Write('controlPermission',$userMonitors['User']['Control']); + $this->Session->Write('systemPermission',$userMonitors['User']['System']); + $this->Session->Write('monitorPermission',$userMonitors['User']['Monitors']); } + else // if auth is not on, you can do everything + { + //$userMonitors = $this->User->find('first', $options); + $this->Session->Write('allowedMonitors',''); + $this->Session->Write('streamPermission','View'); + $this->Session->Write('eventPermission','Edit'); + $this->Session->Write('controlPermission','Edit'); + $this->Session->Write('systemPermission','Edit'); + $this->Session->Write('monitorPermission','Edit'); + } + + + } # end function beforeFilter() } diff --git a/web/api/app/Controller/Component/ImageComponent.php b/web/api/app/Controller/Component/ImageComponent.php index f8ed7a533..fc967a2de 100644 --- a/web/api/app/Controller/Component/ImageComponent.php +++ b/web/api/app/Controller/Component/ImageComponent.php @@ -94,7 +94,10 @@ class ImageComponent extends Component { // Take the StartTime of an Event and return // the path to its location on the filesystem public function getEventPath( $event ) { - return $event['Event']['MonitorId'].'/'.strftime( "%y/%m/%d/%H/%M/%S", strtotime($event['Event']['StartTime']) ); - } + if ( $config['ZM_USE_DEEP_STORAGE'] == 1 ) + return $event['Event']['MonitorId'].'/'.strftime( "%y/%m/%d/%H/%M/%S", strtotime($event['Event']['StartTime']) ); + else + return $event['Event']['MonitorId'].'/'.$event['Event']['Id']; + } } ?> diff --git a/web/api/app/Controller/Component/empty b/web/api/app/Controller/Component/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/Controller/HostController.php b/web/api/app/Controller/HostController.php index 57b210a7e..4fdb48cba 100644 --- a/web/api/app/Controller/HostController.php +++ b/web/api/app/Controller/HostController.php @@ -99,7 +99,18 @@ class HostController extends AppController { )); } + function getTimeZone() + { + //http://php.net/manual/en/function.date-default-timezone-get.php + $tz = date_default_timezone_get(); + $this->set(array( + 'tz' => $tz, + '_serialize' => array('tz') + )); + } + function getVersion() { + //throw new UnauthorizedException(__('API Disabled')); $version = Configure::read('ZM_VERSION'); // not going to use the ZM_API_VERSION // requires recompilation and dependency on ZM upgrade diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 7bf36de75..a0a4c8c6b 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -137,9 +137,18 @@ public function beforeFilter() { 'message' => $message, '_serialize' => array('message') )); - // - restart this monitor after change - // We don't pass the request data as the monitor object because it may be a subset of the full monitor array - $this->daemonControl( $this->Monitor->id, 'restart' ); + + // - restart or stop this monitor after change + $func = $this->Monitor->find('first', array( + 'fields' => array('Function'), + 'conditions' => array('Id' => $id) + ))['Monitor']['Function']; + // We don't pass the request data as the monitor object because it may be a subset of the full monitor array + if ( $func == 'None' ) { + $this->daemonControl( $this->Monitor->id, 'stop' ); + } else { + $this->daemonControl( $this->Monitor->id, 'restart' ); + } } /** diff --git a/web/api/app/Controller/ZonesController.php b/web/api/app/Controller/ZonesController.php index be80c57fc..5fbadc2ed 100644 --- a/web/api/app/Controller/ZonesController.php +++ b/web/api/app/Controller/ZonesController.php @@ -7,38 +7,72 @@ App::uses('AppController', 'Controller'); */ class ZonesController extends AppController { +/** + * Components + * + * @var array + */ +public $components = array('RequestHandler'); + +public function beforeFilter() { + parent::beforeFilter(); + $canView = $this->Session->Read('monitorPermission'); + if ($canView =='None') + { + throw new UnauthorizedException(__('Insufficient Privileges')); + return; + } + +} + // Find all zones which belong to a MonitorId - public function forMonitor($id = null) { - $this->loadModel('Monitor'); - if (!$this->Monitor->exists($id)) { - throw new NotFoundException(__('Invalid monitor')); - } - - $this->Zone->recursive = -1; - - $zones = $this->Zone->find('all', array( - 'conditions' => array('MonitorId' => $id) - )); - $this->set(array( - 'zones' => $zones, - '_serialize' => array('zones') - )); - } +public function forMonitor($id = null) { + $this->loadModel('Monitor'); + if (!$this->Monitor->exists($id)) { + throw new NotFoundException(__('Invalid monitor')); + } + $this->Zone->recursive = -1; + $zones = $this->Zone->find('all', array( + 'conditions' => array('MonitorId' => $id) + )); + $this->set(array( + 'zones' => $zones, + '_serialize' => array('zones') + )); +} +public function index() { + $this->Zone->recursive = -1; + + $allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY); + if (!empty($allowedMonitors)) + { + $mon_options = array('Zones.MonitorId' => $allowedMonitors); + } + else + { + $mon_options=''; + } + $zones = $this->Zone->find('all',$mon_options); + $this->set(array( + 'zones' => $zones, + '_serialize' => array('zones') + )); +} /** * add method * * @return void */ - public function add() { - if ($this->request->is('post')) { - $this->Zone->create(); - if ($this->Zone->save($this->request->data)) { - return $this->flash(__('The zone has been saved.'), array('action' => 'index')); - } - } - $monitors = $this->Zone->Monitor->find('list'); - $this->set(compact('monitors')); - } + public function add() { + if ($this->request->is('post')) { + $this->Zone->create(); + if ($this->Zone->save($this->request->data)) { + return $this->flash(__('The zone has been saved.'), array('action' => 'index')); + } + } + $monitors = $this->Zone->Monitor->find('list'); + $this->set(compact('monitors')); + } /** * edit method @@ -47,23 +81,23 @@ class ZonesController extends AppController { * @param string $id * @return void */ - public function edit($id = null) { - $this->Zone->id = $id; + public function edit($id = null) { + $this->Zone->id = $id; - if (!$this->Zone->exists($id)) { - throw new NotFoundException(__('Invalid zone')); - } - if ($this->request->is(array('post', 'put'))) { - if ($this->Zone->save($this->request->data)) { - return $this->flash(__('The zone has been saved.'), array('action' => 'index')); - } - } else { - $options = array('conditions' => array('Zone.' . $this->Zone->primaryKey => $id)); - $this->request->data = $this->Zone->find('first', $options); - } - $monitors = $this->Zone->Monitor->find('list'); - $this->set(compact('monitors')); - } + if (!$this->Zone->exists($id)) { + throw new NotFoundException(__('Invalid zone')); + } + if ($this->request->is(array('post', 'put'))) { + if ($this->Zone->save($this->request->data)) { + return $this->flash(__('The zone has been saved.'), array('action' => 'index')); + } + } else { + $options = array('conditions' => array('Zone.' . $this->Zone->primaryKey => $id)); + $this->request->data = $this->Zone->find('first', $options); + } + $monitors = $this->Zone->Monitor->find('list'); + $this->set(compact('monitors')); + } /** * delete method @@ -72,49 +106,49 @@ class ZonesController extends AppController { * @param string $id * @return void */ - public function delete($id = null) { - $this->Zone->id = $id; - if (!$this->Zone->exists()) { - throw new NotFoundException(__('Invalid zone')); - } - $this->request->allowMethod('post', 'delete'); - if ($this->Zone->delete()) { - return $this->flash(__('The zone has been deleted.'), array('action' => 'index')); - } else { - return $this->flash(__('The zone could not be deleted. Please, try again.'), array('action' => 'index')); - } - } + public function delete($id = null) { + $this->Zone->id = $id; + if (!$this->Zone->exists()) { + throw new NotFoundException(__('Invalid zone')); + } + $this->request->allowMethod('post', 'delete'); + if ($this->Zone->delete()) { + return $this->flash(__('The zone has been deleted.'), array('action' => 'index')); + } else { + return $this->flash(__('The zone could not be deleted. Please, try again.'), array('action' => 'index')); + } + } - public function createZoneImage( $id = null ) { - $this->loadModel('Monitor'); - $this->Monitor->id = $id; - if (!$this->Monitor->exists()) { - throw new NotFoundException(__('Invalid zone')); - } + public function createZoneImage( $id = null ) { + $this->loadModel('Monitor'); + $this->Monitor->id = $id; + if (!$this->Monitor->exists()) { + throw new NotFoundException(__('Invalid zone')); + } - $this->loadModel('Config'); - $zm_dir_images = $this->Config->find('list', array( - 'conditions' => array('Name' => 'ZM_DIR_IMAGES'), - 'fields' => array('Name', 'Value') - )); + $this->loadModel('Config'); + $zm_dir_images = $this->Config->find('list', array( + 'conditions' => array('Name' => 'ZM_DIR_IMAGES'), + 'fields' => array('Name', 'Value') + )); - $zm_dir_images = $zm_dir_images['ZM_DIR_IMAGES']; - $zm_path_web = Configure::read('ZM_PATH_WEB'); - $zm_path_bin = Configure::read('ZM_PATH_BIN'); - $images_path = "$zm_path_web/$zm_dir_images"; + $zm_dir_images = $zm_dir_images['ZM_DIR_IMAGES']; + $zm_path_web = Configure::read('ZM_PATH_WEB'); + $zm_path_bin = Configure::read('ZM_PATH_BIN'); + $images_path = "$zm_path_web/$zm_dir_images"; - chdir($images_path); + chdir($images_path); - $command = escapeshellcmd("$zm_path_bin/zmu -z -m $id"); - system( $command, $status ); + $command = escapeshellcmd("$zm_path_bin/zmu -z -m $id"); + system( $command, $status ); - $this->set(array( - 'status' => $status, - '_serialize' => array('status') - )); + $this->set(array( + 'status' => $status, + '_serialize' => array('status') + )); - } + } } diff --git a/web/api/app/Lib/empty b/web/api/app/Lib/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/Locale/eng/LC_MESSAGES/empty b/web/api/app/Locale/eng/LC_MESSAGES/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/Model/Behavior/empty b/web/api/app/Model/Behavior/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/Model/Datasource/empty b/web/api/app/Model/Datasource/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/Plugin/empty b/web/api/app/Plugin/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/Test/Case/Controller/Component/empty b/web/api/app/Test/Case/Controller/Component/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/Test/Case/Model/Behavior/empty b/web/api/app/Test/Case/Model/Behavior/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/Test/Case/View/Helper/empty b/web/api/app/Test/Case/View/Helper/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/Test/Fixture/empty b/web/api/app/Test/Fixture/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/Vendor/empty b/web/api/app/Vendor/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/View/Elements/empty b/web/api/app/View/Elements/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/View/Scaffolds/empty b/web/api/app/View/Scaffolds/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/View/View/Elements/empty b/web/api/app/View/View/Elements/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/View/View/Scaffolds/empty b/web/api/app/View/View/Scaffolds/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/webroot/files/empty b/web/api/app/webroot/files/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/app/webroot/js/empty b/web/api/app/webroot/js/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Console/Command/Task/empty b/web/api/lib/Cake/Console/Templates/skel/Console/Command/Task/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Console/Templates/empty b/web/api/lib/Cake/Console/Templates/skel/Console/Templates/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Controller/Component/empty b/web/api/lib/Cake/Console/Templates/skel/Controller/Component/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Lib/empty b/web/api/lib/Cake/Console/Templates/skel/Lib/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Locale/eng/LC_MESSAGES/empty b/web/api/lib/Cake/Console/Templates/skel/Locale/eng/LC_MESSAGES/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Model/Behavior/empty b/web/api/lib/Cake/Console/Templates/skel/Model/Behavior/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Model/Datasource/empty b/web/api/lib/Cake/Console/Templates/skel/Model/Datasource/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Plugin/empty b/web/api/lib/Cake/Console/Templates/skel/Plugin/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Test/Case/Controller/Component/empty b/web/api/lib/Cake/Console/Templates/skel/Test/Case/Controller/Component/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Test/Case/Model/Behavior/empty b/web/api/lib/Cake/Console/Templates/skel/Test/Case/Model/Behavior/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Test/Case/View/Helper/empty b/web/api/lib/Cake/Console/Templates/skel/Test/Case/View/Helper/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Test/Fixture/empty b/web/api/lib/Cake/Console/Templates/skel/Test/Fixture/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/Vendor/empty b/web/api/lib/Cake/Console/Templates/skel/Vendor/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/View/Elements/empty b/web/api/lib/Cake/Console/Templates/skel/View/Elements/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/View/Scaffolds/empty b/web/api/lib/Cake/Console/Templates/skel/View/Scaffolds/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/webroot/files/empty b/web/api/lib/Cake/Console/Templates/skel/webroot/files/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Console/Templates/skel/webroot/js/empty b/web/api/lib/Cake/Console/Templates/skel/webroot/js/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Test/Fixture/sample.xml b/web/api/lib/Cake/Test/Fixture/sample.xml index 0ab267da0..c4eb00084 100644 --- a/web/api/lib/Cake/Test/Fixture/sample.xml +++ b/web/api/lib/Cake/Test/Fixture/sample.xml @@ -1,4 +1,4 @@ - + defect diff --git a/web/api/lib/Cake/Test/test_app/Console/Command/Task/empty b/web/api/lib/Cake/Test/test_app/Console/Command/Task/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Test/test_app/Controller/Component/empty b/web/api/lib/Cake/Test/test_app/Controller/Component/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Test/test_app/Plugin/TestPlugin/Console/Templates/empty b/web/api/lib/Cake/Test/test_app/Plugin/TestPlugin/Console/Templates/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Test/test_app/Plugin/TestPluginTwo/Console/Command/Task/empty b/web/api/lib/Cake/Test/test_app/Plugin/TestPluginTwo/Console/Command/Task/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Test/test_app/Plugin/TestPluginTwo/Console/Templates/empty b/web/api/lib/Cake/Test/test_app/Plugin/TestPluginTwo/Console/Templates/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Test/test_app/View/Scaffolds/empty b/web/api/lib/Cake/Test/test_app/View/Scaffolds/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/Test/test_app/tmp/empty b/web/api/lib/Cake/Test/test_app/tmp/empty deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/api/lib/Cake/bootstrap.php b/web/api/lib/Cake/bootstrap.php.in similarity index 99% rename from web/api/lib/Cake/bootstrap.php rename to web/api/lib/Cake/bootstrap.php.in index b17c9f68f..b4fb6d5f3 100644 --- a/web/api/lib/Cake/bootstrap.php +++ b/web/api/lib/Cake/bootstrap.php.in @@ -18,6 +18,9 @@ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ +// Force Cake's temp folder = ZoneMinder's temp folder +define('TMP', '@ZM_TMPDIR@'); + define('TIME_START', microtime(true)); if (!defined('E_DEPRECATED')) { diff --git a/web/css/bootstrap.min.css b/web/css/bootstrap.min.css new file mode 100644 index 000000000..ed3905e0e --- /dev/null +++ b/web/css/bootstrap.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/web/css/reset.css b/web/css/reset.css index 12656ad4c..8bd161385 100644 --- a/web/css/reset.css +++ b/web/css/reset.css @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* diff --git a/web/fonts/glyphicons-halflings-regular.eot b/web/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 000000000..b93a4953f Binary files /dev/null and b/web/fonts/glyphicons-halflings-regular.eot differ diff --git a/web/fonts/glyphicons-halflings-regular.svg b/web/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 000000000..94fb5490a --- /dev/null +++ b/web/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/fonts/glyphicons-halflings-regular.ttf b/web/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 000000000..1413fc609 Binary files /dev/null and b/web/fonts/glyphicons-halflings-regular.ttf differ diff --git a/web/fonts/glyphicons-halflings-regular.woff b/web/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 000000000..9e612858f Binary files /dev/null and b/web/fonts/glyphicons-halflings-regular.woff differ diff --git a/web/fonts/glyphicons-halflings-regular.woff2 b/web/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 000000000..64539b54c Binary files /dev/null and b/web/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/web/graphics/spinner.gif b/web/graphics/spinner.gif deleted file mode 100644 index 53dd589fa..000000000 Binary files a/web/graphics/spinner.gif and /dev/null differ diff --git a/web/graphics/spinner.png b/web/graphics/spinner.png new file mode 100644 index 000000000..0388e02f0 Binary files /dev/null and b/web/graphics/spinner.png differ diff --git a/web/graphics/transparent.gif b/web/graphics/transparent.gif deleted file mode 100644 index edab8f2b5..000000000 Binary files a/web/graphics/transparent.gif and /dev/null differ diff --git a/web/graphics/transparent.png b/web/graphics/transparent.png new file mode 100644 index 000000000..1748734dc Binary files /dev/null and b/web/graphics/transparent.png differ diff --git a/web/includes/Event.php b/web/includes/Event.php index 5ad1c4031..70668690b 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -1,6 +1,4 @@ {$k} = $v; } } else { - Error("No row for Event " . $IdOrRow ); + Error('No row for Event ' . $IdOrRow ); } } // end function __construct + public function Storage() { - return new Storage( $this->{'StorageId'} ); + return new Storage( isset($this->{'StorageId'}) ? $this->{'StorageId'} : NULL ); } + + public function Monitor() { + return new Monitor( isset($this->{'MonitorId'}) ? $this->{'MonitorId'} : NULL ); + } + public function __call( $fn, array $args){ - if(isset($this->{$fn})){ + if ( array_key_exists( $fn, $this ) ) { return $this->{$fn}; #array_unshift($args, $this); #call_user_func_array( $this->{$fn}, $args); @@ -49,56 +57,60 @@ class Event { $Storage = $this->Storage(); return $Storage->Path().'/'.$this->Relative_Path(); } - public function Relative_Path() { - $event_path = ""; - if ( ZM_USE_DEEP_STORAGE ) - { - $event_path = - $this->{'MonitorId'} - .'/'.strftime( "%y/%m/%d/%H/%M/%S", - $this->Time() - ) - ; - } - else - { - $event_path = - $this->{'MonitorId'} - .'/'.$this->{'Id'} - ; + public function Relative_Path() { + $event_path = ''; + + if ( ZM_USE_DEEP_STORAGE ) { + $event_path = $this->{'MonitorId'} .'/'.strftime( '%y/%m/%d/%H/%M/%S', $this->Time()) ; + } else { + $event_path = $this->{'MonitorId'} .'/'.$this->{'Id'}; } return( $event_path ); + } // end function Relative_Path() - } - - public function LinkPath() { + public function Link_Path() { if ( ZM_USE_DEEP_STORAGE ) { - return $this->{'MonitorId'} .'/'.strftime( "%y/%m/%d/.", $this->Time()).$this->{'Id'}; + return $this->{'MonitorId'} .'/'.strftime( '%y/%m/%d/.', $this->Time()).$this->{'Id'}; } - Error("Calling Link_Path when not using deep storage"); + Error('Calling Link_Path when not using deep storage'); return ''; } public function delete() { + # This wouldn't work with foreign keys dbQuery( 'DELETE FROM Events WHERE Id = ?', array($this->{'Id'}) ); if ( !ZM_OPT_FAST_DELETE ) { dbQuery( 'DELETE FROM Stats WHERE EventId = ?', array($this->{'Id'}) ); dbQuery( 'DELETE FROM Frames WHERE EventId = ?', array($this->{'Id'}) ); if ( ZM_USE_DEEP_STORAGE ) { -# Assumption: All events haev a start time +# Assumption: All events have a start time $start_date = date_parse( $this->{'StartTime'} ); + if ( ! $start_date ) { + Error('Unable to parse start time for event ' . $this->{'Id'} . ' not deleting files.' ); + return; + } $start_date['year'] = $start_date['year'] % 100; +# So this is because ZM creates a link under the day pointing to the time that the event happened. + $link_path = $this->Link_Path(); + if ( ! $link_path ) { + Error('Unable to determine link path for event ' . $this->{'Id'} . ' not deleting files.' ); + return; + } + $Storage = $this->Storage(); -# So this is because ZM creates a link under teh day pointing to the time that the event happened. - $eventlink_path = $Storage->Path().'/'.$this->Link_Path(); + $eventlink_path = $Storage->Path().'/'.$link_path; if ( $id_files = glob( $eventlink_path ) ) { + if ( ! $eventPath = readlink($id_files[0]) ) { + Error("Unable to read link at $id_files[0]"); + return; + } # I know we are using arrays here, but really there can only ever be 1 in the array - $eventPath = preg_replace( '/\.'.$event['Id'].'$/', readlink($id_files[0]), $id_files[0] ); + $eventPath = preg_replace( '/\.'.$this->{'Id'}.'$/', $eventPath, $id_files[0] ); deletePath( $eventPath ); deletePath( $id_files[0] ); $pathParts = explode( '/', $eventPath ); @@ -118,35 +130,190 @@ class Event { } # ! ZM_OPT_FAST_DELETE } # end Event->delete -public function getStreamSrc( $args, $querySep='&' ) { - return ZM_BASE_URL.'/index.php?view=view_video&eid='.$this->{'Id'}; + public function getStreamSrc( $args=array(), $querySep='&' ) { + if ( $this->{'DefaultVideo'} ) { + return ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php?view=view_video&eid='.$this->{'Id'}; + } $streamSrc = ZM_BASE_URL.ZM_PATH_ZMS; - $args[] = "source=event&event=".$this->{'Id'}; + $args['source'] = 'event'; + $args['event'] = $this->{'Id'}; if ( ZM_OPT_USE_AUTH ) { - if ( ZM_AUTH_RELAY == "hashed" ) { - $args[] = "auth=".generateAuthHash( ZM_AUTH_HASH_IPS ); - } elseif ( ZM_AUTH_RELAY == "plain" ) { - $args[] = "user=".$_SESSION['username']; - $args[] = "pass=".$_SESSION['password']; + if ( ZM_AUTH_RELAY == 'hashed' ) { + $args['auth'] = generateAuthHash( ZM_AUTH_HASH_IPS ); + } elseif ( ZM_AUTH_RELAY == 'plain' ) { + $args['user'] = $_SESSION['username']; + $args['pass'] = $_SESSION['password']; } elseif ( ZM_AUTH_RELAY == "none" ) { - $args[] = "user=".$_SESSION['username']; + $args['user'] = $_SESSION['username']; } } - if ( !in_array( "mode=single", $args ) && !empty($GLOBALS['connkey']) ) { - $args[] = "connkey=".$GLOBALS['connkey']; + if ( ( (!isset($args['mode'])) or ( $args['mode'] != 'single' ) ) && !empty($GLOBALS['connkey']) ) { + $args['connkey'] = $GLOBALS['connkey']; } if ( ZM_RAND_STREAM ) { - $args[] = "rand=".time(); + $args['rand'] = time(); } - if ( count($args) ) { - $streamSrc .= "?".join( $querySep, $args ); - } + $streamSrc .= '?'.http_build_query( $args,'', $querySep ); return( $streamSrc ); } // end function getStreamSrc + + function DiskSpace() { + return folder_size( $this->Path() ); + } + + function createListThumbnail( $overwrite=false ) { + # Load the frame with the highest score to use as a thumbnail + if ( !($frame = dbFetchOne( 'SELECT * FROM Frames WHERE EventId=? AND Score=? ORDER BY FrameId LIMIT 1', NULL, array( $this->{'Id'}, $this->{'MaxScore'} ) )) ) { + Error("Unable to find a Frame matching max score " . $this->{'MaxScore'} . ' for event ' . $this->{'Id'} ); + // FIXME: What if somehow the db frame was lost or score was changed? Should probably try another search for any frame. + return( false ); + } + + $frameId = $frame['FrameId']; + + if ( ZM_WEB_LIST_THUMB_WIDTH ) { + $thumbWidth = ZM_WEB_LIST_THUMB_WIDTH; + $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'}; + $thumbHeight = reScale( $this->{'Height'}, $scale ); + } elseif ( ZM_WEB_LIST_THUMB_HEIGHT ) { + $thumbHeight = ZM_WEB_LIST_THUMB_HEIGHT; + $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_HEIGHT)/$this->{'Height'}; + $thumbWidth = reScale( $this->{'Width'}, $scale ); + } else { + Fatal( "No thumbnail width or height specified, please check in Options->Web" ); + } + + $imageData = $this->getImageSrc( $frame, $scale, false, $overwrite ); + if ( ! $imageData ) { + return ( false ); + } + $thumbData = $frame; + $thumbData['Path'] = $imageData['thumbPath']; + $thumbData['Width'] = (int)$thumbWidth; + $thumbData['Height'] = (int)$thumbHeight; + + return( $thumbData ); + } // end function createListThumbnail + + // frame is an array representing the db row for a frame. + function getImageSrc( $frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=false ) { + $Storage = new Storage( isset($this->{'StorageId'}) ? $this->{'StorageId'} : NULL ); + $Event = $this; + $eventPath = $Event->Path(); + + if ( $frame and ! is_array($frame) ) { + # Must be an Id + Debug("Assuming that $frame is an Id"); + $frame = array( 'FrameId'=>$frame, 'Type'=>'' ); + } + + if ( ( ! $frame ) and file_exists( $eventPath.'/snapshot.jpg' ) ) { + # No frame specified, so look for a snapshot to use + $captImage = 'snapshot.jpg'; + Debug("Frame not specified, using snapshot"); + } else { + $captImage = sprintf( '%0'.ZM_EVENT_IMAGE_DIGITS.'d-capture.jpg', $frame['FrameId'] ); + if ( ! file_exists( $eventPath.'/'.$captImage ) ) { + # Generate the frame JPG + if ( $Event->DefaultVideo() ) { + $videoPath = $eventPath.'/'.$Event->DefaultVideo(); + + if ( ! file_exists( $videoPath ) ) { + Error("Event claims to have a video file, but it does not seem to exist at $videoPath" ); + return ''; + } + + #$command ='ffmpeg -v 0 -i '.$videoPath.' -vf "select=gte(n\\,'.$frame['FrameId'].'),setpts=PTS-STARTPTS" '.$eventPath.'/'.$captImage; + $command ='ffmpeg -ss '. $frame['Delta'] .' -i '.$videoPath.' -frames:v 1 '.$eventPath.'/'.$captImage; + Logger::Debug( "Running $command" ); + $output = array(); + $retval = 0; + exec( $command, $output, $retval ); + Logger::Debug("Retval: $retval, output: " . implode("\n", $output)); + } else { + Error("Can't create frame images from video becuase there is no video file for this event (".$Event->DefaultVideo() ); + } + } + } + + $captPath = $eventPath.'/'.$captImage; + if ( ! file_exists( $captPath ) ) { + Error( "Capture file does not exist at $captPath" ); + return ''; + } + $thumbCaptPath = ZM_DIR_IMAGES.'/'.$this->{'Id'}.'-'.$captImage; + + //echo "CI:$captImage, CP:$captPath, TCP:$thumbCaptPath
"; + + $analImage = sprintf( '%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyse.jpg', $frame['FrameId'] ); + $analPath = $eventPath.'/'.$analImage; + + $thumbAnalPath = ZM_DIR_IMAGES.'/'.$this->{'Id'}.'-'.$analImage; + //echo "AI:$analImage, AP:$analPath, TAP:$thumbAnalPath
"; + + $alarmFrame = $frame['Type']=='Alarm'; + + $hasAnalImage = $alarmFrame && file_exists( $analPath ) && filesize( $analPath ); + $isAnalImage = $hasAnalImage && !$captureOnly; + + if ( !ZM_WEB_SCALE_THUMBS || $scale >= SCALE_BASE || !function_exists( 'imagecreatefromjpeg' ) ) { + $imagePath = $thumbPath = $isAnalImage?$analPath:$captPath; + $imageFile = $imagePath; + $thumbFile = $thumbPath; + } else { + if ( version_compare( phpversion(), '4.3.10', '>=') ) + $fraction = sprintf( '%.3F', $scale/SCALE_BASE ); + else + $fraction = sprintf( '%.3f', $scale/SCALE_BASE ); + $scale = (int)round( $scale ); + + $thumbCaptPath = preg_replace( '/\.jpg$/', "-$scale.jpg", $thumbCaptPath ); + $thumbAnalPath = preg_replace( '/\.jpg$/', "-$scale.jpg", $thumbAnalPath ); + + if ( $isAnalImage ) { + $imagePath = $analPath; + $thumbPath = $thumbAnalPath; + } else { + $imagePath = $captPath; + $thumbPath = $thumbCaptPath; + } + + $thumbFile = $thumbPath; + if ( $overwrite || ! file_exists( $thumbFile ) || ! filesize( $thumbFile ) ) { + // Get new dimensions + list( $imageWidth, $imageHeight ) = getimagesize( $imagePath ); + $thumbWidth = $imageWidth * $fraction; + $thumbHeight = $imageHeight * $fraction; + + // Resample + $thumbImage = imagecreatetruecolor( $thumbWidth, $thumbHeight ); + $image = imagecreatefromjpeg( $imagePath ); + imagecopyresampled( $thumbImage, $image, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $imageWidth, $imageHeight ); + + if ( !imagejpeg( $thumbImage, $thumbPath ) ) + Error( "Can't create thumbnail '$thumbPath'" ); + } + } # Create thumbnails + + $imageData = array( + 'eventPath' => $eventPath, + 'imagePath' => $imagePath, + 'thumbPath' => $thumbPath, + 'imageFile' => $imagePath, + 'thumbFile' => $thumbFile, + 'imageClass' => $alarmFrame?'alarm':'normal', + 'isAnalImage' => $isAnalImage, + 'hasAnalImage' => $hasAnalImage, + ); + + return( $imageData ); + } + } # end class + ?> diff --git a/web/includes/Frame.php b/web/includes/Frame.php index 707569157..5a31b3dc3 100644 --- a/web/includes/Frame.php +++ b/web/includes/Frame.php @@ -6,7 +6,7 @@ class Frame { public function __construct( $IdOrRow ) { $row = NULL; if ( $IdOrRow ) { - if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + if ( is_integer( $IdOrRow ) or ctype_digit($IdOrRow) ) { $row = dbFetchOne( 'SELECT * FROM Frames WHERE Id=?', NULL, array( $IdOrRow ) ); if ( ! $row ) { Error("Unable to load Frame record for Id=" . $IdOrRow ); @@ -27,14 +27,16 @@ class Frame { Error("No row for Frame " . $IdOrRow ); } } // end function __construct + public function Storage() { return $this->Event()->Storage(); } + public function Event() { return new Event( $this->{'EventId'} ); } public function __call( $fn, array $args){ - if(isset($this->{$fn})){ + if( array_key_exists( $fn, $this ) ) { return $this->{$fn}; #array_unshift($args, $this); #call_user_func_array( $this->{$fn}, $args); @@ -69,8 +71,10 @@ class Frame { } - public function getImageSrc( ) { - return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'Id'}; + public function getImageSrc( $show='capture' ) { + + return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'FrameId'}.'&eid='.$this->{'EventId'}.'&show='.$show; + #return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'Id'}.'&show='.$show.'&filename='.$this->Event()->MonitorId().'_'.$this->{'EventId'}.'_'.$this->{'FrameId'}.'.jpg'; } // end function getImageSrc public static function find( $parameters = array(), $limit = NULL ) { @@ -84,7 +88,15 @@ class Frame { $values = array_values( $parameters ); } if ( $limit ) { - $sql .= ' LIMIT ' . $limit; + if ( is_integer( $limit ) or ctype_digit( $limit ) ) { + $sql .= ' LIMIT ' . $limit; + } else { + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + Error("Invalid value for limit($limit) passed to Frame::find from $file:$line"); + return; + } } $results = dbFetchAll( $sql, NULL, $values ); if ( $results ) { diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 6b983018e..62a6ac230 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -3,94 +3,236 @@ require_once( 'database.php' ); require_once( 'Server.php' ); class Monitor { - public function __construct( $IdOrRow ) { - $row = NULL; - if ( $IdOrRow ) { - if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { - $row = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array( $IdOrRow ) ); - if ( ! $row ) { - Error("Unable to load Server record for Id=" . $IdOrRow ); - } - } elseif ( is_array( $IdOrRow ) ) { - $row = $IdOrRow; - } else { - Error("Unknown argument passed to Monitor Constructor ($IdOrRow)"); - return; - } - } # end if isset($IdOrRow) - if ( $row ) { - foreach ($row as $k => $v) { - $this->{$k} = $v; - } - if ( $this->{'Controllable'} ) { - $s = dbFetchOne( 'SELECT * FROM Controls WHERE Id=?', NULL, array( $this->{'ControlId'} ) ); - foreach ($s as $k => $v) { - if ( $k == 'Id' ) { - continue; - } - $this->{$k} = $v; - } - } +private $control_fields = array( + 'Name' => '', + 'Type' => 'Local', + 'Protocol' => NULL, + 'CanWake' => '0', + 'CanSleep' => '0', + 'CanReset' => '0', + 'CanZoom' => '0', + 'CanAutoZoom' => '0', + 'CanZoomAbs' => '0', + 'CanZoomRel' => '0', + 'CanZoomCon' => '0', + 'MinZoomRange' => NULL, + 'MaxZoomRange' => NULL, + 'MinZoomStep' => NULL, + 'MaxZoomStep' => NULL, + 'HasZoomSpeed' => '0', + 'MinZoomSpeed' => NULL, + 'MaxZoomSpeed' => NULL, + 'CanFocus' => '0', + 'CanAutoFocus' => '0', + 'CanFocusAbs' => '0', + 'CanFocusRel' => '0', + 'CanFocusCon' => '0', + 'MinFocusRange' => NULL, + 'MaxFocusRange' => NULL, + 'MinFocusStep' => NULL, + 'MaxFocusStep' => NULL, + 'HasFocusSpeed' => '0', + 'MinFocusSpeed' => NULL, + 'MaxFocusSpeed' => NULL, + 'CanIris' => '0', + 'CanAutoIris' => '0', + 'CanIrisAbs' => '0', + 'CanIrisRel' => '0', + 'CanIrisCon' => '0', + 'MinIrisRange' => NULL, + 'MaxIrisRange' => NULL, + 'MinIrisStep' => NULL, + 'MaxIrisStep' => NULL, + 'HasIrisSpeed' => '0', + 'MinIrisSpeed' => NULL, + 'MaxIrisSpeed' => NULL, + 'CanGain' => '0', + 'CanAutoGain' => '0', + 'CanGainAbs' => '0', + 'CanGainRel' => '0', + 'CanGainCon' => '0', + 'MinGainRange' => NULL, + 'MaxGainRange' => NULL, + 'MinGainStep' => NULL, + 'MaxGainStep' => NULL, + 'HasGainSpeed' => '0', + 'MinGainSpeed' => NULL, + 'MaxGainSpeed' => NULL, + 'CanWhite' => '0', + 'CanAutoWhite' => '0', + 'CanWhiteAbs' => '0', + 'CanWhiteRel' => '0', + 'CanWhiteCon' => '0', + 'MinWhiteRange' => NULL, + 'MaxWhiteRange' => NULL, + 'MinWhiteStep' => NULL, + 'MaxWhiteStep' => NULL, + 'HasWhiteSpeed' => '0', + 'MinWhiteSpeed' => NULL, + 'MaxWhiteSpeed' => NULL, + 'HasPresets' => '0', + 'NumPresets' => '0', + 'HasHomePreset' => '0', + 'CanSetPresets' => '0', + 'CanMove' => '0', + 'CanMoveDiag' => '0', + 'CanMoveMap' => '0', + 'CanMoveAbs' => '0', + 'CanMoveRel' => '0', + 'CanMoveCon' => '0', + 'CanPan' => '0', + 'MinPanRange' => NULL, + 'MaxPanRange' => NULL, + 'MinPanStep' => NULL, + 'MaxPanStep' => NULL, + 'HasPanSpeed' => '0', + 'MinPanSpeed' => NULL, + 'MaxPanSpeed' => NULL, + 'HasTurboPan' => '0', + 'TurboPanSpeed' => NULL, + 'CanTilt' => '0', + 'MinTiltRange' => NULL, + 'MaxTiltRange' => NULL, + 'MinTiltStep' => NULL, + 'MaxTiltStep' => NULL, + 'HasTiltSpeed' => '0', + 'MinTiltSpeed' => NULL, + 'MaxTiltSpeed' => NULL, + 'HasTurboTilt' => '0', + 'TurboTiltSpeed' => NULL, + 'CanAutoScan' => '0', + 'NumScanPaths' => '0', +); - } else { - Error("No row for Monitor " . $IdOrRow ); - } - } // end function __construct - public function Server() { - return new Server( $this->{'ServerId'} ); - } - public function __call( $fn, array $args){ - if(isset($this->{$fn})){ - return $this->{$fn}; - #array_unshift($args, $this); - #call_user_func_array( $this->{$fn}, $args); + public function __construct( $IdOrRow = NULL ) { + if ( $IdOrRow ) { + $row = NULL; + if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + $row = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array( $IdOrRow ) ); + if ( ! $row ) { + Error("Unable to load Server record for Id=" . $IdOrRow ); } - } - public function getStreamSrc( $args, $querySep='&' ) { - if ( isset($this->{'ServerId'}) and $this->{'ServerId'} ) { - $Server = new Server( $this->{'ServerId'} ); - $streamSrc = ZM_BASE_PROTOCOL.'://'.$Server->Hostname().ZM_PATH_ZMS; + } elseif ( is_array( $IdOrRow ) ) { + $row = $IdOrRow; + } else { + Error("Unknown argument passed to Monitor Constructor ($IdOrRow)"); + return; + } + + if ( $row ) { + foreach ($row as $k => $v) { + $this->{$k} = $v; + } + if ( $this->{'Controllable'} ) { + $s = dbFetchOne( 'SELECT * FROM Controls WHERE Id=?', NULL, array( $this->{'ControlId'} ) ); + foreach ($s as $k => $v) { + if ( $k == 'Id' ) { + continue; +# The reason for these is that the name overlaps Monitor fields. + } else if ( $k == 'Protocol' ) { + $this->{'ControlProtocol'} = $v; + } else if ( $k == 'Name' ) { + $this->{'ControlName'} = $v; + } else if ( $k == 'Type' ) { + $this->{'ControlType'} = $v; + } else { + $this->{$k} = $v; + } + } + } + + } else { + Error('No row for Monitor ' . $IdOrRow ); + } + } # end if isset($IdOrRow) + } // end function __construct + public function Server() { + return new Server( $this->{'ServerId'} ); + } + public function __call( $fn, array $args){ + if ( count( $args ) ) { + $this->{$fn} = $args[0]; + } + if ( array_key_exists( $fn, $this ) ) { + return $this->{$fn}; + #array_unshift($args, $this); + #call_user_func_array( $this->{$fn}, $args); } else { - $streamSrc = ZM_BASE_URL.ZM_PATH_ZMS; - } + if ( array_key_exists( $fn, $this->control_fields ) ) { + return $this->control_fields{$fn}; + } else { - $args[] = "monitor=".$this->{'Id'}; + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + Warning( "Unknown function call Monitor->$fn from $file:$line" ); + } + } + } - if ( ZM_OPT_USE_AUTH ) { - if ( ZM_AUTH_RELAY == "hashed" ) { - $args[] = "auth=".generateAuthHash( ZM_AUTH_HASH_IPS ); - } elseif ( ZM_AUTH_RELAY == "plain" ) { - $args[] = "user=".$_SESSION['username']; - $args[] = "pass=".$_SESSION['password']; - } elseif ( ZM_AUTH_RELAY == "none" ) { - $args[] = "user=".$_SESSION['username']; - } - } - if ( !in_array( "mode=single", $args ) && !empty($GLOBALS['connkey']) ) { - $args[] = "connkey=".$GLOBALS['connkey']; - } - if ( ZM_RAND_STREAM ) { - $args[] = "rand=".time(); - } + public function getStreamSrc( $args, $querySep='&' ) { + if ( isset($this->{'ServerId'}) and $this->{'ServerId'} ) { + $Server = new Server( $this->{'ServerId'} ); + $streamSrc = ZM_BASE_PROTOCOL.'://'.$Server->Hostname().ZM_PATH_ZMS; + } else { + $streamSrc = ZM_BASE_URL.ZM_PATH_ZMS; + } - if ( count($args) ) { - $streamSrc .= "?".join( $querySep, $args ); - } + $args['monitor'] = $this->{'Id'}; - return( $streamSrc ); - } // end function etStreamSrc - public function Width() { - if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { - return $this->{'Height'}; - } - return $this->{'Width'}; - } - public function Height() { - if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { - return $this->{'Width'}; - } - return $this->{'Height'}; - } + if ( ZM_OPT_USE_AUTH ) { + if ( ZM_AUTH_RELAY == 'hashed' ) { + $args['auth'] = generateAuthHash( ZM_AUTH_HASH_IPS ); + } elseif ( ZM_AUTH_RELAY == 'plain' ) { + $args['user'] = $_SESSION['username']; + $args['pass'] = $_SESSION['password']; + } elseif ( ZM_AUTH_RELAY == 'none' ) { + $args['user'] = $_SESSION['username']; + } + } + if ( ( (!isset($args['mode'])) or ( $args['mode'] != 'single' ) ) && !empty($GLOBALS['connkey']) ) { + $args['connkey'] = $GLOBALS['connkey']; + } + if ( ZM_RAND_STREAM ) { + $args['rand'] = time(); + } + + $streamSrc .= '?'.http_build_query( $args,'', $querySep ); + + return( $streamSrc ); + } // end function getStreamSrc + + public function Width() { + if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { + return $this->{'Height'}; + } + return $this->{'Width'}; + } + + public function Height() { + if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { + return $this->{'Width'}; + } + return $this->{'Height'}; + } + + public function set( $data ) { + foreach ($data as $k => $v) { + if ( is_array( $v ) ) { + # perhaps should turn into a comma-separated string + $this->{$k} = implode(',',$v); + } else if ( is_string( $v ) ) { + $this->{$k} = trim( $v ); + } else if ( is_integer( $v ) ) { + $this->{$k} = $v; + } else if ( is_bool( $v ) ) { + $this->{$k} = $v; + } else { + Error( "Unknown type $k => $v of var " . gettype( $v ) ); + $this->{$k} = $v; + } + } + } } ?> diff --git a/web/includes/Server.php b/web/includes/Server.php index dfce67eb8..fa892f8d9 100644 --- a/web/includes/Server.php +++ b/web/includes/Server.php @@ -5,7 +5,7 @@ class Server { public function __construct( $IdOrRow = NULL ) { $row = NULL; if ( $IdOrRow ) { - if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + if ( is_integer( $IdOrRow ) or ctype_digit( $IdOrRow ) ) { $row = dbFetchOne( 'SELECT * FROM Servers WHERE Id=?', NULL, array( $IdOrRow ) ); if ( ! $row ) { Error("Unable to load Server record for Id=" . $IdOrRow ); @@ -47,7 +47,7 @@ class Server { return $this->{'Name'}; } public function __call( $fn, array $args= NULL){ - if(isset($this->{$fn})){ + if( array_key_exists( $fn, $this) ) { return $this->{$fn}; #array_unshift($args, $this); #call_user_func_array( $this->{$fn}, $args); @@ -63,9 +63,15 @@ class Server { ) ); $values = array_values( $parameters ); } - if ( $limit ) { - $sql .= ' LIMIT ' . $limit; - } + if ( is_integer( $limit ) or ctype_digit( $limit ) ) { + $sql .= ' LIMIT ' . $limit; + } else { + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + Error("Invalid value for limit($limit) passed to Server::find from $file:$line"); + return; + } $results = dbFetchAll( $sql, NULL, $values ); if ( $results ) { return array_map( function($id){ return new Server($id); }, $results ); diff --git a/web/includes/Storage.php b/web/includes/Storage.php index 546cb9cbe..940653404 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -1,51 +1,89 @@ $v) { - $this->{$k} = $v; - } - } else { - $this->{'Name'} = ''; - $this->{'Path'} = ''; - } + public function __construct( $IdOrRow = NULL ) { + $row = NULL; + if ( $IdOrRow ) { + if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + $row = dbFetchOne( 'SELECT * FROM Storage WHERE Id=?', NULL, array( $IdOrRow ) ); + if ( ! $row ) { + Error("Unable to load Storage record for Id=" . $IdOrRow ); + } + } elseif ( is_array( $IdOrRow ) ) { + $row = $IdOrRow; + } } + if ( $row ) { + foreach ($row as $k => $v) { + $this->{$k} = $v; + } + } else { + $this->{'Name'} = ''; + $this->{'Path'} = ''; + } + } - public function Path() { - if ( isset( $this->{'Path'} ) and ( $this->{'Path'} != '' ) ) { - return $this->{'Path'}; - } else if ( ! isset($this->{'Id'}) ) { - return ZM_DIR_EVENTS; - } - return $this->{'Name'}; - } - public function __call( $fn, array $args= NULL){ - if(isset($this->{$fn})){ - return $this->{$fn}; - #array_unshift($args, $this); - #call_user_func_array( $this->{$fn}, $args); - } + public function Path() { + if ( isset( $this->{'Path'} ) and ( $this->{'Path'} != '' ) ) { + return $this->{'Path'}; + } else if ( ! isset($this->{'Id'}) ) { + $path = ZM_DIR_EVENTS; + if ( $path[0] != '/' ) { + $this->{'Path'} = ZM_PATH_WEB.'/'.ZM_DIR_EVENTS; + } else { + $this->{'Path'} = ZM_DIR_EVENTS; + } + return $this->{'Path'}; + } - public static function find_all() { - $storage_areas = array(); - $result = dbQuery( 'SELECT * FROM Storage ORDER BY Name'); - $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Storage' ); - foreach ( $results as $row => $obj ) { - $storage_areas[] = $obj; - } - return $storage_areas; + return $this->{'Name'}; + } + public function Name() { + if ( isset( $this->{'Name'} ) and ( $this->{'Name'} != '' ) ) { + return $this->{'Name'}; + } else if ( ! isset($this->{'Id'}) ) { + return 'Default'; } + return $this->{'Name'}; + } + + public function __call( $fn, array $args= NULL){ + if(isset($this->{$fn})){ + return $this->{$fn}; +#array_unshift($args, $this); +#call_user_func_array( $this->{$fn}, $args); + } + } + public static function find_all() { + $storage_areas = array(); + $result = dbQuery( 'SELECT * FROM Storage ORDER BY Name'); + $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Storage' ); + foreach ( $results as $row => $obj ) { + $storage_areas[] = $obj; + } + return $storage_areas; + } + public function disk_usage_percent() { + $path = $this->Path(); + if ( ! $path ) { + Warning("Storage::disk_usage_percent: path is empty"); + return 0; + } else if ( ! file_exists( $path ) ) { + Warning("Storage::disk_usage_percent: path $path does not exist"); + return 0; + } + + $total = disk_total_space( $path ); + if ( ! $total ) { + Error("disk_total_space returned false for " . $path ); + return 0; + } + $free = disk_free_space( $path ); + if ( ! $free ) { + Error("disk_free_space returned false for " . $path ); + } + $usage = round(($total - $free) / $total * 100); + return $usage; + } } ?> diff --git a/web/includes/actions.php b/web/includes/actions.php index 4cd056116..d306c3bbc 100644 --- a/web/includes/actions.php +++ b/web/includes/actions.php @@ -15,7 +15,7 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // @@ -23,12 +23,11 @@ // credit: http://wezfurlong.org/blog/2006/nov/http-post-from-php-without-curl/ -function do_post_request($url, $data, $optional_headers = null) -{ +function do_post_request($url, $data, $optional_headers = null) { $params = array('http' => array( - 'method' => 'POST', - 'content' => $data - )); + 'method' => 'POST', + 'content' => $data + )); if ($optional_headers !== null) { $params['http']['header'] = $optional_headers; } @@ -44,988 +43,864 @@ function do_post_request($url, $data, $optional_headers = null) return $response; } -function getAffectedIds( $name ) -{ - $names = $name."s"; - $ids = array(); - if ( isset($_REQUEST[$names]) || isset($_REQUEST[$name]) ) - { - if ( isset($_REQUEST[$names]) ) - $ids = validInt($_REQUEST[$names]); - else if ( isset($_REQUEST[$name]) ) - $ids[] = validInt($_REQUEST[$name]); - } - return( $ids ); +function getAffectedIds( $name ) { + $names = $name.'s'; + $ids = array(); + if ( isset($_REQUEST[$names]) || isset($_REQUEST[$name]) ) { + if ( isset($_REQUEST[$names]) ) + $ids = validInt($_REQUEST[$names]); + else if ( isset($_REQUEST[$name]) ) + $ids[] = validInt($_REQUEST[$name]); + } + return( $ids ); } -if ( ZM_OPT_USE_AUTH && ZM_AUTH_HASH_LOGINS && empty($user) && !empty($_REQUEST['auth']) ) -{ - if ( $authUser = getAuthUser( $_REQUEST['auth'] ) ) + +if ( !empty($action) ) { + if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 'remote' || isset($_REQUEST['password']) ) ) { + // if true, a popup will display after login + // PP - lets validate reCaptcha if it exists + if ( defined('ZM_OPT_USE_GOOG_RECAPTCHA') + && defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY') + && defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY') + && ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_GOOG_RECAPTCHA_SECRETKEY + && ZM_OPT_GOOG_RECAPTCHA_SITEKEY) { - userLogin( $authUser['Username'], $authUser['Password'], true ); - } -} - -if ( !empty($action) ) -{ - if ( $action == "login" && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == "remote" || isset($_REQUEST['password']) ) ) - { - // if true, a popup will display after login - // PP - lets validate reCaptcha if it exists - if ( defined('ZM_OPT_USE_GOOG_RECAPTCHA') - && defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY') - && defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY') - && ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_GOOG_RECAPTCHA_SECRETKEY - && ZM_OPT_GOOG_RECAPTCHA_SITEKEY) - { - $url = 'https://www.google.com/recaptcha/api/siteverify'; - $fields = array ( - 'secret'=> ZM_OPT_GOOG_RECAPTCHA_SECRETKEY, - 'response' => $_REQUEST['g-recaptcha-response'], - 'remoteip'=> $_SERVER['REMOTE_ADDR'] - - ); - $res= do_post_request($url, http_build_query($fields)); - $responseData = json_decode($res,true); - // PP - credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php - // if recaptcha resulted in error, we might have to deny login - if (isset($responseData['success']) && $responseData['success'] == false) - { - // PP - before we deny auth, let's make sure the error was not 'invalid secret' - // because that means the user did not configure the secret key correctly - // in this case, we prefer to let him login in and display a message to correct - // the key. Unfortunately, there is no way to check for invalid site key in code - // as it produces the same error as when you don't answer a recaptcha - if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) - { - if (!in_array('invalid-input-secret',$responseData['error-codes'])) - { - Error ("reCaptcha authentication failed"); - userLogout(); - $view='login'; - $refreshParent = true; - } - else - { - //Let them login but show an error - echo ''; - Error ("Invalid recaptcha secret detected"); - - } - } - - } - - } - } - - // General scope actions - if ( $action == "login" && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == "remote" || isset($_REQUEST['password']) ) ) - { - $username = validStr( $_REQUEST['username'] ); - $password = isset($_REQUEST['password'])?validStr($_REQUEST['password']):''; - userLogin( $username, $password ); - } - elseif ( $action == "logout" ) - { - userLogout(); - $refreshParent = true; - $view = 'none'; - } - elseif ( $action == "bandwidth" && isset($_REQUEST['newBandwidth']) ) - { - $_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']); - setcookie( "zmBandwidth", validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10 ); - $refreshParent = true; - } - - // Event scope actions, view permissions only required - if ( canView( 'Events' ) ) - { - if ( $action == "filter" ) - { - if ( !empty($_REQUEST['subaction']) ) - { - if ( $_REQUEST['subaction'] == "addterm" ) - $_REQUEST['filter'] = addFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); - elseif ( $_REQUEST['subaction'] == "delterm" ) - $_REQUEST['filter'] = delFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); - } - elseif ( canEdit( 'Events' ) ) - { - if ( !empty($_REQUEST['execute']) ) - $tempFilterName = "_TempFilter".time(); - if ( isset($tempFilterName) ) - $filterName = $tempFilterName; - elseif ( !empty($_REQUEST['newFilterName']) ) - $filterName = $_REQUEST['newFilterName']; - if ( !empty($filterName) ) - { - $_REQUEST['filter']['sort_field'] = validStr($_REQUEST['sort_field']); - $_REQUEST['filter']['sort_asc'] = validStr($_REQUEST['sort_asc']); - $_REQUEST['filter']['limit'] = validInt($_REQUEST['limit']); - $sql = "replace into Filters set Name = ".dbEscape($filterName).", Query = ".dbEscape(jsonEncode($_REQUEST['filter'])); - if ( !empty($_REQUEST['autoArchive']) ) - $sql .= ", AutoArchive = ".dbEscape($_REQUEST['autoArchive']); - if ( !empty($_REQUEST['autoVideo']) ) - $sql .= ", AutoVideo = ".dbEscape($_REQUEST['autoVideo']); - if ( !empty($_REQUEST['autoUpload']) ) - $sql .= ", AutoUpload = ".dbEscape($_REQUEST['autoUpload']); - if ( !empty($_REQUEST['autoEmail']) ) - $sql .= ", AutoEmail = ".dbEscape($_REQUEST['autoEmail']); - if ( !empty($_REQUEST['autoMessage']) ) - $sql .= ", AutoMessage = ".dbEscape($_REQUEST['autoMessage']); - if ( !empty($_REQUEST['autoExecute']) && !empty($_REQUEST['autoExecuteCmd']) ) - $sql .= ", AutoExecute = ".dbEscape($_REQUEST['autoExecute']).", AutoExecuteCmd = ".dbEscape($_REQUEST['autoExecuteCmd']); - if ( !empty($_REQUEST['autoDelete']) ) - $sql .= ", AutoDelete = ".dbEscape($_REQUEST['autoDelete']); - if ( !empty($_REQUEST['background']) ) - $sql .= ", Background = ".dbEscape($_REQUEST['background']); - dbQuery( $sql ); - $refreshParent = true; - } - } - } - } - - // Event scope actions, edit permissions required - if ( canEdit( 'Events' ) ) - { - if ( $action == "rename" && isset($_REQUEST['eventName']) && !empty($_REQUEST['eid']) ) - { - dbQuery( 'UPDATE Events SET Name=? WHERE Id=?', array( $_REQUEST['eventName'], $_REQUEST['eid'] ) ); - } - else if ( $action == "eventdetail" ) - { - if ( !empty($_REQUEST['eid']) ) - { - dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['eid'] ) ); - $refreshParent = true; - } - else - { - foreach( getAffectedIds( 'markEid' ) as $markEid ) - { - dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid ) ); - $refreshParent = true; - } - } - } - elseif ( $action == "archive" || $action == "unarchive" ) - { - $archiveVal = ($action == "archive")?1:0; - if ( !empty($_REQUEST['eid']) ) - { - dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $_REQUEST['eid']) ); - } - else - { - foreach( getAffectedIds( 'markEid' ) as $markEid ) - { - dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $markEid ) ); - $refreshParent = true; - } - } - } - elseif ( $action == "delete" ) - { - foreach( getAffectedIds( 'markEid' ) as $markEid ) - { - deleteEvent( $markEid ); - $refreshParent = true; - } - if ( !empty($_REQUEST['fid']) ) - { - dbQuery( 'DELETE FROM Filters WHERE Name=?', array( $_REQUEST['fid'] ) ); - //$refreshParent = true; - } - } - } - - // Monitor control actions, require a monitor id and control view permissions for that monitor - if ( !empty($_REQUEST['mid']) && canView( 'Control', $_REQUEST['mid'] ) ) - { - require_once( 'control_functions.php' ); - require_once( 'Monitor.php' ); - $mid = validInt($_REQUEST['mid']); - if ( $action == "control" ) - { - $monitor = new Monitor( $mid ); - - $ctrlCommand = buildControlCommand( $monitor ); - sendControlCommand( $monitor->Id(), $ctrlCommand ); - } - elseif ( $action == "settings" ) - { - $args = " -m " . escapeshellarg($mid); - $args .= " -B" . escapeshellarg($_REQUEST['newBrightness']); - $args .= " -C" . escapeshellarg($_REQUEST['newContrast']); - $args .= " -H" . escapeshellarg($_REQUEST['newHue']); - $args .= " -O" . escapeshellarg($_REQUEST['newColour']); - - $zmuCommand = getZmuCommand( $args ); - - $zmuOutput = exec( $zmuCommand ); - list( $brightness, $contrast, $hue, $colour ) = explode( ' ', $zmuOutput ); - dbQuery( "update Monitors set Brightness = ?, Contrast = ?, Hue = ?, Colour = ? where Id = ?", array($brightness, $contrast, $hue, $colour, $mid)); - } - } - - // Control capability actions, require control edit permissions - if ( canEdit( 'Control' ) ) - { - if ( $action == "controlcap" ) - { - if ( !empty($_REQUEST['cid']) ) - { - $control = dbFetchOne( "select * from Controls where Id = ?", NULL, array($_REQUEST['cid']) ); - } - else - { - $control = array(); - } - - // Define a field type for anything that's not simple text equivalent - $types = array( - // Empty - ); - - $columns = getTableColumns( 'Controls' ); - foreach ( $columns as $name=>$type ) - { - if ( preg_match( '/^(Can|Has)/', $name ) ) - { - $types[$name] = 'toggle'; - } - } - $changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); - - if ( count( $changes ) ) - { - if ( !empty($_REQUEST['cid']) ) - { - dbQuery( "update Controls set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['cid']) ); - } - else - { - dbQuery( "insert into Controls set ".implode( ", ", $changes ) ); - //$_REQUEST['cid'] = dbInsertId(); - } - $refreshParent = true; - } - $view = 'none'; - } - elseif ( $action == "delete" ) - { - if ( isset($_REQUEST['markCids']) ) - { - foreach( $_REQUEST['markCids'] as $markCid ) - { - dbQuery( "delete from Controls where Id = ?", array($markCid) ); - dbQuery( "update Monitors set Controllable = 0, ControlId = 0 where ControlId = ?", array($markCid) ); - $refreshParent = true; - } - } - } - } - - // Monitor edit actions, require a monitor id and edit permissions for that monitor - if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) - { - $mid = validInt($_REQUEST['mid']); - if ( $action == "function" ) - { - $monitor = dbFetchOne( "SELECT * FROM Monitors WHERE Id=?", NULL, array($mid) ); - - $newFunction = validStr($_REQUEST['newFunction']); - $newEnabled = isset( $_REQUEST['newEnabled'] ) and $_REQUEST['newEnabled'] != "1" ? "0" : "1"; - $oldFunction = $monitor['Function']; - $oldEnabled = $monitor['Enabled']; - if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) - { - dbQuery( "update Monitors set Function=?, Enabled=? where Id=?", array( $newFunction, $newEnabled, $mid ) ); - - $monitor['Function'] = $newFunction; - $monitor['Enabled'] = $newEnabled; - //if ( $cookies ) session_write_close(); - if ( daemonCheck() ) - { - $restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled); - zmaControl( $monitor, "stop" ); - zmcControl( $monitor, $restart?"restart":"" ); - zmaControl( $monitor, "start" ); - } - $refreshParent = true; - } - } - elseif ( $action == "zone" && isset( $_REQUEST['zid'] ) ) - { - $zid = validInt($_REQUEST['zid']); - $monitor = dbFetchOne( "SELECT * FROM Monitors WHERE Id=?", NULL, array($mid) ); - - if ( !empty($zid) ) - { - $zone = dbFetchOne( "SELECT * FROM Zones WHERE MonitorId=? AND Id=?", NULL, array( $mid, $zid ) ); - } - else - { - $zone = array(); - } - - if ( $_REQUEST['newZone']['Units'] == 'Percent' ) - { - $_REQUEST['newZone']['MinAlarmPixels'] = intval(($_REQUEST['newZone']['MinAlarmPixels']*$_REQUEST['newZone']['Area'])/100); - $_REQUEST['newZone']['MaxAlarmPixels'] = intval(($_REQUEST['newZone']['MaxAlarmPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MinFilterPixels']) ) - $_REQUEST['newZone']['MinFilterPixels'] = intval(($_REQUEST['newZone']['MinFilterPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MaxFilterPixels']) ) - $_REQUEST['newZone']['MaxFilterPixels'] = intval(($_REQUEST['newZone']['MaxFilterPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MinBlobPixels']) ) - $_REQUEST['newZone']['MinBlobPixels'] = intval(($_REQUEST['newZone']['MinBlobPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MaxBlobPixels']) ) - $_REQUEST['newZone']['MaxBlobPixels'] = intval(($_REQUEST['newZone']['MaxBlobPixels']*$_REQUEST['newZone']['Area'])/100); - } - - unset( $_REQUEST['newZone']['Points'] ); - $types = array(); - $changes = getFormChanges( $zone, $_REQUEST['newZone'], $types ); - - if ( count( $changes ) ) - { - if ( $zid > 0 ) - { - dbQuery( "UPDATE Zones SET ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zid) ); - } - else - { - dbQuery( "INSERT INTO Zones SET MonitorId=?, ".implode( ", ", $changes ), array( $mid ) ); - } - //if ( $cookies ) session_write_close(); - if ( daemonCheck() ) - { - if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) - { - zmaControl( $monitor, "stop" ); - zmcControl( $monitor, "restart" ); - zmaControl( $monitor, "start" ); - } - else - { - zmaControl( $mid, "restart" ); - } - } - if ( $_REQUEST['newZone']['Type'] == 'Privacy' && $monitor['Controllable'] ) { - require_once( 'control_functions.php' ); - sendControlCommand( $mid, 'quit' ); - } - $refreshParent = true; - } - $view = 'none'; - } - elseif ( $action == "plugin" && isset($_REQUEST['pl'])) - { - $sql="SELECT * FROM PluginsConfig WHERE MonitorId=? AND ZoneId=? AND pluginName=?"; - $pconfs=dbFetchAll( $sql, NULL, array( $mid, $_REQUEST['zid'], $_REQUEST['pl'] ) ); - $changes=0; - foreach( $pconfs as $pconf ) - { - $value=$_REQUEST['pluginOpt'][$pconf['Name']]; - if(array_key_exists($pconf['Name'], $_REQUEST['pluginOpt']) && ($pconf['Value']!=$value)) - { - dbQuery("UPDATE PluginsConfig SET Value=? WHERE id=?", array( $value, $pconf['Id'] ) ); - $changes++; - } - } - if($changes>0) - { - if ( daemonCheck() ) - { - zmaControl( $mid, "restart" ); - } - $refreshParent = true; - } - $view = 'none'; - } - elseif ( $action == "sequence" && isset($_REQUEST['smid']) ) - { - $smid = validInt($_REQUEST['smid']); - $monitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ); - $smonitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($smid) ); - - dbQuery( "update Monitors set Sequence=? where Id=?", array( $smonitor['Sequence'], $monitor['Id'] ) ); - dbQuery( "update Monitors set Sequence=? WHERE Id=?", array( $monitor['Sequence'], $smonitor['Id'] ) ); - + $url = 'https://www.google.com/recaptcha/api/siteverify'; + $fields = array ( + 'secret'=> ZM_OPT_GOOG_RECAPTCHA_SECRETKEY, + 'response' => $_REQUEST['g-recaptcha-response'], + 'remoteip'=> $_SERVER['REMOTE_ADDR'] + ); + $res= do_post_request($url, http_build_query($fields)); + $responseData = json_decode($res,true); + // PP - credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php + // if recaptcha resulted in error, we might have to deny login + if (isset($responseData['success']) && $responseData['success'] == false) { + // PP - before we deny auth, let's make sure the error was not 'invalid secret' + // because that means the user did not configure the secret key correctly + // in this case, we prefer to let him login in and display a message to correct + // the key. Unfortunately, there is no way to check for invalid site key in code + // as it produces the same error as when you don't answer a recaptcha + if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) { + if (!in_array('invalid-input-secret',$responseData['error-codes'])) { + Error ('reCaptcha authentication failed'); + userLogout(); + $view='login'; $refreshParent = true; - fixSequences(); + } else { + //Let them login but show an error + echo ''; + Error ("Invalid recaptcha secret detected"); + } } - if ( $action == "delete" ) - { - if ( isset($_REQUEST['markZids']) ) - { - $deletedZid = 0; - foreach( $_REQUEST['markZids'] as $markZid ) - { - $zone = dbFetchOne( "select * from Zones where Id=?", NULL, array($markZid) ); - dbQuery( "delete from Zones WHERE MonitorId=? AND Id=?", array( $mid, $markZid) ); - $deletedZid = 1; - } - if ( $deletedZid ) - { - //if ( $cookies ) - //session_write_close(); - if ( daemonCheck() ) - if ( $zone['Type'] == 'Privacy' ) - { - zmaControl( $mid, "stop" ); - zmcControl( $mid, "restart" ); - zmaControl( $mid, "start" ); - } - else - { - zmaControl( $mid, "restart" ); - } - $refreshParent = true; - } - } + } // end if success==false + + } // end if using reCaptcha + + $username = validStr( $_REQUEST['username'] ); + $password = isset($_REQUEST['password'])?validStr($_REQUEST['password']):''; + userLogin( $username, $password ); + $refreshParent = true; + $view = 'console'; + $redirect = true; + } else if ( $action == 'logout' ) { + userLogout(); + $refreshParent = true; + $view = 'none'; + } else if ( $action == 'bandwidth' && isset($_REQUEST['newBandwidth']) ) { + $_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']); + setcookie( 'zmBandwidth', validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10 ); + $refreshParent = true; + } + + // Event scope actions, view permissions only required + if ( canView( 'Events' ) ) { + + if ( $action == 'filter' ) { + if ( !empty($_REQUEST['subaction']) ) { + if ( $_REQUEST['subaction'] == 'addterm' ) + $_REQUEST['filter'] = addFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); + elseif ( $_REQUEST['subaction'] == 'delterm' ) + $_REQUEST['filter'] = delFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); + } elseif ( canEdit( 'Events' ) ) { + $sql = ''; + $endSql = ''; + $filterName = ''; + if ( !empty($_REQUEST['execute']) ) { + // TempFilterName is used in event listing later on + $tempFilterName = $filterName = '_TempFilter'.time(); + } elseif ( !empty($_REQUEST['newFilterName']) ) { + $filterName = $_REQUEST['newFilterName']; } - } - - // Monitor edit actions, monitor id derived, require edit permissions for that monitor - if ( canEdit( 'Monitors' ) ) - { - if ( $action == "monitor" ) - { - if ( !empty($_REQUEST['mid']) ) - { - $mid = validInt($_REQUEST['mid']); - $monitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ); - - if ( ZM_OPT_X10 ) - { - $x10Monitor = dbFetchOne( "select * from TriggersX10 where MonitorId=?", NULL, array($mid) ); - if ( !$x10Monitor ) - $x10Monitor = array(); - } - } - else - { - $monitor = array(); - if ( ZM_OPT_X10 ) - { - $x10Monitor = array(); - } - } - - // Define a field type for anything that's not simple text equivalent - $types = array( - 'Triggers' => 'set', - 'Controllable' => 'toggle', - 'TrackMotion' => 'toggle', - 'Enabled' => 'toggle', - 'DoNativeMotDet' => 'toggle', - 'Exif' => 'toggle', - 'RTSPDescribe' => 'toggle', - ); - - $columns = getTableColumns( 'Monitors' ); - $changes = getFormChanges( $monitor, $_REQUEST['newMonitor'], $types, $columns ); - - if ( count( $changes ) ) - { - if ( !empty($_REQUEST['mid']) ) - { - $mid = validInt($_REQUEST['mid']); - dbQuery( "update Monitors set ".implode( ", ", $changes )." where Id =?", array($mid) ); - if ( isset($changes['Name']) ) - { - $saferOldName = basename( $monitor['Name'] ); - $saferNewName = basename( $_REQUEST['newMonitor']['Name'] ); - rename( ZM_DIR_EVENTS."/".$saferOldName, ZM_DIR_EVENTS."/".$saferNewName); - } - if ( isset($changes['Width']) || isset($changes['Height']) ) - { - $newW = $_REQUEST['newMonitor']['Width']; - $newH = $_REQUEST['newMonitor']['Height']; - $newA = $newW * $newH; - $oldW = $monitor['Width']; - $oldH = $monitor['Height']; - $oldA = $oldW * $oldH; - - $zones = dbFetchAll( "select * from Zones where MonitorId=?", NULL, array($mid) ); - foreach ( $zones as $zone ) - { - $newZone = $zone; - $points = coordsToPoints( $zone['Coords'] ); - for ( $i = 0; $i < count($points); $i++ ) - { - $points[$i]['x'] = intval(($points[$i]['x']*($newW-1))/($oldW-1)); - $points[$i]['y'] = intval(($points[$i]['y']*($newH-1))/($oldH-1)); - } - $newZone['Coords'] = pointsToCoords( $points ); - $newZone['Area'] = intval(round(($zone['Area']*$newA)/$oldA)); - $newZone['MinAlarmPixels'] = intval(round(($newZone['MinAlarmPixels']*$newA)/$oldA)); - $newZone['MaxAlarmPixels'] = intval(round(($newZone['MaxAlarmPixels']*$newA)/$oldA)); - $newZone['MinFilterPixels'] = intval(round(($newZone['MinFilterPixels']*$newA)/$oldA)); - $newZone['MaxFilterPixels'] = intval(round(($newZone['MaxFilterPixels']*$newA)/$oldA)); - $newZone['MinBlobPixels'] = intval(round(($newZone['MinBlobPixels']*$newA)/$oldA)); - $newZone['MaxBlobPixels'] = intval(round(($newZone['MaxBlobPixels']*$newA)/$oldA)); - - $changes = getFormChanges( $zone, $newZone, $types ); - - if ( count( $changes ) ) - { - dbQuery( "update Zones set ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zone['Id'] ) ); - } - } - } - } - elseif ( !$user['MonitorIds'] ) - { - # FIXME This is actually a race condition. Should lock the table. - $maxSeq = dbFetchOne( "select max(Sequence) as MaxSequence from Monitors", "MaxSequence" ); - $changes[] = "Sequence = ".($maxSeq+1); - - dbQuery( "insert into Monitors set ".implode( ", ", $changes ) ); - $mid = dbInsertId(); - $zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height']; - dbQuery( "insert into Zones set MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) ); - //$view = 'none'; - mkdir( ZM_DIR_EVENTS.'/'.$mid, 0755 ); - $saferName = basename($_REQUEST['newMonitor']['Name']); - symlink( $mid, ZM_DIR_EVENTS.'/'.$saferName ); - if ( isset($_COOKIE['zmGroup']) ) - { - dbQuery( "UPDATE Groups SET MonitorIds = concat(MonitorIds,',".$mid."') WHERE Id=?", array($_COOKIE['zmGroup']) ); - } - } - $restart = true; - } - - if ( ZM_OPT_X10 ) - { - $x10Changes = getFormChanges( $x10Monitor, $_REQUEST['newX10Monitor'] ); - - if ( count( $x10Changes ) ) - { - if ( $x10Monitor && isset($_REQUEST['newX10Monitor']) ) - { - dbQuery( "update TriggersX10 set ".implode( ", ", $x10Changes )." where MonitorId=?", array($mid) ); - } - elseif ( !$user['MonitorIds'] ) - { - if ( !$x10Monitor ) - { - dbQuery( "insert into TriggersX10 set MonitorId = ?".implode( ", ", $x10Changes ), array( $mid ) ); - } - else - { - dbQuery( "delete from TriggersX10 where MonitorId = ?", array($mid) ); - } - } - $restart = true; - } - } - - if ( $restart ) - { - $monitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ); - //fixDevices(); - //if ( $cookies ) - //session_write_close(); - if ( daemonCheck() ) - { - zmaControl( $monitor, "stop" ); - zmcControl( $monitor, "restart" ); - zmaControl( $monitor, "start" ); - } - if ( $monitor['Controllable'] ) { - require_once( 'control_functions.php' ); - sendControlCommand( $mid, 'quit' ); - } - //daemonControl( 'restart', 'zmwatch.pl' ); - $refreshParent = true; - } - $view = 'none'; - } - if ( $action == "delete" ) - { - if ( isset($_REQUEST['markMids']) && !$user['MonitorIds'] ) - { - foreach( $_REQUEST['markMids'] as $markMid ) - { - if ( canEdit( 'Monitors', $markMid ) ) - { - if ( $monitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($markMid) ) ) - { - if ( daemonCheck() ) - { - zmaControl( $monitor, "stop" ); - zmcControl( $monitor, "stop" ); - } - - // This is the important stuff - dbQuery( "delete from Monitors where Id = ?", array($markMid) ); - dbQuery( "delete from Zones where MonitorId = ?", array($markMid) ); - if ( ZM_OPT_X10 ) - dbQuery( "delete from TriggersX10 where MonitorId=?", array($markMid) ); - - fixSequences(); - - // If fast deletes are on, then zmaudit will clean everything else up later - // If fast deletes are off and there are lots of events then this step may - // well time out before completing, in which case zmaudit will still tidy up - if ( !ZM_OPT_FAST_DELETE ) - { - // Slight hack, we maybe should load *, but we happen to know that the deleteEvent function uses Id and StartTime. - $markEids = dbFetchAll( "SELECT Id,StartTime FROM Events WHERE MonitorId=?", NULL, array($markMid) ); - foreach( $markEids as $markEid ) - deleteEvent( $markEid, $markMid ); - - deletePath( ZM_DIR_EVENTS."/".basename($monitor['Name']) ); - deletePath( ZM_DIR_EVENTS."/".$monitor['Id'] ); // I'm trusting the Id. - } - } - } - } - } - } - } - - // Device view actions - if ( canEdit( 'Devices' ) ) - { - if ( $action == "device" ) - { - if ( !empty($_REQUEST['command']) ) - { - setDeviceStatusX10( $_REQUEST['key'], $_REQUEST['command'] ); - } - elseif ( isset( $_REQUEST['newDevice'] ) ) - { - if ( isset($_REQUEST['did']) ) - { - dbQuery( "update Devices set Name=?, KeyString=? where Id=?", array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'], $_REQUEST['did']) ); - } - else - { - dbQuery( "insert into Devices set Name=?, KeyString=?", array( $_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'] ) ); - } - $refreshParent = true; - $view = 'none'; - } - } - elseif ( $action == "delete" ) - { - if ( isset($_REQUEST['markDids']) ) - { - foreach( $_REQUEST['markDids'] as $markDid ) - { - dbQuery( "delete from Devices where Id=?", array($markDid) ); - $refreshParent = true; - } - } - } - } - - // Group view actions - if ( canView( 'Groups' ) && $action == "setgroup" ) { - if ( !empty($_REQUEST['gid']) ) { - setcookie( "zmGroup", validInt($_REQUEST['gid']), time()+3600*24*30*12*10 ); + if ( $filterName ) { + # Replace will teplace any filter with the same Id + # Since we aren't specifying the Id , this is effectively an insert + $sql = 'REPLACE INTO Filters SET Name = '.dbEscape($filterName).','; } else { - setcookie( "zmGroup", "", time()-3600*24*2 ); + $sql = 'UPDATE Filters SET'; + $endSql = 'WHERE Id = '.$_REQUEST['Id']; } - $refreshParent = true; - } - // Group edit actions - if ( canEdit( 'Groups' ) ) { - if ( $action == "group" ) { - # Should probably verfy that each monitor id is a valid monitor, that we have access to. HOwever at the moment, you have to have System permissions to do this - $monitors = empty( $_POST['newGroup']['MonitorIds'] ) ? NULL : implode(',', $_POST['newGroup']['MonitorIds']); - if ( !empty($_POST['gid']) ) { - dbQuery( "UPDATE Groups SET Name=?, MonitorIds=? WHERE Id=?", array($_POST['newGroup']['Name'], $monitors, $_POST['gid']) ); + # endSql is only set if ! filterName... so... woulnd't this always be true + if ( !empty($filterName) || $endSql ) { + $_REQUEST['filter']['sort_field'] = validStr($_REQUEST['sort_field']); + $_REQUEST['filter']['sort_asc'] = validStr($_REQUEST['sort_asc']); + $_REQUEST['filter']['limit'] = validInt($_REQUEST['limit']); + $sql .= ' Query = '.dbEscape(jsonEncode($_REQUEST['filter'])); + if ( !empty($_REQUEST['AutoArchive']) ) + $sql .= ', AutoArchive = '.dbEscape($_REQUEST['AutoArchive']); + if ( !empty($_REQUEST['AutoVideo']) ) + $sql .= ', AutoVideo = '.dbEscape($_REQUEST['AutoVideo']); + if ( !empty($_REQUEST['AutoUpload']) ) + $sql .= ', AutoUpload = '.dbEscape($_REQUEST['AutoUpload']); + if ( !empty($_REQUEST['AutoEmail']) ) + $sql .= ', AutoEmail = '.dbEscape($_REQUEST['AutoEmail']); + if ( !empty($_REQUEST['AutoMessage']) ) + $sql .= ', AutoMessage = '.dbEscape($_REQUEST['AutoMessage']); + if ( !empty($_REQUEST['AutoExecute']) && !empty($_REQUEST['AutoExecuteCmd']) ) + $sql .= ', AutoExecute = '.dbEscape($_REQUEST['AutoExecute']).", AutoExecuteCmd = ".dbEscape($_REQUEST['AutoExecuteCmd']); + if ( !empty($_REQUEST['AutoDelete']) ) + $sql .= ', AutoDelete = '.dbEscape($_REQUEST['AutoDelete']); + if ( !empty($_REQUEST['background']) ) + $sql .= ', Background = '.dbEscape($_REQUEST['background']); + if ( !empty($_REQUEST['concurrent']) ) + $sql .= ', Concurrent = '.dbEscape($_REQUEST['concurrent']); + $sql .= $endSql; + dbQuery( $sql ); + if ( $filterName ) { + $filter = dbFetchOne( 'SELECT * FROM Filters WHERE Name=?', NULL, array($filterName) ); + if ( $filter ) { + # This won't work yet because refreshparent refreshes the old filter. Need to do a redirect instead of a refresh. + $_REQUEST['Id'] = $filter['Id']; } else { - dbQuery( "INSERT INTO Groups SET Name=?, MonitorIds=?", array( $_POST['newGroup']['Name'], $monitors ) ); + Error("No new Id despite new name"); } - $view = 'none'; + } + $refreshParent = '/index.php?view=filter&Id='.$_REQUEST['Id']; } - if ( !empty($_REQUEST['gid']) && $action == "delete" ) { - dbQuery( "delete from Groups where Id = ?", array($_REQUEST['gid']) ); - if ( isset($_COOKIE['zmGroup']) ) - { - if ( $_REQUEST['gid'] == $_COOKIE['zmGroup'] ) - { - unset( $_COOKIE['zmGroup'] ); - setcookie( "zmGroup", "", time()-3600*24*2 ); - $refreshParent = true; - } - } + } // end if canedit events + } // end if action == filter + } // end if canview events + + // Event scope actions, edit permissions required + if ( canEdit( 'Events' ) ) { + if ( $action == 'rename' && isset($_REQUEST['eventName']) && !empty($_REQUEST['eid']) ) { + dbQuery( 'UPDATE Events SET Name=? WHERE Id=?', array( $_REQUEST['eventName'], $_REQUEST['eid'] ) ); + } else if ( $action == 'eventdetail' ) { + if ( !empty($_REQUEST['eid']) ) { + dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['eid'] ) ); + $refreshParent = true; + } else { + foreach( getAffectedIds( 'markEid' ) as $markEid ) { + dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid ) ); + $refreshParent = true; + } + } + } elseif ( $action == 'archive' || $action == 'unarchive' ) { + $archiveVal = ($action == 'archive')?1:0; + if ( !empty($_REQUEST['eid']) ) { + dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $_REQUEST['eid']) ); + } else { + foreach( getAffectedIds( 'markEid' ) as $markEid ) { + dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $markEid ) ); + $refreshParent = true; + } + } + } elseif ( $action == 'delete' ) { + foreach( getAffectedIds( 'markEid' ) as $markEid ) { + deleteEvent( $markEid ); + $refreshParent = true; + } + if ( isset( $_REQUEST['object'] ) and ( $_REQUEST['object'] == 'filter' ) ) { + if ( !empty($_REQUEST['Id']) ) { + dbQuery( 'DELETE FROM Filters WHERE Id=?', array( $_REQUEST['Id'] ) ); + //$refreshParent = true; + } + } + } + } + + // Monitor control actions, require a monitor id and control view permissions for that monitor + if ( !empty($_REQUEST['mid']) && canView( 'Control', $_REQUEST['mid'] ) ) { + require_once( 'control_functions.php' ); + require_once( 'Monitor.php' ); + $mid = validInt($_REQUEST['mid']); + if ( $action == 'control' ) { + $monitor = new Monitor( $mid ); + + $ctrlCommand = buildControlCommand( $monitor ); + sendControlCommand( $monitor->Id(), $ctrlCommand ); + } elseif ( $action == 'settings' ) { + $args = " -m " . escapeshellarg($mid); + $args .= " -B" . escapeshellarg($_REQUEST['newBrightness']); + $args .= " -C" . escapeshellarg($_REQUEST['newContrast']); + $args .= " -H" . escapeshellarg($_REQUEST['newHue']); + $args .= " -O" . escapeshellarg($_REQUEST['newColour']); + + $zmuCommand = getZmuCommand( $args ); + + $zmuOutput = exec( $zmuCommand ); + list( $brightness, $contrast, $hue, $colour ) = explode( ' ', $zmuOutput ); + dbQuery( 'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?', array($brightness, $contrast, $hue, $colour, $mid)); + } + } + + // Control capability actions, require control edit permissions + if ( canEdit( 'Control' ) ) { + if ( $action == 'controlcap' ) { + if ( !empty($_REQUEST['cid']) ) { + $control = dbFetchOne( 'SELECT * FROM Controls WHERE Id = ?', NULL, array($_REQUEST['cid']) ); + } else { + $control = array(); + } + + // Define a field type for anything that's not simple text equivalent + $types = array( + // Empty + ); + + $columns = getTableColumns( 'Controls' ); + foreach ( $columns as $name=>$type ) { + if ( preg_match( '/^(Can|Has)/', $name ) ) { + $types[$name] = 'toggle'; + } + } + $changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['cid']) ) { + dbQuery( "update Controls set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['cid']) ); + } else { + dbQuery( "insert into Controls set ".implode( ", ", $changes ) ); + //$_REQUEST['cid'] = dbInsertId(); } $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markCids']) ) { + foreach( $_REQUEST['markCids'] as $markCid ) { + dbQuery( "delete from Controls where Id = ?", array($markCid) ); + dbQuery( "update Monitors set Controllable = 0, ControlId = 0 where ControlId = ?", array($markCid) ); + $refreshParent = true; + } + } } + } - // System edit actions - if ( canEdit( 'System' ) ) - { - if ( isset( $_REQUEST['object'] ) and ( $_REQUEST['object'] == 'server' ) ) { + // Monitor edit actions, require a monitor id and edit permissions for that monitor + if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) { + $mid = validInt($_REQUEST['mid']); + if ( $action == 'function' ) { + $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array($mid) ); - if ( $action == "Save" ) { - if ( !empty($_REQUEST['id']) ) - $dbServer = dbFetchOne( "SELECT * FROM Servers WHERE Id=?", NULL, array($_REQUEST['id']) ); - else - $dbServer = array(); + $newFunction = validStr($_REQUEST['newFunction']); + # Because we use a checkbox, it won't get passed in the request. So not being in _REQUEST means 0 + $newEnabled = ( !isset( $_REQUEST['newEnabled'] ) or $_REQUEST['newEnabled'] != '1' ) ? '0' : '1'; + $oldFunction = $monitor['Function']; + $oldEnabled = $monitor['Enabled']; + if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) { + dbQuery( 'UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?', array( $newFunction, $newEnabled, $mid ) ); - $types = array(); - $changes = getFormChanges( $dbServer, $_REQUEST['newServer'], $types ); - - if ( count( $changes ) ) { - if ( !empty($_REQUEST['id']) ) { - dbQuery( "UPDATE Servers SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); - } else { - dbQuery( "INSERT INTO Servers set ".implode( ", ", $changes ) ); - } - $refreshParent = true; - } - $view = 'none'; - } else if ( $action == 'delete' ) { - if ( !empty($_REQUEST['markIds']) ) { - foreach( $_REQUEST['markIds'] as $Id ) - dbQuery( "DELETE FROM Servers WHERE Id=?", array($Id) ); - } - $refreshParent = true; - } else { - Error( "Unknown action $action in saving Server" ); - } - - } else if ( $action == "version" && isset($_REQUEST['option']) ) - { - $option = $_REQUEST['option']; - switch( $option ) - { - case 'go' : - { - // Ignore this, the caller will open the page itself - break; - } - case 'ignore' : - { - dbQuery( "update Config set Value = '".ZM_DYN_LAST_VERSION."' where Name = 'ZM_DYN_CURR_VERSION'" ); - break; - } - case 'hour' : - case 'day' : - case 'week' : - { - $nextReminder = time(); - if ( $option == 'hour' ) - { - $nextReminder += 60*60; - } - elseif ( $option == 'day' ) - { - $nextReminder += 24*60*60; - } - elseif ( $option == 'week' ) - { - $nextReminder += 7*24*60*60; - } - dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_NEXT_REMINDER'" ); - break; - } - case 'never' : - { - dbQuery( "update Config set Value = '0' where Name = 'ZM_CHECK_FOR_UPDATES'" ); - break; - } - } + $monitor['Function'] = $newFunction; + $monitor['Enabled'] = $newEnabled; + if ( daemonCheck() ) { + $restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled); + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, $restart?'restart':'' ); + zmaControl( $monitor, 'start' ); } - if ( $action == "donate" && isset($_REQUEST['option']) ) - { - $option = $_REQUEST['option']; - switch( $option ) - { - case 'go' : - { - // Ignore this, the caller will open the page itself - break; - } - case 'hour' : - case 'day' : - case 'week' : - case 'month' : - { - $nextReminder = time(); - if ( $option == 'hour' ) - { - $nextReminder += 60*60; - } - elseif ( $option == 'day' ) - { - $nextReminder += 24*60*60; - } - elseif ( $option == 'week' ) - { - $nextReminder += 7*24*60*60; - } - elseif ( $option == 'month' ) - { - $nextReminder += 30*24*60*60; - } - dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_DONATE_REMINDER_TIME'" ); - break; - } - case 'never' : - case 'already' : - { - dbQuery( "update Config set Value = '0' where Name = 'ZM_DYN_SHOW_DONATE_REMINDER'" ); - break; - } - } + $refreshParent = true; + } + } elseif ( $action == 'zone' && isset( $_REQUEST['zid'] ) ) { + $zid = validInt($_REQUEST['zid']); + $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array($mid) ); + + if ( !empty($zid) ) { + $zone = dbFetchOne( 'SELECT * FROM Zones WHERE MonitorId=? AND Id=?', NULL, array( $mid, $zid ) ); + } else { + $zone = array(); + } + + if ( $_REQUEST['newZone']['Units'] == 'Percent' ) { + $_REQUEST['newZone']['MinAlarmPixels'] = intval(($_REQUEST['newZone']['MinAlarmPixels']*$_REQUEST['newZone']['Area'])/100); + $_REQUEST['newZone']['MaxAlarmPixels'] = intval(($_REQUEST['newZone']['MaxAlarmPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MinFilterPixels']) ) + $_REQUEST['newZone']['MinFilterPixels'] = intval(($_REQUEST['newZone']['MinFilterPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MaxFilterPixels']) ) + $_REQUEST['newZone']['MaxFilterPixels'] = intval(($_REQUEST['newZone']['MaxFilterPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MinBlobPixels']) ) + $_REQUEST['newZone']['MinBlobPixels'] = intval(($_REQUEST['newZone']['MinBlobPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MaxBlobPixels']) ) + $_REQUEST['newZone']['MaxBlobPixels'] = intval(($_REQUEST['newZone']['MaxBlobPixels']*$_REQUEST['newZone']['Area'])/100); + } + + unset( $_REQUEST['newZone']['Points'] ); + $types = array(); + $changes = getFormChanges( $zone, $_REQUEST['newZone'], $types ); + + if ( count( $changes ) ) { + if ( $zid > 0 ) { + dbQuery( "UPDATE Zones SET ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zid) ); + } else { + dbQuery( "INSERT INTO Zones SET MonitorId=?, ".implode( ", ", $changes ), array( $mid ) ); } - if ( $action == "options" && isset($_REQUEST['tab']) ) - { - $configCat = $configCats[$_REQUEST['tab']]; - $changed = false; - foreach ( $configCat as $name=>$value ) - { - unset( $newValue ); - if ( $value['Type'] == "boolean" && empty($_REQUEST['newConfig'][$name]) ) - $newValue = 0; - elseif ( isset($_REQUEST['newConfig'][$name]) ) - $newValue = preg_replace( "/\r\n/", "\n", stripslashes( $_REQUEST['newConfig'][$name] ) ); - - if ( isset($newValue) && ($newValue != $value['Value']) ) - { - dbQuery( 'UPDATE Config SET Value=? WHERE Name=?', array( $newValue, $name ) ); - $changed = true; - } - } - if ( $changed ) - { - switch( $_REQUEST['tab'] ) - { - case "system" : - case "config" : - case "paths" : - $restartWarning = true; - break; - case "web" : - case "tools" : - break; - case "logging" : - case "network" : - case "mail" : - case "upload" : - $restartWarning = true; - break; - case "highband" : - case "medband" : - case "lowband" : - break; - } - } - loadConfig( false ); - } - elseif ( $action == "user" ) - { - if ( !empty($_REQUEST['uid']) ) - $dbUser = dbFetchOne( "SELECT * FROM Users WHERE Id=?", NULL, array($_REQUEST['uid']) ); - else - $dbUser = array(); - - $types = array(); - $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); - - if ( $_REQUEST['newUser']['Password'] ) - $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; - else - unset( $changes['Password'] ); - - if ( count( $changes ) ) - { - if ( !empty($_REQUEST['uid']) ) - { - dbQuery( "update Users set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['uid']) ); - } - else - { - dbQuery( "insert into Users set ".implode( ", ", $changes ) ); - } - $refreshParent = true; - if ( $dbUser['Username'] == $user['Username'] ) - userLogin( $dbUser['Username'], $dbUser['Password'] ); - } - $view = 'none'; - } - elseif ( $action == "state" ) - { - if ( !empty($_REQUEST['runState']) ) - { - //if ( $cookies ) session_write_close(); - packageControl( $_REQUEST['runState'] ); - $refreshParent = true; - } - } - elseif ( $action == "save" ) - { - if ( !empty($_REQUEST['runState']) || !empty($_REQUEST['newState']) ) - { - $sql = "select Id,Function,Enabled from Monitors order by Id"; - $definitions = array(); - foreach( dbFetchAll( $sql ) as $monitor ) - { - $definitions[] = $monitor['Id'].":".$monitor['Function'].":".$monitor['Enabled']; - } - $definition = join( ',', $definitions ); - if ( $_REQUEST['newState'] ) - $_REQUEST['runState'] = $_REQUEST['newState']; - dbQuery( "replace into States set Name=?, Definition=?", array( $_REQUEST['runState'],$definition) ); - } - } - elseif ( $action == "delete" ) - { - if ( isset($_REQUEST['runState']) ) - dbQuery( "delete from States where Name=?", array($_REQUEST['runState']) ); - - if ( isset($_REQUEST['markUids']) ) - { - foreach( $_REQUEST['markUids'] as $markUid ) - dbQuery( "delete from Users where Id = ?", array($markUid) ); - if ( $markUid == $user['Id'] ) - userLogout(); - } - } - } - else - { - if ( ZM_USER_SELF_EDIT && $action == "user" ) - { - $uid = $user['Id']; - - $dbUser = dbFetchOne( "select Id, Password, Language from Users where Id = ?", NULL, array($uid) ); - - $types = array(); - $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); - - if ( !empty($_REQUEST['newUser']['Password']) ) - $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; - else - unset( $changes['Password'] ); - if ( count( $changes ) ) - { - dbQuery( "update Users set ".implode( ", ", $changes )." where Id=?", array($uid) ); - $refreshParent = true; - } - $view = 'none'; - } - } - - if ( $action == "reset" ) - { - $_SESSION['zmEventResetTime'] = strftime( STRF_FMT_DATETIME_DB ); - setcookie( "zmEventResetTime", $_SESSION['zmEventResetTime'], time()+3600*24*30*12*10 ); //if ( $cookies ) session_write_close(); + if ( daemonCheck() ) { + if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) { + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'restart' ); + zmaControl( $monitor, 'start' ); + } else { + zmaControl( $mid, 'restart' ); + } + } + if ( $_REQUEST['newZone']['Type'] == 'Privacy' && $monitor['Controllable'] ) { + require_once( 'control_functions.php' ); + sendControlCommand( $mid, 'quit' ); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'plugin' && isset($_REQUEST['pl'])) { + $sql='SELECT * FROM PluginsConfig WHERE MonitorId=? AND ZoneId=? AND pluginName=?'; + $pconfs=dbFetchAll( $sql, NULL, array( $mid, $_REQUEST['zid'], $_REQUEST['pl'] ) ); + $changes=0; + foreach( $pconfs as $pconf ) { + $value=$_REQUEST['pluginOpt'][$pconf['Name']]; + if(array_key_exists($pconf['Name'], $_REQUEST['pluginOpt']) && ($pconf['Value']!=$value)) { + dbQuery("UPDATE PluginsConfig SET Value=? WHERE id=?", array( $value, $pconf['Id'] ) ); + $changes++; + } + } + if($changes>0) { + if ( daemonCheck() ) { + zmaControl( $mid, 'restart' ); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'sequence' && isset($_REQUEST['smid']) ) { + $smid = validInt($_REQUEST['smid']); + $monitor = dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($mid) ); + $smonitor = dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($smid) ); + + dbQuery( 'update Monitors set Sequence=? where Id=?', array( $smonitor['Sequence'], $monitor['Id'] ) ); + dbQuery( 'update Monitors set Sequence=? WHERE Id=?', array( $monitor['Sequence'], $smonitor['Id'] ) ); + + $refreshParent = true; + fixSequences(); + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markZids']) ) { + $deletedZid = 0; + foreach( $_REQUEST['markZids'] as $markZid ) { + $zone = dbFetchOne( 'select * from Zones where Id=?', NULL, array($markZid) ); + dbQuery( 'delete from Zones WHERE MonitorId=? AND Id=?', array( $mid, $markZid) ); + $deletedZid = 1; + } + if ( $deletedZid ) { + //if ( $cookies ) + //session_write_close(); + if ( daemonCheck() ) { + if ( $zone['Type'] == 'Privacy' ) { + zmaControl( $mid, 'stop' ); + zmcControl( $mid, 'restart' ); + zmaControl( $mid, 'start' ); + } else { + zmaControl( $mid, 'restart' ); + } + } // end if daemonCheck() + $refreshParent = true; + } // end if deletedzid + } // end if isset($_REQUEST['markZids']) + } // end if action + } // end if $mid and canEdit($mid) + + // Monitor edit actions, monitor id derived, require edit permissions for that monitor + if ( canEdit( 'Monitors' ) ) { + if ( $action == 'monitor' ) { + $mid = 0; + if ( !empty($_REQUEST['mid']) ) { + $mid = validInt($_REQUEST['mid']); + $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid) ); + + if ( ZM_OPT_X10 ) { + $x10Monitor = dbFetchOne( 'SELECT * FROM TriggersX10 WHERE MonitorId=?', NULL, array($mid) ); + if ( !$x10Monitor ) + $x10Monitor = array(); + } + } else { + $monitor = array(); + if ( ZM_OPT_X10 ) { + $x10Monitor = array(); + } + } + + // Define a field type for anything that's not simple text equivalent + $types = array( + 'Triggers' => 'set', + 'Controllable' => 'toggle', + 'TrackMotion' => 'toggle', + 'Enabled' => 'toggle', + 'DoNativeMotDet' => 'toggle', + 'Exif' => 'toggle', + 'RTSPDescribe' => 'toggle', + 'RecordAudio' => 'toggle', + ); + + $columns = getTableColumns( 'Monitors' ); + $changes = getFormChanges( $monitor, $_REQUEST['newMonitor'], $types, $columns ); + + if ( count( $changes ) ) { + if ( $mid ) { + + # If we change anything that changes the shared mem size, zma can complain. So let's stop first. + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'stop' ); + dbQuery( 'UPDATE Monitors SET '.implode( ", ", $changes ).' WHERE Id =?', array($mid) ); + if ( isset($changes['Name']) ) { + $saferOldName = basename( $monitor['Name'] ); + $saferNewName = basename( $_REQUEST['newMonitor']['Name'] ); + rename( ZM_DIR_EVENTS."/".$saferOldName, ZM_DIR_EVENTS."/".$saferNewName); + } + if ( isset($changes['Width']) || isset($changes['Height']) ) { + $newW = $_REQUEST['newMonitor']['Width']; + $newH = $_REQUEST['newMonitor']['Height']; + $newA = $newW * $newH; + $oldW = $monitor['Width']; + $oldH = $monitor['Height']; + $oldA = $oldW * $oldH; + + $zones = dbFetchAll( 'SELECT * FROM Zones WHERE MonitorId=?', NULL, array($mid) ); + foreach ( $zones as $zone ) { + $newZone = $zone; + $points = coordsToPoints( $zone['Coords'] ); + for ( $i = 0; $i < count($points); $i++ ) { + $points[$i]['x'] = intval(($points[$i]['x']*($newW-1))/($oldW-1)); + $points[$i]['y'] = intval(($points[$i]['y']*($newH-1))/($oldH-1)); + } + $newZone['Coords'] = pointsToCoords( $points ); + $newZone['Area'] = intval(round(($zone['Area']*$newA)/$oldA)); + $newZone['MinAlarmPixels'] = intval(round(($newZone['MinAlarmPixels']*$newA)/$oldA)); + $newZone['MaxAlarmPixels'] = intval(round(($newZone['MaxAlarmPixels']*$newA)/$oldA)); + $newZone['MinFilterPixels'] = intval(round(($newZone['MinFilterPixels']*$newA)/$oldA)); + $newZone['MaxFilterPixels'] = intval(round(($newZone['MaxFilterPixels']*$newA)/$oldA)); + $newZone['MinBlobPixels'] = intval(round(($newZone['MinBlobPixels']*$newA)/$oldA)); + $newZone['MaxBlobPixels'] = intval(round(($newZone['MaxBlobPixels']*$newA)/$oldA)); + + $changes = getFormChanges( $zone, $newZone, $types ); + + if ( count( $changes ) ) { + dbQuery( "update Zones set ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zone['Id'] ) ); + } + } + } + } elseif ( ! $user['MonitorIds'] ) { // Can only create new monitors if we are not restricted to specific monitors +# FIXME This is actually a race condition. Should lock the table. + $maxSeq = dbFetchOne( 'SELECT max(Sequence) AS MaxSequence FROM Monitors', 'MaxSequence' ); + $changes[] = 'Sequence = '.($maxSeq+1); + + dbQuery( 'INSERT INTO Monitors SET '.implode( ', ', $changes ) ); + $mid = dbInsertId(); + $zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height']; + dbQuery( "insert into Zones set MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) ); + //$view = 'none'; + mkdir( ZM_DIR_EVENTS.'/'.$mid, 0755 ); + $saferName = basename($_REQUEST['newMonitor']['Name']); + symlink( $mid, ZM_DIR_EVENTS.'/'.$saferName ); + if ( isset($_COOKIE['zmGroup']) ) { + dbQuery( "UPDATE Groups SET MonitorIds = concat(MonitorIds,',".$mid."') WHERE Id=?", array($_COOKIE['zmGroup']) ); + } + } else { + Error("Users with Monitors restrictions cannot create new monitors."); + } + $restart = true; + } + + if ( ZM_OPT_X10 ) { + $x10Changes = getFormChanges( $x10Monitor, $_REQUEST['newX10Monitor'] ); + + if ( count( $x10Changes ) ) { + if ( $x10Monitor && isset($_REQUEST['newX10Monitor']) ) { + dbQuery( "update TriggersX10 set ".implode( ", ", $x10Changes )." where MonitorId=?", array($mid) ); + } elseif ( !$user['MonitorIds'] ) { + if ( !$x10Monitor ) { + dbQuery( "insert into TriggersX10 set MonitorId = ?, ".implode( ", ", $x10Changes ), array( $mid ) ); + } else { + dbQuery( "delete from TriggersX10 where MonitorId = ?", array($mid) ); + } + } + $restart = true; + } + } + + if ( $restart ) { + $new_monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid) ); + //fixDevices(); + //if ( $cookies ) + //session_write_close(); + + zmcControl( $new_monitor, 'start' ); + zmaControl( $new_monitor, 'start' ); + + if ( $monitor['Controllable'] ) { + require_once( 'control_functions.php' ); + sendControlCommand( $mid, 'quit' ); + } + // really should thump zmwatch and maybe zmtrigger too. + //daemonControl( 'restart', 'zmwatch.pl' ); + $refreshParent = true; + } // end if restart + $view = 'none'; } + if ( $action == 'delete' ) { + if ( isset($_REQUEST['markMids']) && !$user['MonitorIds'] ) { + foreach( $_REQUEST['markMids'] as $markMid ) { + if ( canEdit( 'Monitors', $markMid ) ) { + if ( $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($markMid) ) ) { + if ( daemonCheck() ) { + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'stop' ); + } + + // This is the important stuff + dbQuery( 'DELETE FROM Monitors WHERE Id = ?', array($markMid) ); + dbQuery( 'DELETE FROM Zones WHERE MonitorId = ?', array($markMid) ); + if ( ZM_OPT_X10 ) + dbQuery( 'DELETE FROM TriggersX10 WHERE MonitorId=?', array($markMid) ); + + fixSequences(); + + // If fast deletes are on, then zmaudit will clean everything else up later + // If fast deletes are off and there are lots of events then this step may + // well time out before completing, in which case zmaudit will still tidy up + if ( !ZM_OPT_FAST_DELETE ) { + // Slight hack, we maybe should load *, but we happen to know that the deleteEvent function uses Id and StartTime. + $markEids = dbFetchAll( 'SELECT Id,StartTime FROM Events WHERE MonitorId=?', NULL, array($markMid) ); + foreach( $markEids as $markEid ) + deleteEvent( $markEid, $markMid ); + + deletePath( ZM_DIR_EVENTS.'/'.basename($monitor['Name']) ); + deletePath( ZM_DIR_EVENTS.'/'.$monitor['Id'] ); // I'm trusting the Id. + } // end if ZM_OPT_FAST_DELETE + } // end if found the monitor in the db + } // end if canedit this monitor + } // end foreach monitor in MarkMid + } // markMids is set and we aren't limited to specific monitors + } // end if action == Delete + } + + // Device view actions + if ( canEdit( 'Devices' ) ) { + if ( $action == 'device' ) { + if ( !empty($_REQUEST['command']) ) { + setDeviceStatusX10( $_REQUEST['key'], $_REQUEST['command'] ); + } elseif ( isset( $_REQUEST['newDevice'] ) ) { + if ( isset($_REQUEST['did']) ) { + dbQuery( "update Devices set Name=?, KeyString=? where Id=?", array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'], $_REQUEST['did']) ); + } else { + dbQuery( "insert into Devices set Name=?, KeyString=?", array( $_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'] ) ); + } + $refreshParent = true; + $view = 'none'; + } + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markDids']) ) { + foreach( $_REQUEST['markDids'] as $markDid ) { + dbQuery( "delete from Devices where Id=?", array($markDid) ); + $refreshParent = true; + } + } + } // end if action + } // end if canedit devices + + // Group view actions + if ( canView( 'Groups' ) && $action == 'setgroup' ) { + if ( !empty($_REQUEST['gid']) ) { + setcookie( 'zmGroup', validInt($_REQUEST['gid']), time()+3600*24*30*12*10 ); + } else { + setcookie( 'zmGroup', '', time()-3600*24*2 ); + } + $refreshParent = true; + } + + // Group edit actions + if ( canEdit( 'Groups' ) ) { + if ( $action == 'group' ) { +# Should probably verfy that each monitor id is a valid monitor, that we have access to. HOwever at the moment, you have to have System permissions to do this + $monitors = empty( $_POST['newGroup']['MonitorIds'] ) ? NULL : implode(',', $_POST['newGroup']['MonitorIds']); + if ( !empty($_POST['gid']) ) { + dbQuery( "UPDATE Groups SET Name=?, MonitorIds=? WHERE Id=?", array($_POST['newGroup']['Name'], $monitors, $_POST['gid']) ); + } else { + dbQuery( "INSERT INTO Groups SET Name=?, MonitorIds=?", array( $_POST['newGroup']['Name'], $monitors ) ); + } + $view = 'none'; + } + if ( !empty($_REQUEST['gid']) && $action == 'delete' ) { + dbQuery( 'DELETE FROM Groups WHERE Id = ?', array($_REQUEST['gid']) ); + if ( isset($_COOKIE['zmGroup']) ) { + if ( $_REQUEST['gid'] == $_COOKIE['zmGroup'] ) { + unset( $_COOKIE['zmGroup'] ); + setcookie( 'zmGroup', '', time()-3600*24*2 ); + $refreshParent = true; + } + } + } + $refreshParent = true; + } // end if can edit groups + + // System edit actions + if ( canEdit( 'System' ) ) { + if ( isset( $_REQUEST['object'] ) ) { + if ( $_REQUEST['object'] == 'server' ) { + + if ( $action == 'Save' ) { + if ( !empty($_REQUEST['id']) ) + $dbServer = dbFetchOne( 'SELECT * FROM Servers WHERE Id=?', NULL, array($_REQUEST['id']) ); + else + $dbServer = array(); + + $types = array(); + $changes = getFormChanges( $dbServer, $_REQUEST['newServer'], $types ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['id']) ) { + dbQuery( "UPDATE Servers SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); + } else { + dbQuery( "INSERT INTO Servers set ".implode( ", ", $changes ) ); + } + $refreshParent = true; + } + $view = 'none'; + } else if ( $action == 'delete' ) { + if ( !empty($_REQUEST['markIds']) ) { + foreach( $_REQUEST['markIds'] as $Id ) + dbQuery( "DELETE FROM Servers WHERE Id=?", array($Id) ); + } + $refreshParent = true; + } else { + Error( "Unknown action $action in saving Server" ); + } + } else if ( $_REQUEST['object'] == 'storage' ) { + if ( $action == 'Save' ) { + if ( !empty($_REQUEST['id']) ) + $dbStorage = dbFetchOne( 'SELECT * FROM Storage WHERE Id=?', NULL, array($_REQUEST['id']) ); + else + $dbStorage = array(); + + $types = array(); + $changes = getFormChanges( $dbStorage, $_REQUEST['newStorage'], $types ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['id']) ) { + dbQuery( "UPDATE Storage SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); + } else { + dbQuery( "INSERT INTO Storage set ".implode( ", ", $changes ) ); + } + $refreshParent = true; + } + $view = 'none'; + } else if ( $action == 'delete' ) { + if ( !empty($_REQUEST['markIds']) ) { + foreach( $_REQUEST['markIds'] as $Id ) + dbQuery( 'DELETE FROM Storage WHERE Id=?', array($Id) ); + } + $refreshParent = true; + } else { + Error( "Unknown action $action in saving Storage" ); + } + } # end if isset($_REQUEST['object'] ) + + } else if ( $action == 'version' && isset($_REQUEST['option']) ) { + $option = $_REQUEST['option']; + switch( $option ) { + case 'go' : + { + // Ignore this, the caller will open the page itself + break; + } + case 'ignore' : + { + dbQuery( "update Config set Value = '".ZM_DYN_LAST_VERSION."' where Name = 'ZM_DYN_CURR_VERSION'" ); + break; + } + case 'hour' : + case 'day' : + case 'week' : + { + $nextReminder = time(); + if ( $option == 'hour' ) { + $nextReminder += 60*60; + } elseif ( $option == 'day' ) { + $nextReminder += 24*60*60; + } elseif ( $option == 'week' ) { + $nextReminder += 7*24*60*60; + } + dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_NEXT_REMINDER'" ); + break; + } + case 'never' : + { + dbQuery( "update Config set Value = '0' where Name = 'ZM_CHECK_FOR_UPDATES'" ); + break; + } + } + } + if ( $action == 'donate' && isset($_REQUEST['option']) ) { + $option = $_REQUEST['option']; + switch( $option ) { + case 'go' : + { + // Ignore this, the caller will open the page itself + break; + } + case 'hour' : + case 'day' : + case 'week' : + case 'month' : + { + $nextReminder = time(); + if ( $option == 'hour' ) { + $nextReminder += 60*60; + } elseif ( $option == 'day' ) { + $nextReminder += 24*60*60; + } elseif ( $option == 'week' ) { + $nextReminder += 7*24*60*60; + } elseif ( $option == 'month' ) { + $nextReminder += 30*24*60*60; + } + dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_DONATE_REMINDER_TIME'" ); + break; + } + case 'never' : + case 'already' : + { + dbQuery( "update Config set Value = '0' where Name = 'ZM_DYN_SHOW_DONATE_REMINDER'" ); + break; + } + } // end switch option + } + if ( $action == 'options' && isset($_REQUEST['tab']) ) { + $configCat = $configCats[$_REQUEST['tab']]; + $changed = false; + foreach ( $configCat as $name=>$value ) { + unset( $newValue ); + if ( $value['Type'] == 'boolean' && empty($_REQUEST['newConfig'][$name]) ) + $newValue = 0; + elseif ( isset($_REQUEST['newConfig'][$name]) ) + $newValue = preg_replace( "/\r\n/", "\n", stripslashes( $_REQUEST['newConfig'][$name] ) ); + + if ( isset($newValue) && ($newValue != $value['Value']) ) { + dbQuery( 'UPDATE Config SET Value=? WHERE Name=?', array( $newValue, $name ) ); + $changed = true; + } + } + if ( $changed ) { + switch( $_REQUEST['tab'] ) { + case 'system' : + case 'config' : + case 'paths' : + $restartWarning = true; + break; + case 'web' : + case 'tools' : + break; + case 'logging' : + case 'network' : + case 'mail' : + case 'upload' : + $restartWarning = true; + break; + case 'highband' : + case 'medband' : + case 'lowband' : + break; + } + } + loadConfig( false ); + } elseif ( $action == 'user' ) { + if ( !empty($_REQUEST['uid']) ) + $dbUser = dbFetchOne( "SELECT * FROM Users WHERE Id=?", NULL, array($_REQUEST['uid']) ); + else + $dbUser = array(); + + $types = array(); + $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); + + if ( $_REQUEST['newUser']['Password'] ) + $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; + else + unset( $changes['Password'] ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['uid']) ) { + dbQuery( "update Users set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['uid']) ); + # If we are updating the logged in user, then update our session user data. + if ( $user and ( $dbUser['Username'] == $user['Username'] ) ) + userLogin( $dbUser['Username'], $dbUser['Password'] ); + } else { + dbQuery( "insert into Users set ".implode( ", ", $changes ) ); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'state' ) { + if ( !empty($_REQUEST['runState']) ) { + //if ( $cookies ) session_write_close(); + packageControl( $_REQUEST['runState'] ); + $refreshParent = true; + } + } elseif ( $action == 'save' ) { + if ( !empty($_REQUEST['runState']) || !empty($_REQUEST['newState']) ) { + $sql = 'SELECT Id,Function,Enabled FROM Monitors ORDER BY Id'; + $definitions = array(); + foreach( dbFetchAll( $sql ) as $monitor ) + { + $definitions[] = $monitor['Id'].":".$monitor['Function'].":".$monitor['Enabled']; + } + $definition = join( ',', $definitions ); + if ( $_REQUEST['newState'] ) + $_REQUEST['runState'] = $_REQUEST['newState']; + dbQuery( "replace into States set Name=?, Definition=?", array( $_REQUEST['runState'],$definition) ); + } + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['runState']) ) + dbQuery( "delete from States where Name=?", array($_REQUEST['runState']) ); + + if ( isset($_REQUEST['markUids']) ) { + foreach( $_REQUEST['markUids'] as $markUid ) + dbQuery( "delete from Users where Id = ?", array($markUid) ); + if ( $markUid == $user['Id'] ) + userLogout(); + } + } + } else { + if ( ZM_USER_SELF_EDIT && $action == 'user' ) { + $uid = $user['Id']; + + $dbUser = dbFetchOne( 'SELECT Id, Password, Language FROM Users WHERE Id = ?', NULL, array($uid) ); + + $types = array(); + $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); + + if ( !empty($_REQUEST['newUser']['Password']) ) + $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; + else + unset( $changes['Password'] ); + if ( count( $changes ) ) { + dbQuery( "update Users set ".implode( ", ", $changes )." where Id=?", array($uid) ); + $refreshParent = true; + } + $view = 'none'; + } + } + + if ( $action == 'reset' ) { + $_SESSION['zmEventResetTime'] = strftime( STRF_FMT_DATETIME_DB ); + setcookie( 'zmEventResetTime', $_SESSION['zmEventResetTime'], time()+3600*24*30*12*10 ); + //if ( $cookies ) session_write_close(); + } } ?> diff --git a/web/includes/config.php.in b/web/includes/config.php.in index ba96d9a54..991414a56 100644 --- a/web/includes/config.php.in +++ b/web/includes/config.php.in @@ -15,15 +15,17 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // // // This section contains options substituted by the zmconfig.pl utility, do not edit these directly // define( "ZM_CONFIG", "@ZM_CONFIG@" ); // Path to config file +define( "ZM_CONFIG_SUBDIR", "@ZM_CONFIG_SUBDIR@" ); // Path to config subfolder // Define, and override any given in config file define( "ZM_VERSION", "@VERSION@" ); // Version +define( "ZM_DIR_TEMP", "@ZM_TMPDIR@" ); $configFile = ZM_CONFIG; $localConfigFile = basename($configFile); @@ -35,19 +37,28 @@ if ( file_exists( $localConfigFile ) && filesize( $localConfigFile ) > 0 ) error_log( "Warning, overriding installed $localConfigFile file with local copy" ); $configFile = $localConfigFile; } - -$cfg = fopen( $configFile, "r") or die("Could not open config file."); -while ( !feof($cfg) ) -{ - $str = fgets( $cfg, 256 ); - if ( preg_match( '/^\s*$/', $str )) - continue; - elseif ( preg_match( '/^\s*#/', $str )) - continue; - elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*(.*?)\s*$/', $str, $matches )) - define( $matches[1], $matches[2] ); + +# Process name, value pairs from the main config file first +$configvals = process_configfile($configFile); + +# Search for user created config files. If one or more are found then +# update our config value array with those values +$configSubFolder = ZM_CONFIG_SUBDIR; +if ( is_dir($configSubFolder) ) { + if ( is_readable($configSubFolder) ) { + foreach ( glob("$configSubFolder/*.conf") as $filename ) { + $configvals = array_replace($configvals, process_configfile($filename) ); + } + } else { + error_log( "WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on $configSubFolder." ); + } +} + +# Now that our array our finalized, define each key => value +# pair in the array as a constant +foreach( $configvals as $key => $value) { + define( $key, $value ); } -fclose( $cfg ); // // This section is options normally derived from other options or configuration @@ -188,5 +199,27 @@ if ( ! defined('ZM_SERVER_ID') ) { } } +function process_configfile($configFile) { + if ( is_readable( $configFile ) ) { + $configvals = array(); + + $cfg = fopen( $configFile, "r") or die("Could not open config file."); + while ( !feof($cfg) ) + { + $str = fgets( $cfg, 256 ); + if ( preg_match( '/^\s*$/', $str )) + continue; + elseif ( preg_match( '/^\s*#/', $str )) + continue; + elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*(.*?)\s*$/', $str, $matches )) + $configvals[$matches[1]] = $matches[2]; + } + fclose( $cfg ); + return( $configvals ); + } else { + error_log( "WARNING: ZoneMinder configuration file found but is not readable. Check file permissions on $configFile." ); + return( false ); + } +} ?> diff --git a/web/includes/control_functions.php b/web/includes/control_functions.php index dfbc8cb3e..d5fa06675 100644 --- a/web/includes/control_functions.php +++ b/web/includes/control_functions.php @@ -9,7 +9,7 @@ function buildControlCommand( $monitor ) $slow = 0.9; // Threshold for slow speed/timeouts $turbo = 0.9; // Threshold for turbo speed - if ( preg_match( '/^([a-z]+)([A-Z][a-z]+)([A-Z][a-z]+)+$/', $_REQUEST['control'], $matches ) ) + if ( preg_match( '/^([a-z]+)([A-Z][a-z]+)([A-Za-z]+)+$/', $_REQUEST['control'], $matches ) ) { $command = $matches[1]; $mode = $matches[2]; @@ -278,6 +278,8 @@ function buildControlCommand( $monitor ) } } } + } else { + Error("Invalid control parameter: " . $_REQUEST['control'] ); } } elseif ( isset($_REQUEST['x']) && isset($_REQUEST['y']) ) diff --git a/web/includes/csrf/LICENSE.txt b/web/includes/csrf/LICENSE.txt new file mode 100644 index 000000000..37b07717d --- /dev/null +++ b/web/includes/csrf/LICENSE.txt @@ -0,0 +1,9 @@ +Copyright (c) 2008-2013, Edward Z. Yang +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/web/includes/csrf/NEWS.txt b/web/includes/csrf/NEWS.txt new file mode 100644 index 000000000..66d52f6da --- /dev/null +++ b/web/includes/csrf/NEWS.txt @@ -0,0 +1,69 @@ + + [[ news ]] + +1.0.4 released 2013-07-17 + + [SECURITY FIXES] + + - When secret key was not explicitly set, it was not being used + by the csrf_hash() function. Thanks sparticvs for reporting. + + [FEATURES] + + - The default 'CSRF check failed' page now offers a handy 'Try + again' button, which resubmits the form. + + [BUG FIXES] + + - The fix for 1.0.3 inadvertantly turned off XMLHttpRequest + overloading for all browsers; it has now been fixed to only + apply to IE. + +1.0.3 released 2012-01-31 + + [BUG FIXES] + + - Internet Explorer 8 adds support for XMLHttpRequest.prototype, + but this support is broken for method overloading. We + explicitly disable JavaScript overloading for Internet Explorer. + Thanks Kelly Lu for reporting. + + - A global declaration was omitted, resulting in a variable + not being properly introduced in PHP 5.3. Thanks Whitney Beck for + reporting. + +1.0.2 released 2009-03-08 + + [SECURITY FIXES] + + - Due to a typo, csrf-magic accidentally treated the secret key + as always present. This means that there was a possible CSRF + attack against users without any cookies. No attacks in the + wild were known at the time of this release. Thanks Jakub + Vrána for reporting. + +1.0.1 released 2008-11-02 + + [NEW FEATURES] + + - Support for composite tokens; this also fixes a bug with using + IP-based tokens for users with cookies disabled. + + - Native support cookie tokens; use csrf_conf('cookie', $name) to + specify the name of a cookie that the CSRF token should be + placed in. This is useful if you have a Squid cache, and need + to configure it to ignore this token. + + - Tips/tricks section in README.txt. + + - There is now a two hour expiration time on all tokens. This + can be modified using csrf_conf('expires', $seconds). + + - ClickJacking protection using an iframe breaker. Disable with + csrf_conf('frame-breaker', false). + + [BUG FIXES] + + - CsrfMagic.send() incorrectly submitted GET requests twice, + once without the magic token and once with the token. Reported + by Kelly Lu . diff --git a/web/includes/csrf/README.txt b/web/includes/csrf/README.txt new file mode 100644 index 000000000..98d225dba --- /dev/null +++ b/web/includes/csrf/README.txt @@ -0,0 +1,160 @@ + + [[ csrf-magic ]] + +Add the following line to the top of all web-accessible PHP pages. If you have +a common file included by everything, put it there. + + include_once '/path/to/csrf-magic.php'; + +Do it, test it, then forget about it. csrf-magic is protecting you if nothing +bad happens. Read on if you run into problems. + + + TABLE OF CONTENTS + + ------------------- + + 1. TIPS AND TRICKS + 2. AJAX + 3. CONFIGURE + 4. THANKS + 5. FOOTNOTES + + ------------------- + + + +1. TIPS AND TRICKS + + * If your JavaScript and AJAX is persistently getting errors, check the + AJAX section below on how to fix. + + * The CSS overlay protection makes it impossible to display your website + in frame/iframe elements. You can disable it with + csrf_conf('frame-breaker', false) in your csrf_startup() function. + + * csrf-magic will start a session. To disable, use csrf_conf('auto-session', + false) in your csrf_startup() function. + + * The default error message is a little user unfriendly. Write your own + function which outputs an error message and set csrf_conf('callback', + 'myCallbackFunction') in your csrf_startup() function. + + * Make sure csrf_conf('secret', 'ABCDEFG') has something random in it. If + the directory csrf-magic.php is in is writable, csrf-magic will generate + a secret key for you in the csrf-secret.php file. + + * Remember you can use auto_prepend to include csrf-magic.php on all your + pages. You may want to create a stub file which you can include that + includes csrf-magic.php as well as performs configuration. + + * The default expiration time for tokens is two hours. If you expect your + users to need longer to fill out forms, be sure to enable double + submission when the token is invalid. + + +2. AJAX + +csrf-magic has the ability to dynamically rewrite AJAX requests which use +XMLHttpRequest. However, due to the invasiveness of this procedure, it is +not enabled by default. You can enable it by adding this code before you +include csrf-magic.php. + + function csrf_startup() { + csrf_conf('rewrite-js', '/web/path/to/csrf-magic.js'); + } + // include_once '/path/to/csrf-magic.php'; + +(Be sure to place csrf-magic.js somewhere web accessible). + +The default method CSRF Magic uses to rewrite AJAX requests will +only work for browsers with support for XmlHttpRequest.prototype (this excludes +all versions of Internet Explorer). See this page for more information: +http://stackoverflow.com/questions/664315/internet-explorer-8-prototypes-and-xmlhttprequest + +However, csrf-magic.js will +automatically detect and play nice with the following JavaScript frameworks: + + * jQuery + * Prototype + * MooTools + * Ext + * Dojo + +(Note 2013-07-16: It has been a long time since this manual support has +been updated, and some JavaScript libraries have placed their copies of XHR +in local variables in closures, which makes it difficult for us to monkey-patch +it in automatically.) + +To rewrite your own JavaScript library to use csrf-magic.js, you should modify +your function that generates XMLHttpRequest to have this at the end: + + return new CsrfMagic(xhrObject); + +With whatever xhrObject may be. If you have literal instances of XMLHttpRequest +in your code, find and replace ''new XMLHttpRequest'' with ''new CsrfMagic'' +(CsrfMagic will automatically instantiate an XMLHttpRequest object in a +cross-platform manner as necessary). + +If you don't want csrf-magic monkeying around with your XMLHttpRequest object, +you can manually rewrite your AJAX code to include the variable. The important +information is stored in the global variables csrfMagicName and csrfMagicToken. +CsrfMagic.process may also be of interest, as it takes one parameter, a +querystring, and prepends the CSRF token to the value. + + +3. CONFIGURE + +csrf-magic has some configuration options that you can set inside the +csrf_startup() function. They are described in csrf-magic.php, and you can +set them using the convenience function csrf_conf($name, $value). + +For example, this is a recommended configuration: + + /** + * This is a function that gets called if a csrf check fails. csrf-magic will + * then exit afterwards. + */ + function my_csrf_callback() { + echo "You're doing bad things young man!"; + } + + function csrf_startup() { + + // While csrf-magic has a handy little heuristic for determining whether + // or not the content in the buffer is HTML or not, you should really + // give it a nudge and turn rewriting *off* when the content is + // not HTML. Implementation details will vary. + if (isset($_POST['ajax'])) csrf_conf('rewrite', false); + + // This is a secret value that must be set in order to enable username + // and IP based checks. Don't show this to anyone. A secret id will + // automatically be generated for you if the directory csrf-magic.php + // is placed in is writable. + csrf_conf('secret', 'ABCDEFG123456'); + + // This enables JavaScript rewriting and will ensure your AJAX calls + // don't stop working. + csrf_conf('rewrite-js', '/csrf-magic.js'); + + // This makes csrf-magic call my_csrf_callback() before exiting when + // there is a bad csrf token. This lets me customize the error page. + csrf_conf('callback', 'my_csrf_callback'); + + // While this is enabled by default to boost backwards compatibility, + // for security purposes it should ideally be off. Some users can be + // NATted or have dialup addresses which rotate frequently. Cookies + // are much more reliable. + csrf_conf('allow-ip', false); + + } + + // Finally, include the library + include_once '/path/to/csrf-magic.php'; + +Configuration gets stored in the $GLOBALS['csrf'] array. + + +4. THANKS + +My thanks to Chris Shiflett, for unintentionally inspiring the idea, as well +as telling me the original variant of the Bob and Mallory story, +and the Django CSRF Middleware authors, who thought up of this before me. +Gareth Heyes suggested using the frame-breaker option to protect against +CSS overlay attacks. diff --git a/web/includes/csrf/csrf-magic.js b/web/includes/csrf/csrf-magic.js new file mode 100644 index 000000000..0989c1065 --- /dev/null +++ b/web/includes/csrf/csrf-magic.js @@ -0,0 +1,191 @@ +/** + * @file + * + * Rewrites XMLHttpRequest to automatically send CSRF token with it. In theory + * plays nice with other JavaScript libraries, needs testing though. + */ + +// Here are the basic overloaded method definitions +// The wrapper must be set BEFORE onreadystatechange is written to, since +// a bug in ActiveXObject prevents us from properly testing for it. +CsrfMagic = function(real) { + // try to make it ourselves, if you didn't pass it + if (!real) try { real = new XMLHttpRequest; } catch (e) {;} + if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {;} + if (!real) try { real = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {;} + if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP.4.0'); } catch (e) {;} + this.csrf = real; + // properties + var csrfMagic = this; + real.onreadystatechange = function() { + csrfMagic._updateProps(); + return csrfMagic.onreadystatechange ? csrfMagic.onreadystatechange() : null; + }; + csrfMagic._updateProps(); +} + +CsrfMagic.prototype = { + + open: function(method, url, async, username, password) { + if (method == 'POST') this.csrf_isPost = true; + // deal with Opera bug, thanks jQuery + if (username) return this.csrf_open(method, url, async, username, password); + else return this.csrf_open(method, url, async); + }, + csrf_open: function(method, url, async, username, password) { + if (username) return this.csrf.open(method, url, async, username, password); + else return this.csrf.open(method, url, async); + }, + + send: function(data) { + if (!this.csrf_isPost) return this.csrf_send(data); + prepend = csrfMagicName + '=' + csrfMagicToken + '&'; + // XXX: Removed to eliminate 'Refused to set unsafe header "Content-length" ' errors in modern browsers + // if (this.csrf_purportedLength === undefined) { + // this.csrf_setRequestHeader("Content-length", this.csrf_purportedLength + prepend.length); + // delete this.csrf_purportedLength; + // } + delete this.csrf_isPost; + return this.csrf_send(prepend + data); + }, + csrf_send: function(data) { + return this.csrf.send(data); + }, + + setRequestHeader: function(header, value) { + // We have to auto-set this at the end, since we don't know how long the + // nonce is when added to the data. + if (this.csrf_isPost && header == "Content-length") { + this.csrf_purportedLength = value; + return; + } + return this.csrf_setRequestHeader(header, value); + }, + csrf_setRequestHeader: function(header, value) { + return this.csrf.setRequestHeader(header, value); + }, + + abort: function() { + return this.csrf.abort(); + }, + getAllResponseHeaders: function() { + return this.csrf.getAllResponseHeaders(); + }, + getResponseHeader: function(header) { + return this.csrf.getResponseHeader(header); + } // , +} + +// proprietary +CsrfMagic.prototype._updateProps = function() { + this.readyState = this.csrf.readyState; + if (this.readyState == 4) { + this.responseText = this.csrf.responseText; + this.responseXML = this.csrf.responseXML; + this.status = this.csrf.status; + this.statusText = this.csrf.statusText; + } +} +CsrfMagic.process = function(base) { + if(typeof base == 'object') { + base[csrfMagicName] = csrfMagicToken; + return base; + } + var prepend = csrfMagicName + '=' + csrfMagicToken; + if (base) return prepend + '&' + base; + return prepend; +} +// callback function for when everything on the page has loaded +CsrfMagic.end = function() { + // This rewrites forms AGAIN, so in case buffering didn't work this + // certainly will. + forms = document.getElementsByTagName('form'); + for (var i = 0; i < forms.length; i++) { + form = forms[i]; + if (form.method.toUpperCase() !== 'POST') continue; + if (form.elements[csrfMagicName]) continue; + var input = document.createElement('input'); + input.setAttribute('name', csrfMagicName); + input.setAttribute('value', csrfMagicToken); + input.setAttribute('type', 'hidden'); + form.appendChild(input); + } +} + +// Sets things up for Mozilla/Opera/nice browsers +// We very specifically match against Internet Explorer, since they haven't +// implemented prototypes correctly yet. +if (window.XMLHttpRequest && window.XMLHttpRequest.prototype && '\v' != 'v') { + var x = XMLHttpRequest.prototype; + var c = CsrfMagic.prototype; + + // Save the original functions + x.csrf_open = x.open; + x.csrf_send = x.send; + x.csrf_setRequestHeader = x.setRequestHeader; + + // Notice that CsrfMagic is itself an instantiatable object, but only + // open, send and setRequestHeader are necessary as decorators. + x.open = c.open; + x.send = c.send; + x.setRequestHeader = c.setRequestHeader; +} else { + // The only way we can do this is by modifying a library you have been + // using. We support YUI, script.aculo.us, prototype, MooTools, + // jQuery, Ext and Dojo. + if (window.jQuery) { + // jQuery didn't implement a new XMLHttpRequest function, so we have + // to do this the hard way. + jQuery.csrf_ajax = jQuery.ajax; + jQuery.ajax = function( s ) { + if (s.type && s.type.toUpperCase() == 'POST') { + s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s)); + if ( s.data && s.processData && typeof s.data != "string" ) { + s.data = jQuery.param(s.data); + } + s.data = CsrfMagic.process(s.data); + } + return jQuery.csrf_ajax( s ); + } + } + if (window.Prototype) { + // This works for script.aculo.us too + Ajax.csrf_getTransport = Ajax.getTransport; + Ajax.getTransport = function() { + return new CsrfMagic(Ajax.csrf_getTransport()); + } + } + if (window.MooTools) { + Browser.csrf_Request = Browser.Request; + Browser.Request = function () { + return new CsrfMagic(Browser.csrf_Request()); + } + } + if (window.YAHOO) { + // old YUI API + YAHOO.util.Connect.csrf_createXhrObject = YAHOO.util.Connect.createXhrObject; + YAHOO.util.Connect.createXhrObject = function (transaction) { + obj = YAHOO.util.Connect.csrf_createXhrObject(transaction); + obj.conn = new CsrfMagic(obj.conn); + return obj; + } + } + if (window.Ext) { + // Ext can use other js libraries as loaders, so it has to come last + // Ext's implementation is pretty identical to Yahoo's, but we duplicate + // it for comprehensiveness's sake. + Ext.lib.Ajax.csrf_createXhrObject = Ext.lib.Ajax.createXhrObject; + Ext.lib.Ajax.createXhrObject = function (transaction) { + obj = Ext.lib.Ajax.csrf_createXhrObject(transaction); + obj.conn = new CsrfMagic(obj.conn); + return obj; + } + } + if (window.dojo) { + // NOTE: this doesn't work with latest dojo + dojo.csrf__xhrObj = dojo._xhrObj; + dojo._xhrObj = function () { + return new CsrfMagic(dojo.csrf__xhrObj()); + } + } +} diff --git a/web/includes/csrf/csrf-magic.php b/web/includes/csrf/csrf-magic.php new file mode 100644 index 000000000..153417f4e --- /dev/null +++ b/web/includes/csrf/csrf-magic.php @@ -0,0 +1,405 @@ + + */ +$GLOBALS['csrf']['input-name'] = '__csrf_magic'; + +/** + * Set this to false if your site must work inside of frame/iframe elements, + * but do so at your own risk: this configuration protects you against CSS + * overlay attacks that defeat tokens. + */ +$GLOBALS['csrf']['frame-breaker'] = true; + +/** + * Whether or not CSRF Magic should be allowed to start a new session in order + * to determine the key. + */ +$GLOBALS['csrf']['auto-session'] = true; + +/** + * Whether or not csrf-magic should produce XHTML style tags. + */ +$GLOBALS['csrf']['xhtml'] = true; + +// FUNCTIONS: + +// Don't edit this! +$GLOBALS['csrf']['version'] = '1.0.4'; + +/** + * Rewrites
on the fly to add CSRF tokens to them. This can also + * inject our JavaScript library. + */ +function csrf_ob_handler($buffer, $flags) { + // Even though the user told us to rewrite, we should do a quick heuristic + // to check if the page is *actually* HTML. We don't begin rewriting until + // we hit the first "; + $buffer = preg_replace('#(]*method\s*=\s*["\']post["\'][^>]*>)#i', '$1' . $input, $buffer); + if ($GLOBALS['csrf']['frame-breaker']) { + $buffer = str_ireplace('', '', $buffer); + } + if ($js = $GLOBALS['csrf']['rewrite-js']) { + $buffer = str_ireplace( + '', + ''. + '', + $buffer + ); + $script = ''; + $buffer = str_ireplace('', $script . '', $buffer, $count); + if (!$count) { + $buffer .= $script; + } + } + return $buffer; +} + +/** + * Checks if this is a post request, and if it is, checks if the nonce is valid. + * @param bool $fatal Whether or not to fatally error out if there is a problem. + * @return True if check passes or is not necessary, false if failure. + */ +function csrf_check($fatal = true) { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') return true; + csrf_start(); + $name = $GLOBALS['csrf']['input-name']; + $ok = false; + $tokens = ''; + do { + if (!isset($_POST[$name])) break; + // we don't regenerate a token and check it because some token creation + // schemes are volatile. + $tokens = $_POST[$name]; + if (!csrf_check_tokens($tokens)) break; + $ok = true; + } while (false); + if ($fatal && !$ok) { + $callback = $GLOBALS['csrf']['callback']; + if (trim($tokens, 'A..Za..z0..9:;,') !== '') $tokens = 'hidden'; + $callback($tokens); + exit; + } + return $ok; +} + +/** + * Retrieves a valid token(s) for a particular context. Tokens are separated + * by semicolons. + */ +function csrf_get_tokens() { + $has_cookies = !empty($_COOKIE); + + // $ip implements a composite key, which is sent if the user hasn't sent + // any cookies. It may or may not be used, depending on whether or not + // the cookies "stick" + $secret = csrf_get_secret(); + if (!$has_cookies && $secret) { + // :TODO: Harden this against proxy-spoofing attacks + $IP_ADDRESS = (isset($_SERVER['IP_ADDRESS']) ? $_SERVER['IP_ADDRESS'] : $_SERVER['REMOTE_ADDR']); + $ip = ';ip:' . csrf_hash($IP_ADDRESS); + } else { + $ip = ''; + } + csrf_start(); + + // These are "strong" algorithms that don't require per se a secret + if (session_id()) return 'sid:' . csrf_hash(session_id()) . $ip; + if ($GLOBALS['csrf']['cookie']) { + $val = csrf_generate_secret(); + setcookie($GLOBALS['csrf']['cookie'], $val); + return 'cookie:' . csrf_hash($val) . $ip; + } + if ($GLOBALS['csrf']['key']) return 'key:' . csrf_hash($GLOBALS['csrf']['key']) . $ip; + // These further algorithms require a server-side secret + if (!$secret) return 'invalid'; + if ($GLOBALS['csrf']['user'] !== false) { + return 'user:' . csrf_hash($GLOBALS['csrf']['user']); + } + if ($GLOBALS['csrf']['allow-ip']) { + return ltrim($ip, ';'); + } + return 'invalid'; +} + +function csrf_flattenpost($data) { + $ret = array(); + foreach($data as $n => $v) { + $ret = array_merge($ret, csrf_flattenpost2(1, $n, $v)); + } + return $ret; +} +function csrf_flattenpost2($level, $key, $data) { + if(!is_array($data)) return array($key => $data); + $ret = array(); + foreach($data as $n => $v) { + $nk = $level >= 1 ? $key."[$n]" : "[$n]"; + $ret = array_merge($ret, csrf_flattenpost2($level+1, $nk, $v)); + } + return $ret; +} + +/** + * @param $tokens is safe for HTML consumption + */ +function csrf_callback($tokens) { + // (yes, $tokens is safe to echo without escaping) + header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); + $data = ''; + foreach (csrf_flattenpost($_POST) as $key => $value) { + if ($key == $GLOBALS['csrf']['input-name']) continue; + $data .= ''; + } + echo "CSRF check failed + +

CSRF check failed. Your form session may have expired, or you may not have + cookies enabled.

+ $data +

Debug: $tokens

+"; +} + +/** + * Checks if a composite token is valid. Outward facing code should use this + * instead of csrf_check_token() + */ +function csrf_check_tokens($tokens) { + if (is_string($tokens)) $tokens = explode(';', $tokens); + foreach ($tokens as $token) { + if (csrf_check_token($token)) return true; + } + return false; +} + +/** + * Checks if a token is valid. + */ +function csrf_check_token($token) { + if (strpos($token, ':') === false) return false; + list($type, $value) = explode(':', $token, 2); + if (strpos($value, ',') === false) return false; + list($x, $time) = explode(',', $token, 2); + if ($GLOBALS['csrf']['expires']) { + if (time() > $time + $GLOBALS['csrf']['expires']) return false; + } + switch ($type) { + case 'sid': + return $value === csrf_hash(session_id(), $time); + case 'cookie': + $n = $GLOBALS['csrf']['cookie']; + if (!$n) return false; + if (!isset($_COOKIE[$n])) return false; + return $value === csrf_hash($_COOKIE[$n], $time); + case 'key': + if (!$GLOBALS['csrf']['key']) return false; + return $value === csrf_hash($GLOBALS['csrf']['key'], $time); + // We could disable these 'weaker' checks if 'key' was set, but + // that doesn't make me feel good then about the cookie-based + // implementation. + case 'user': + if (!csrf_get_secret()) return false; + if ($GLOBALS['csrf']['user'] === false) return false; + return $value === csrf_hash($GLOBALS['csrf']['user'], $time); + case 'ip': + if (!csrf_get_secret()) return false; + // do not allow IP-based checks if the username is set, or if + // the browser sent cookies + if ($GLOBALS['csrf']['user'] !== false) return false; + if (!empty($_COOKIE)) return false; + if (!$GLOBALS['csrf']['allow-ip']) return false; + $IP_ADDRESS = (isset($_SERVER['IP_ADDRESS']) ? $_SERVER['IP_ADDRESS'] : $_SERVER['REMOTE_ADDR']); + return $value === csrf_hash($IP_ADDRESS, $time); + } + return false; +} + +/** + * Sets a configuration value. + */ +function csrf_conf($key, $val) { + if (!isset($GLOBALS['csrf'][$key])) { + trigger_error('No such configuration ' . $key, E_USER_WARNING); + return; + } + $GLOBALS['csrf'][$key] = $val; +} + +/** + * Starts a session if we're allowed to. + */ +function csrf_start() { + if ($GLOBALS['csrf']['auto-session'] && !session_id()) { + session_start(); + } +} + +/** + * Retrieves the secret, and generates one if necessary. + */ +function csrf_get_secret() { + if ($GLOBALS['csrf']['secret']) return $GLOBALS['csrf']['secret']; + $dir = dirname(__FILE__); + $file = $dir . '/csrf-secret.php'; + $secret = ''; + if (file_exists($file)) { + include $file; + return $secret; + } + if (is_writable($dir)) { + $secret = csrf_generate_secret(); + $fh = fopen($file, 'w'); + fwrite($fh, 'setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - } catch(PDOException $ex ) { - echo "Unable to connect to ZM db." . $ex->getMessage(); - $dbConn = null; - } + if (strpos(ZM_DB_HOST, ':')) { + // Host variable may carry a port or socket. + list($host, $portOrSocket) = explode(':', ZM_DB_HOST, 2); + if (ctype_digit($portOrSocket)) { + $socket = ':host='.$host . ';port='.$portOrSocket; + } else { + $socket = ':unix_socket='.$portOrSocket; + } + } else { + $socket = ':host='.ZM_DB_HOST; + } + + try { + $dbConn = new PDO( ZM_DB_TYPE . $socket . ';dbname='.ZM_DB_NAME, ZM_DB_USER, ZM_DB_PASS ); + $dbConn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + $dbConn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } catch(PDOException $ex ) { + echo "Unable to connect to ZM db." . $ex->getMessage(); + $dbConn = null; + } } dbConnect(); -function dbDisconnect() -{ - global $dbConn; - $dbConn = null; +function dbDisconnect() { + global $dbConn; + $dbConn = null; } -function dbLogOff() -{ - global $dbLogLevel; - $dbLogLevel = DB_LOG_OFF; +function dbLogOff() { + global $dbLogLevel; + $dbLogLevel = DB_LOG_OFF; } -function dbLogOn() -{ - global $dbLogLevel; - $dbLogLevel = DB_LOG_ONLY; +function dbLogOn() { + global $dbLogLevel; + $dbLogLevel = DB_LOG_ONLY; } -function dbLogDebug() -{ - global $dbLogLevel; - $dbLogLevel = DB_LOG_DEBUG; +function dbLogDebug() { + global $dbLogLevel; + $dbLogLevel = DB_LOG_DEBUG; } -function dbDebug() -{ - dbLogDebug(); +function dbDebug() { + dbLogDebug(); } -function dbLog( $sql, $update=false ) -{ - global $dbLogLevel; - $noExecute = $update && ($dbLogLevel >= DB_LOG_DEBUG); - if ( $dbLogLevel > DB_LOG_OFF ) - Debug( "SQL-LOG: $sql".($noExecute?" (not executed)":"") ); - return( $noExecute ); +function dbLog( $sql, $update=false ) { + global $dbLogLevel; + $noExecute = $update && ($dbLogLevel >= DB_LOG_DEBUG); + if ( $dbLogLevel > DB_LOG_OFF ) + Logger::Debug( "SQL-LOG: $sql".($noExecute?" (not executed)":"") ); + return( $noExecute ); } -function dbError( $sql ) -{ - Fatal( "SQL-ERR '".$dbConn->errorInfo()."', statement was '".$sql."'" ); +function dbError( $sql ) { + Fatal( "SQL-ERR '".$dbConn->errorInfo()."', statement was '".$sql."'" ); } -function dbEscape( $string ) -{ - global $dbConn; - if ( version_compare( phpversion(), "4.3.0", "<") ) - if ( get_magic_quotes_gpc() ) - return( $dbConn->quote( stripslashes( $string ) ) ); - else - return( $dbConn->quote( $string ) ); +function dbEscape( $string ) { + global $dbConn; + if ( version_compare( phpversion(), "4.3.0", "<") ) + if ( get_magic_quotes_gpc() ) + return( $dbConn->quote( stripslashes( $string ) ) ); else - if ( get_magic_quotes_gpc() ) - return( $dbConn->quote( stripslashes( $string ) ) ); - else - return( $dbConn->quote( $string ) ); + return( $dbConn->quote( $string ) ); + else + if ( get_magic_quotes_gpc() ) + return( $dbConn->quote( stripslashes( $string ) ) ); + else + return( $dbConn->quote( $string ) ); } function dbQuery( $sql, $params=NULL ) { - global $dbConn; - if ( dbLog( $sql, true ) ) - return; - $result = NULL; - try { - if ( isset($params) ) { - $result = $dbConn->prepare( $sql ); - $result->execute( $params ); - } else { - $result = $dbConn->query( $sql ); - } - } catch(PDOException $e) { - Fatal( "SQL-ERR '".$e->getMessage()."', statement was '".$sql."'" ); + global $dbConn; + if ( dbLog( $sql, true ) ) + return; + $result = NULL; + try { + if ( isset($params) ) { + $result = $dbConn->prepare( $sql ); + $result->execute( $params ); + } else { + $result = $dbConn->query( $sql ); } - return( $result ); + } catch(PDOException $e) { + Fatal( "SQL-ERR '".$e->getMessage()."', statement was '".$sql."'" ); + } + return( $result ); } -function dbFetchOne( $sql, $col=false, $params=NULL ) -{ - $result = dbQuery( $sql, $params ); - if ( ! $result ) { - Fatal( "SQL-ERR dbFetchOne no result, statement was '".$sql."'" . ( $params ? 'params: ' . join(',',$params) : '' ) ); - return false; - } +function dbFetchOne( $sql, $col=false, $params=NULL ) { + $result = dbQuery( $sql, $params ); + if ( ! $result ) { + Fatal( "SQL-ERR dbFetchOne no result, statement was '".$sql."'" . ( $params ? 'params: ' . join(',',$params) : '' ) ); + return false; + } - if ( $result && $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) - return( $col?$dbRow[$col]:$dbRow ); - return( false ); + if ( $result && $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) { + if ( $col ) { + if ( ! isset( $dbRow[$col] ) ) { + Warning( "$col does not exist in the returned row" ); + } + return $dbRow[$col]; + } + return $dbRow; + } + return( false ); } -function dbFetchAll( $sql, $col=false, $params=NULL ) -{ - $result = dbQuery( $sql, $params ); - if ( ! $result ) { - Fatal( "SQL-ERR dbFetchAll no result, statement was '".$sql."'" . ( $params ? 'params: ' .join(',', $params) : '' ) ); - return false; - } +function dbFetchAll( $sql, $col=false, $params=NULL ) { + $result = dbQuery( $sql, $params ); + if ( ! $result ) { + Fatal( "SQL-ERR dbFetchAll no result, statement was '".$sql."'" . ( $params ? 'params: ' .join(',', $params) : '' ) ); + return false; + } - $dbRows = array(); - while( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) - $dbRows[] = $col?$dbRow[$col]:$dbRow; - return( $dbRows ); + $dbRows = array(); + while( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) + $dbRows[] = $col?$dbRow[$col]:$dbRow; + return( $dbRows ); } -function dbFetchAssoc( $sql, $indexCol, $dataCol=false ) -{ - $result = dbQuery( $sql ); +function dbFetchAssoc( $sql, $indexCol, $dataCol=false ) { + $result = dbQuery( $sql ); - $dbRows = array(); - while( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) - $dbRows[$dbRow[$indexCol]] = $dataCol?$dbRow[$dataCol]:$dbRow; - return( $dbRows ); + $dbRows = array(); + while( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) + $dbRows[$dbRow[$indexCol]] = $dataCol?$dbRow[$dataCol]:$dbRow; + return( $dbRows ); } -function dbFetch( $sql, $col=false ) -{ - return( dbFetchAll( $sql, $col ) ); +function dbFetch( $sql, $col=false ) { + return( dbFetchAll( $sql, $col ) ); } -function dbFetchNext( $result, $col=false ) -{ - if ( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) - return( $col?$dbRow[$col]:$dbRow ); - return( false ); +function dbFetchNext( $result, $col=false ) { + if ( $dbRow = $result->fetch( PDO::FETCH_ASSOC ) ) + return( $col?$dbRow[$col]:$dbRow ); + return( false ); } -function dbNumRows( $sql ) -{ - $result = dbQuery( $sql ); - return( $result->rowCount() ); +function dbNumRows( $sql ) { + $result = dbQuery( $sql ); + return( $result->rowCount() ); } -function dbInsertId() -{ - global $dbConn; - return( $dbConn->lastInsertId() ); +function dbInsertId() { + global $dbConn; + return( $dbConn->lastInsertId() ); } -function getEnumValues( $table, $column ) -{ - $row = dbFetchOne( "describe $table $column" ); - preg_match_all( "/'([^']+)'/", $row['Type'], $matches ); - return( $matches[1] ); +function getEnumValues( $table, $column ) { + $row = dbFetchOne( "describe $table $column" ); + preg_match_all( "/'([^']+)'/", $row['Type'], $matches ); + return( $matches[1] ); } -function getSetValues( $table, $column ) -{ - return( getEnumValues( $table, $column ) ); +function getSetValues( $table, $column ) { + return( getEnumValues( $table, $column ) ); } -function getUniqueValues( $table, $column, $asString=1 ) -{ - $values = array(); - $sql = "select distinct $column from $table where (not isnull($column) and $column != '') order by $column"; - foreach( dbFetchAll( $sql ) as $row ) - { - if ( $asString ) - $values[$row[$column]] = $row[$column]; - else - $values[] = $row[$column]; - } - return( $values ); +function getUniqueValues( $table, $column, $asString=1 ) { + $values = array(); + $sql = "select distinct $column from $table where (not isnull($column) and $column != '') order by $column"; + foreach( dbFetchAll( $sql ) as $row ) { + if ( $asString ) + $values[$row[$column]] = $row[$column]; + else + $values[] = $row[$column]; + } + return( $values ); } -function getTableColumns( $table, $asString=1 ) -{ - $columns = array(); - $sql = "describe $table"; - foreach( dbFetchAll( $sql ) as $row ) - { - if ( $asString ) - $columns[$row['Field']] = $row['Type']; - else - $columns[] = $row['Type']; - } - return( $columns ); +function getTableColumns( $table, $asString=1 ) { + $columns = array(); + $sql = "describe $table"; + foreach( dbFetchAll( $sql ) as $row ) { + if ( $asString ) + $columns[$row['Field']] = $row['Type']; + else + $columns[] = $row['Type']; + } + return( $columns ); } -function getTableAutoInc( $table ) -{ - $row = dbFetchOne( "show table status where Name=?", NULL, array($table) ); - return( $row['Auto_increment'] ); +function getTableAutoInc( $table ) { + $row = dbFetchOne( "show table status where Name=?", NULL, array($table) ); + return( $row['Auto_increment'] ); } -function getTableDescription( $table, $asString=1 ) -{ - $columns = array(); - foreach( dbFetchAll( "describe $table" ) as $row ) - { - $desc = array( - 'name' => $row['Field'], - 'required' => ($row['Null']=='NO')?true:false, - 'default' => $row['Default'], - 'db' => $row, +function getTableDescription( $table, $asString=1 ) { + $columns = array(); + foreach( dbFetchAll( "describe $table" ) as $row ) { + $desc = array( + 'name' => $row['Field'], + 'required' => ($row['Null']=='NO')?true:false, + 'default' => $row['Default'], + 'db' => $row, ); - if ( preg_match( "/^varchar\((\d+)\)$/", $row['Type'], $matches ) ) - { - $desc['type'] = 'text'; - $desc['typeAttrib'] = 'varchar'; - $desc['maxLength'] = $matches[1]; - } - elseif ( preg_match( "/^(\w+)?text$/", $row['Type'], $matches ) ) - { - $desc['type'] = 'text'; - if (!empty($matches[1]) ) - $desc['typeAttrib'] = $matches[1]; - switch ( $matches[1] ) - { - case 'tiny' : - $desc['maxLength'] = 255; - break; - case 'medium' : - $desc['maxLength'] = 32768; - break; - case '' : - case 'big' : - //$desc['minLength'] = -128; - break; - default : - Error( "Unexpected text qualifier '".$matches[1]."' found for field '".$row['Field']."' in table '".$table."'" ); - break; - } - } - elseif ( preg_match( "/^(enum|set)\((.*)\)$/", $row['Type'], $matches ) ) - { - $desc['type'] = 'text'; - $desc['typeAttrib'] = $matches[1]; - preg_match_all( "/'([^']+)'/", $matches[2], $matches ); - $desc['values'] = $matches[1]; - } - elseif ( preg_match( "/^(\w+)?int\(\d+\)(?:\s+(unsigned))?$/", $row['Type'], $matches ) ) - { - $desc['type'] = 'integer'; - switch ( $matches[1] ) - { - case 'tiny' : - $desc['minValue'] = -128; - $desc['maxValue'] = 127; - break; - case 'small' : - $desc['minValue'] = -32768; - $desc['maxValue'] = 32767; - break; - case 'medium' : - $desc['minValue'] = -8388608; - $desc['maxValue'] = 8388607; - break; - case '' : - $desc['minValue'] = -2147483648; - $desc['maxValue'] = 2147483647; - break; - case 'big' : - //$desc['minValue'] = -128; - //$desc['maxValue'] = 127; - break; - default : - Error( "Unexpected integer qualifier '".$matches[1]."' found for field '".$row['Field']."' in table '".$table."'" ); - break; - } - if ( !empty($matches[1]) ) - $desc['typeAttrib'] = $matches[1]; - if ( $desc['unsigned'] = ( isset($matches[2]) && $matches[2] == 'unsigned' ) ) - { - $desc['maxValue'] += (-$desc['minValue']); - $desc['minValue'] = 0; - } - } - elseif ( preg_match( "/^(?:decimal|numeric)\((\d+)(?:,(\d+))?\)(?:\s+(unsigned))?$/", $row['Type'], $matches ) ) - { - $desc['type'] = 'fixed'; - $desc['range'] = $matches[1]; - if ( isset($matches[2]) ) - $desc['precision'] = $matches[2]; - else - $desc['precision'] = 0; - $desc['unsigned'] = ( isset($matches[3]) && $matches[3] == 'unsigned' ); - } - elseif ( preg_match( "/^(datetime|timestamp|date|time)$/", $row['Type'], $matches ) ) - { - $desc['type'] = 'datetime'; - switch ( $desc['typeAttrib'] = $matches[1] ) - { - case 'datetime' : - case 'timestamp' : - $desc['hasDate'] = true; - $desc['hasTime'] = true; - break; - case 'date' : - $desc['hasDate'] = true; - $desc['hasTime'] = false; - break; - case 'time' : - $desc['hasDate'] = false; - $desc['hasTime'] = true; - break; - } - } - else - { - Error( "Can't parse database type '".$row['Type']."' found for field '".$row['Field']."' in table '".$table."'" ); - } - - if ( $asString ) - $columns[$row['Field']] = $desc; - else - $columns[] = $desc; + if ( preg_match( "/^varchar\((\d+)\)$/", $row['Type'], $matches ) ) { + $desc['type'] = 'text'; + $desc['typeAttrib'] = 'varchar'; + $desc['maxLength'] = $matches[1]; + } elseif ( preg_match( "/^(\w+)?text$/", $row['Type'], $matches ) ) { + $desc['type'] = 'text'; + if (!empty($matches[1]) ) + $desc['typeAttrib'] = $matches[1]; + switch ( $matches[1] ) { + case 'tiny' : + $desc['maxLength'] = 255; + break; + case 'medium' : + $desc['maxLength'] = 32768; + break; + case '' : + case 'big' : + //$desc['minLength'] = -128; + break; + default : + Error( "Unexpected text qualifier '".$matches[1]."' found for field '".$row['Field']."' in table '".$table."'" ); + break; + } + } elseif ( preg_match( "/^(enum|set)\((.*)\)$/", $row['Type'], $matches ) ) { + $desc['type'] = 'text'; + $desc['typeAttrib'] = $matches[1]; + preg_match_all( "/'([^']+)'/", $matches[2], $matches ); + $desc['values'] = $matches[1]; + } elseif ( preg_match( "/^(\w+)?int\(\d+\)(?:\s+(unsigned))?$/", $row['Type'], $matches ) ) { + $desc['type'] = 'integer'; + switch ( $matches[1] ) { + case 'tiny' : + $desc['minValue'] = -128; + $desc['maxValue'] = 127; + break; + case 'small' : + $desc['minValue'] = -32768; + $desc['maxValue'] = 32767; + break; + case 'medium' : + $desc['minValue'] = -8388608; + $desc['maxValue'] = 8388607; + break; + case '' : + $desc['minValue'] = -2147483648; + $desc['maxValue'] = 2147483647; + break; + case 'big' : + //$desc['minValue'] = -128; + //$desc['maxValue'] = 127; + break; + default : + Error( "Unexpected integer qualifier '".$matches[1]."' found for field '".$row['Field']."' in table '".$table."'" ); + break; + } + if ( !empty($matches[1]) ) + $desc['typeAttrib'] = $matches[1]; + if ( $desc['unsigned'] = ( isset($matches[2]) && $matches[2] == 'unsigned' ) ) { + $desc['maxValue'] += (-$desc['minValue']); + $desc['minValue'] = 0; + } + } elseif ( preg_match( "/^(?:decimal|numeric)\((\d+)(?:,(\d+))?\)(?:\s+(unsigned))?$/", $row['Type'], $matches ) ) { + $desc['type'] = 'fixed'; + $desc['range'] = $matches[1]; + if ( isset($matches[2]) ) + $desc['precision'] = $matches[2]; + else + $desc['precision'] = 0; + $desc['unsigned'] = ( isset($matches[3]) && $matches[3] == 'unsigned' ); + } elseif ( preg_match( "/^(datetime|timestamp|date|time)$/", $row['Type'], $matches ) ) { + $desc['type'] = 'datetime'; + switch ( $desc['typeAttrib'] = $matches[1] ) { + case 'datetime' : + case 'timestamp' : + $desc['hasDate'] = true; + $desc['hasTime'] = true; + break; + case 'date' : + $desc['hasDate'] = true; + $desc['hasTime'] = false; + break; + case 'time' : + $desc['hasDate'] = false; + $desc['hasTime'] = true; + break; + } + } else { + Error( "Can't parse database type '".$row['Type']."' found for field '".$row['Field']."' in table '".$table."'" ); } - return( $columns ); + + if ( $asString ) + $columns[$row['Field']] = $desc; + else + $columns[] = $desc; + } + return( $columns ); } -function dbFetchMonitor( $mid ) -{ - return( dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ) ); +function dbFetchMonitor( $mid ) { + return( dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ) ); } -function dbFetchGroup( $gid ) -{ - return( dbFetchOne( "select * from Groups where Id = ?", NULL, array($gid) ) ); +function dbFetchGroup( $gid ) { + return( dbFetchOne( "select * from Groups where Id = ?", NULL, array($gid) ) ); } ?> diff --git a/web/includes/functions.php b/web/includes/functions.php index 2b73160db..8e0004ea9 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -15,11 +15,11 @@ // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // // Compatibility functions -if ( version_compare( phpversion(), "4.3.0", "<") ) { +if ( version_compare( phpversion(), '4.3.0', '<') ) { function ob_get_clean() { $buffer = ob_get_contents(); ob_end_clean(); @@ -27,24 +27,28 @@ if ( version_compare( phpversion(), "4.3.0", "<") ) { } } -function userLogin( $username, $password="", $passwordHashed=false ) { +# We are requiring these because this file is getting included from the api, which hasn't already included them. +require_once( 'logger.php' ); +require_once( 'database.php' ); + +function userLogin( $username, $password='', $passwordHashed=false ) { global $user, $cookies; - $sql = "select * from Users where Enabled = 1"; + $sql = 'SELECT * FROM Users WHERE Enabled = 1'; $sql_values = NULL; - if ( ZM_AUTH_TYPE == "builtin" ) { + if ( ZM_AUTH_TYPE == 'builtin' ) { if ( $passwordHashed ) { - $sql .= " AND Username=? AND Password=?"; + $sql .= ' AND Username=? AND Password=?'; } else { - $sql .= " AND Username=? AND Password=password(?)"; + $sql .= ' AND Username=? AND Password=password(?)'; } $sql_values = array( $username, $password ); } else { - $sql .= " AND Username = ?"; + $sql .= ' AND Username = ?'; $sql_values = array( $username ); } $_SESSION['username'] = $username; - if ( ZM_AUTH_RELAY == "plain" ) { + if ( ZM_AUTH_RELAY == 'plain' ) { // Need to save this in session $_SESSION['password'] = $password; } @@ -52,11 +56,14 @@ function userLogin( $username, $password="", $passwordHashed=false ) { if ( $dbUser = dbFetchOne( $sql, NULL, $sql_values ) ) { Info( "Login successful for user \"$username\"" ); $_SESSION['user'] = $user = $dbUser; - if ( ZM_AUTH_TYPE == "builtin" ) { + unset($_SESSION['loginFailed']); + if ( ZM_AUTH_TYPE == 'builtin' ) { $_SESSION['passwordHash'] = $user['Password']; } + session_regenerate_id(); } else { Warning( "Login denied for user \"$username\"" ); + $_SESSION['loginFailed'] = true; unset( $user ); } if ( $cookies ) @@ -76,11 +83,11 @@ function userLogout() { } function noCacheHeaders() { - header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past - header("Last-Modified: ".gmdate( "D, d M Y H:i:s" )." GMT"); // always modified - header("Cache-Control: no-store, no-cache, must-revalidate"); // HTTP/1.1 - header("Cache-Control: post-check=0, pre-check=0", false); - header("Pragma: no-cache"); // HTTP/1.0 + header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past + header('Last-Modified: '.gmdate( 'D, d M Y H:i:s' ).' GMT'); // always modified + header('Cache-Control: no-store, no-cache, must-revalidate'); // HTTP/1.1 + header('Cache-Control: post-check=0, pre-check=0', false); + header('Pragma: no-cache'); // HTTP/1.0 } function CORSHeaders() { @@ -97,28 +104,28 @@ function CORSHeaders() { $Server = new Server( $row ); if ( $_SERVER['HTTP_ORIGIN'] == $Server->Url() ) { $valid = true; - header("Access-Control-Allow-Origin: " . $Server->Url() ); - header("Access-Control-Allow-Headers: x-requested-with,x-request"); + header('Access-Control-Allow-Origin: ' . $Server->Url() ); + header('Access-Control-Allow-Headers: x-requested-with,x-request'); } } if ( ! $valid ) { - Warning( $_SERVER['HTTP_ORIGIN'] . " is not found in servers list." ); + Warning( $_SERVER['HTTP_ORIGIN'] . ' is not found in servers list.' ); } } } function getAuthUser( $auth ) { - if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == "hashed" && !empty($auth) ) { - $remoteAddr = ""; + if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' && !empty($auth) ) { + $remoteAddr = ''; if ( ZM_AUTH_HASH_IPS ) { $remoteAddr = $_SERVER['REMOTE_ADDR']; if ( !$remoteAddr ) { Error( "Can't determine remote address for authentication, using empty string" ); - $remoteAddr = ""; + $remoteAddr = ''; } } - $sql = "select Username, Password, Enabled, Stream+0, Events+0, Control+0, Monitors+0, System+0, MonitorIds from Users where Enabled = 1"; + $sql = 'select Username, Password, Enabled, Stream+0, Events+0, Control+0, Monitors+0, System+0, MonitorIds from Users where Enabled = 1'; foreach ( dbFetchAll( $sql ) as $user ) { $now = time(); for ( $i = 0; $i < 2; $i++, $now -= (60*60) ) { // Try for last two hours @@ -137,7 +144,7 @@ function getAuthUser( $auth ) { } function generateAuthHash( $useRemoteAddr ) { - if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == "hashed" ) { + if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) { $time = localtime(); if ( $useRemoteAddr ) { $authKey = ZM_AUTH_HASH_SECRET.$_SESSION['username'].$_SESSION['passwordHash'].$_SESSION['remoteAddr'].$time[2].$time[3].$time[4].$time[5]; @@ -146,7 +153,7 @@ function generateAuthHash( $useRemoteAddr ) { } $auth = md5( $authKey ); } else { - $auth = ""; + $auth = ''; } return( $auth ); } @@ -155,24 +162,24 @@ function getStreamSrc( $args, $querySep='&' ) { $streamSrc = ZM_BASE_URL.ZM_PATH_ZMS; if ( ZM_OPT_USE_AUTH ) { - if ( ZM_AUTH_RELAY == "hashed" ) { - $args[] = "auth=".generateAuthHash( ZM_AUTH_HASH_IPS ); - } elseif ( ZM_AUTH_RELAY == "plain" ) { - $args[] = "user=".$_SESSION['username']; - $args[] = "pass=".$_SESSION['password']; - } elseif ( ZM_AUTH_RELAY == "none" ) { - $args[] = "user=".$_SESSION['username']; + if ( ZM_AUTH_RELAY == 'hashed' ) { + $args[] = 'auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); + } elseif ( ZM_AUTH_RELAY == 'plain' ) { + $args[] = 'user='.$_SESSION['username']; + $args[] = 'pass='.$_SESSION['password']; + } elseif ( ZM_AUTH_RELAY == 'none' ) { + $args[] = 'user='.$_SESSION['username']; } } - if ( !in_array( "mode=single", $args ) && !empty($GLOBALS['connkey']) ) { - $args[] = "connkey=".$GLOBALS['connkey']; + if ( !in_array( 'mode=single', $args ) && !empty($GLOBALS['connkey']) ) { + $args[] = 'connkey='.$GLOBALS['connkey']; } if ( ZM_RAND_STREAM ) { - $args[] = "rand=".time(); + $args[] = 'rand='.time(); } if ( count($args) ) { - $streamSrc .= "?".join( $querySep, $args ); + $streamSrc .= '?'.join( $querySep, $args ); } return( $streamSrc ); @@ -190,11 +197,11 @@ function getMimeType( $file ) { return( trim( exec( 'file -bi '.escapeshellarg( $file ).' 2>/dev/null' ) ) ); } -function outputVideoStream( $id, $src, $width, $height, $format, $title="" ) { +function outputVideoStream( $id, $src, $width, $height, $format, $title='' ) { echo getVideoStreamHTML( $id, $src, $width, $height, $format, $title ); } -function getVideoStreamHTML( $id, $src, $width, $height, $format, $title="" ) { +function getVideoStreamHTML( $id, $src, $width, $height, $format, $title='' ) { $html = ''; $width = validInt($width); $height = validInt($height); @@ -205,24 +212,24 @@ function getVideoStreamHTML( $id, $src, $width, $height, $format, $title="" ) { } else { switch( $format ) { case 'asf' : - $mimeType = "video/x-ms-asf"; + $mimeType = 'video/x-ms-asf'; break; case 'avi' : case 'wmv' : - $mimeType = "video/x-msvideo"; + $mimeType = 'video/x-msvideo'; break; case 'mov' : - $mimeType = "video/quicktime"; + $mimeType = 'video/quicktime'; break; case 'mpg' : case 'mpeg' : - $mimeType = "video/mpeg"; + $mimeType = 'video/mpeg'; break; case 'swf' : - $mimeType = "application/x-shockwave-flash"; + $mimeType = 'application/x-shockwave-flash'; break; case '3gp' : - $mimeType = "video/3gpp"; + $mimeType = 'video/3gpp'; break; default : $mimeType = "video/$format"; @@ -233,9 +240,9 @@ function getVideoStreamHTML( $id, $src, $width, $height, $format, $title="" ) { $mimeType = 'video/'.$format; if ( ZM_WEB_USE_OBJECT_TAGS ) { switch( $mimeType ) { - case "video/x-ms-asf" : - case "video/x-msvideo" : - case "video/mp4" : + case 'video/x-ms-asf' : + case 'video/x-msvideo' : + case 'video/mp4' : { if ( isWindows() ) { return ''; } } - case "video/quicktime" : + case 'video/quicktime' : { return ' '; } - case "application/x-shockwave-flash" : + case 'application/x-shockwave-flash' : { return ''; } -function outputImageStream( $id, $src, $width, $height, $title="" ) { +function outputImageStream( $id, $src, $width, $height, $title='' ) { echo getImageStream( $id, $src, $width, $height, $title ); } -function getImageStream( $id, $src, $width, $height, $title="" ) { +function getImageStream( $id, $src, $width, $height, $title='' ) { if ( canStreamIframe() ) { - return '