Merge branch 'master' into fix_event_viewing
This commit is contained in:
commit
087b7abb94
|
@ -137,11 +137,11 @@ set(ZM_WEB_USER "" CACHE STRING
|
||||||
set(ZM_WEB_GROUP "" CACHE STRING
|
set(ZM_WEB_GROUP "" CACHE STRING
|
||||||
"The group apache or the local web server runs on,
|
"The group apache or the local web server runs on,
|
||||||
Leave empty to be the same as the web user")
|
Leave empty to be the same as the web user")
|
||||||
set(ZM_DIR_EVENTS "events" CACHE PATH
|
set(ZM_DIR_EVENTS "${ZM_CONTENTDIR}/events" CACHE PATH
|
||||||
"Location where events are recorded to, default: events")
|
"Location where events are recorded to, default: ZM_CONTENTDIR/events")
|
||||||
set(ZM_DIR_IMAGES "events" CACHE PATH
|
set(ZM_DIR_IMAGES "${ZM_CONTENTDIR}/images" CACHE PATH
|
||||||
"Location where images, not directly associated with events,
|
"Location where images, not directly associated with events,
|
||||||
are recorded to, default: images")
|
are recorded to, default: ZM_CONTENTDIR/images")
|
||||||
set(ZM_DIR_SOUNDS "sounds" CACHE PATH
|
set(ZM_DIR_SOUNDS "sounds" CACHE PATH
|
||||||
"Location to look for optional sound files, default: sounds")
|
"Location to look for optional sound files, default: sounds")
|
||||||
set(ZM_PATH_ZMS "/cgi-bin/nph-zms" CACHE PATH
|
set(ZM_PATH_ZMS "/cgi-bin/nph-zms" CACHE PATH
|
||||||
|
@ -154,7 +154,7 @@ set(ZM_PATH_ARP "" CACHE PATH
|
||||||
"Full path to compatible arp binary. Leave empty for automatic detection.")
|
"Full path to compatible arp binary. Leave empty for automatic detection.")
|
||||||
set(ZM_CONFIG_DIR "/${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH
|
set(ZM_CONFIG_DIR "/${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH
|
||||||
"Location of ZoneMinder configuration, default system config directory")
|
"Location of ZoneMinder configuration, default system config directory")
|
||||||
set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/conf.d" CACHE PATH
|
set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/zm/conf.d" CACHE PATH
|
||||||
"Location of ZoneMinder configuration subfolder, default: ZM_CONFIG_DIR/conf.d")
|
"Location of ZoneMinder configuration subfolder, default: ZM_CONFIG_DIR/conf.d")
|
||||||
set(ZM_EXTRA_LIBS "" CACHE STRING
|
set(ZM_EXTRA_LIBS "" CACHE STRING
|
||||||
"A list of optional libraries, separated by semicolons, e.g. ssl;theora")
|
"A list of optional libraries, separated by semicolons, e.g. ssl;theora")
|
||||||
|
@ -801,11 +801,12 @@ endif(ZM_PERL_SEARCH_PATH)
|
||||||
|
|
||||||
# If this is an out-of-source build, copy the files we need to the binary directory
|
# 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))
|
if(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR))
|
||||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf.d" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/conf.d")
|
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf.d" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE)
|
||||||
endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR))
|
endif(NOT (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR))
|
||||||
|
|
||||||
# Generate files from the .in files
|
# Generate files from the .in files
|
||||||
configure_file(zm.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zm.conf" @ONLY)
|
configure_file(zm.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zm.conf" @ONLY)
|
||||||
|
configure_file(conf.d/01-system-paths.conf.in "${CMAKE_CURRENT_BINARY_DIR}/conf.d/01-system-paths.conf" @ONLY)
|
||||||
configure_file(zoneminder-config.cmake "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY)
|
configure_file(zoneminder-config.cmake "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY)
|
||||||
configure_file(zmconfgen.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmconfgen.pl" @ONLY)
|
configure_file(zmconfgen.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmconfgen.pl" @ONLY)
|
||||||
configure_file(zmlinkcontent.sh.in "${CMAKE_CURRENT_BINARY_DIR}/zmlinkcontent.sh" @ONLY)
|
configure_file(zmlinkcontent.sh.in "${CMAKE_CURRENT_BINARY_DIR}/zmlinkcontent.sh" @ONLY)
|
||||||
|
@ -850,7 +851,7 @@ endif(zmconfgen_result EQUAL 0)
|
||||||
|
|
||||||
# Install zm.conf and conf.d subfolder
|
# Install zm.conf and conf.d subfolder
|
||||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm.conf" DESTINATION "${ZM_CONFIG_DIR}")
|
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm.conf" DESTINATION "${ZM_CONFIG_DIR}")
|
||||||
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/conf.d/" DESTINATION "${ZM_CONFIG_SUBDIR}")
|
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/conf.d/" DESTINATION "${ZM_CONFIG_SUBDIR}" PATTERN "*.in" EXCLUDE)
|
||||||
|
|
||||||
# Uninstall target
|
# Uninstall target
|
||||||
configure_file(
|
configure_file(
|
||||||
|
|
11
INSTALL
11
INSTALL
|
@ -45,9 +45,20 @@ Possible configuration options:
|
||||||
ZM_DB_NAME Name of ZoneMinder database, default: zm
|
ZM_DB_NAME Name of ZoneMinder database, default: zm
|
||||||
ZM_DB_USER Name of ZoneMinder database user, default: zmuser
|
ZM_DB_USER Name of ZoneMinder database user, default: zmuser
|
||||||
ZM_DB_PASS Password of ZoneMinder database user, default: zmpass
|
ZM_DB_PASS Password of ZoneMinder database user, default: zmpass
|
||||||
|
ZM_DB_SSL_CA_CERT Path to SSL CA certificate, default: empty; SSL not enabled
|
||||||
|
ZM_DB_SSL_CLIENT_KEY Path to SSL client key, default: empty; SSL not enabled
|
||||||
|
ZM_DB_SSL_CLIENT_CERT Path to SSL client certificate, default: empty; SSL not enabled
|
||||||
ZM_WEB_USER The user apache or the local web server runs on. Leave empty for automatic detection. If that fails, you can use this variable to force
|
ZM_WEB_USER The user apache or the local web server runs on. Leave empty for automatic detection. If that fails, you can use this variable to force
|
||||||
ZM_WEB_GROUP The group apache or the local web server runs on, Leave empty to be the same as the web user
|
ZM_WEB_GROUP The group apache or the local web server runs on, Leave empty to be the same as the web user
|
||||||
|
ZM_DIR_EVENTS Location where events are recorded to, default: ZM_CONTENTDIR/events
|
||||||
|
ZM_DIR_IMAGES Location where images, not directly associated with events, are recorded to, default: ZM_CONTENTDIR/images
|
||||||
|
ZM_DIR_SOUNDS Location to look for optional sound files, default: sounds
|
||||||
|
ZM_PATH_ZMS Web url to zms streaming server, default: /cgi-bin/nph-zms
|
||||||
Advanced:
|
Advanced:
|
||||||
|
ZM_PATH_MAP Location to save mapped memory files, default: /dev/shm
|
||||||
|
ZM_PATH_ARP Full path to compatible arp binary. Leave empty for automatic detection.
|
||||||
|
ZM_CONFIG_DIR Location of the main ZoneMinder config file, zm.conf. default: /etc/zm
|
||||||
|
ZM_CONFIG_SUBDIR Location of custom config files. default: ZM_CONFIG_DIR/conf.d
|
||||||
ZM_EXTRA_LIBS A list of optional libraries, separated by semicolons, e.g. ssl;theora
|
ZM_EXTRA_LIBS A list of optional libraries, separated by semicolons, e.g. ssl;theora
|
||||||
ZM_MYSQL_ENGINE MySQL engine to use with database, default: InnoDB
|
ZM_MYSQL_ENGINE MySQL engine to use with database, default: InnoDB
|
||||||
ZM_NO_MMAP Set to ON to not use mmap shared memory. Shouldn't be enabled unless you experience problems with the shared memory. default: OFF
|
ZM_NO_MMAP Set to ON to not use mmap shared memory. Shouldn't be enabled unless you experience problems with the shared memory. default: OFF
|
||||||
|
|
|
@ -27,8 +27,8 @@ This is the recommended method to install ZoneMinder onto your system. ZoneMinde
|
||||||
|
|
||||||
- Ubuntu via [Iconnor's PPA](https://launchpad.net/~iconnor/+archive/ubuntu/zoneminder)
|
- Ubuntu via [Iconnor's PPA](https://launchpad.net/~iconnor/+archive/ubuntu/zoneminder)
|
||||||
- Debian from their [default repository](https://packages.debian.org/search?searchon=names&keywords=zoneminder)
|
- Debian from their [default repository](https://packages.debian.org/search?searchon=names&keywords=zoneminder)
|
||||||
- RHEL/CentOS and clones via [zmrepo](http://zmrepo.zoneminder.com/)
|
- RHEL/CentOS and clones via [RPM Fusion](http://rpmfusion.org)
|
||||||
- Fedora via [zmrepo](http://zmrepo.zoneminder.com/)
|
- Fedora via [RPM Fusion](http://rpmfusion.org)
|
||||||
- OpenSuse via [third party repository](http://www.zoneminder.com/wiki/index.php/Installing_using_ZoneMinder_RPMs_for_SuSE)
|
- OpenSuse via [third party repository](http://www.zoneminder.com/wiki/index.php/Installing_using_ZoneMinder_RPMs_for_SuSE)
|
||||||
- Mageia from their default repository
|
- Mageia from their default repository
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,9 @@ echo "Database host : $ZM_DB_HOST"
|
||||||
echo "Database name : $ZM_DB_NAME"
|
echo "Database name : $ZM_DB_NAME"
|
||||||
echo "Database user : $ZM_DB_USER"
|
echo "Database user : $ZM_DB_USER"
|
||||||
echo "Database password : Not shown"
|
echo "Database password : Not shown"
|
||||||
|
echo "Database SSL CA Cert : $ZM_DB_SSL_CA_CERT"
|
||||||
|
echo "Database SSL Client Key : $ZM_DB_SSL_CLIENT_KEY"
|
||||||
|
echo "Database SSL Client Cert : $ZM_DB_SSL_CLIENT_CERT"
|
||||||
|
|
||||||
|
|
||||||
CMPATH="CACHE PATH \"Imported by cmakecacheimport.sh\" FORCE"
|
CMPATH="CACHE PATH \"Imported by cmakecacheimport.sh\" FORCE"
|
||||||
|
@ -72,6 +75,9 @@ echo "set(ZM_DB_HOST \"$ZM_DB_HOST\" $CMSTRING)">>zm_conf.cmake
|
||||||
echo "set(ZM_DB_NAME \"$ZM_DB_NAME\" $CMSTRING)">>zm_conf.cmake
|
echo "set(ZM_DB_NAME \"$ZM_DB_NAME\" $CMSTRING)">>zm_conf.cmake
|
||||||
echo "set(ZM_DB_USER \"$ZM_DB_USER\" $CMSTRING)">>zm_conf.cmake
|
echo "set(ZM_DB_USER \"$ZM_DB_USER\" $CMSTRING)">>zm_conf.cmake
|
||||||
echo "set(ZM_DB_PASS \"$ZM_DB_PASS\" $CMSTRING)">>zm_conf.cmake
|
echo "set(ZM_DB_PASS \"$ZM_DB_PASS\" $CMSTRING)">>zm_conf.cmake
|
||||||
|
echo "set(ZM_DB_SSL_CA_CERT \"$ZM_DB_SSL_CA_CERT\" $CMSTRING)">>zm_conf.cmake
|
||||||
|
echo "set(ZM_DB_SSL_CLIENT_KEY \"$ZM_DB_SSL_CLIENT_KEY\" $CMSTRING)">>zm_conf.cmake
|
||||||
|
echo "set(ZM_DB_SSL_CLIENT_CERT \"$ZM_DB_SSL_CLIENT_CERT\" $CMSTRING)">>zm_conf.cmake
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Wrote zm_conf.cmake"
|
echo "Wrote zm_conf.cmake"
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
# ==========================================================================
|
||||||
|
#
|
||||||
|
# ZoneMinder System Paths Configuration
|
||||||
|
#
|
||||||
|
# ==========================================================================
|
||||||
|
#
|
||||||
|
# This config file contains the variables previously found under Options -> Paths
|
||||||
|
#
|
||||||
|
# *** DO NOT EDIT THIS FILE ***
|
||||||
|
#
|
||||||
|
# To make custom changes to the variables below, create a new configuration
|
||||||
|
# file, with an extention of .conf, containing your desired modifications.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Full path to the folder events are recorded to.
|
||||||
|
# The web account user must have full read/write permission to this folder.
|
||||||
|
ZM_DIR_EVENTS=@ZM_DIR_EVENTS@
|
||||||
|
|
||||||
|
# Full path to the folder images, not directly associated with events,
|
||||||
|
# are recorded to.
|
||||||
|
# The web account user must have full read/write permission to this folder.
|
||||||
|
ZM_DIR_IMAGES=@ZM_DIR_IMAGES@
|
||||||
|
|
||||||
|
# Foldername under the webroot where ZoneMinder looks for optional sound files
|
||||||
|
# to play when an alarm is detected.
|
||||||
|
ZM_DIR_SOUNDS=@ZM_DIR_SOUNDS@
|
||||||
|
|
||||||
|
# Full path to the folder where exported archives are stored
|
||||||
|
# The web account user must have full read/write permission to this folder.
|
||||||
|
ZM_DIR_EXPORTS=@ZM_TMPDIR@
|
||||||
|
|
||||||
|
# ZoneMinder url path to the zms streaming server
|
||||||
|
ZM_PATH_ZMS=@ZM_PATH_ZMS@
|
||||||
|
|
||||||
|
# Full Path to ZoneMinder's mapped memory files
|
||||||
|
# The web account user must have full read/write permission to this folder.
|
||||||
|
ZM_PATH_MAP=@ZM_PATH_MAP@
|
||||||
|
|
||||||
|
# Full Path to ZoneMinder's socket folder
|
||||||
|
# The web account user must have full read/write permission to this folder.
|
||||||
|
ZM_PATH_SOCKS=@ZM_SOCKDIR@
|
||||||
|
|
||||||
|
# Full path to ZoneMinder's log folder
|
||||||
|
# The web account user must have full read/write permission to this folder.
|
||||||
|
ZM_PATH_LOGS=@ZM_LOGDIR@
|
||||||
|
|
||||||
|
# Full path to ZoneMinder's swap folder
|
||||||
|
# The web account user must have full read/write permission to this folder.
|
||||||
|
ZM_PATH_SWAP=@ZM_TMPDIR@
|
||||||
|
|
||||||
|
# Full path to optional arp binary
|
||||||
|
# ZoneMinder will find the arp binary automatically on most systems
|
||||||
|
ZM_PATH_ARP=@ZM_PATH_ARP@
|
|
@ -0,0 +1,12 @@
|
||||||
|
# ==========================================================================
|
||||||
|
#
|
||||||
|
# ZoneMinder Multiserver Configuration
|
||||||
|
#
|
||||||
|
# ==========================================================================
|
||||||
|
# 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=
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
# 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=
|
|
||||||
|
|
|
@ -331,13 +331,13 @@ CREATE TABLE `Monitors` (
|
||||||
`Format` int(10) unsigned NOT NULL default '0',
|
`Format` int(10) unsigned NOT NULL default '0',
|
||||||
`V4LMultiBuffer` tinyint(1) unsigned,
|
`V4LMultiBuffer` tinyint(1) unsigned,
|
||||||
`V4LCapturesPerFrame` tinyint(3) unsigned,
|
`V4LCapturesPerFrame` tinyint(3) unsigned,
|
||||||
`Protocol` varchar(16) NOT NULL default '',
|
`Protocol` varchar(16) default '',
|
||||||
`Method` varchar(16) NOT NULL default '',
|
`Method` varchar(16) NOT NULL default '',
|
||||||
`Host` varchar(64),
|
`Host` varchar(64),
|
||||||
`Port` varchar(8) NOT NULL default '',
|
`Port` varchar(8) NOT NULL default '',
|
||||||
`SubPath` varchar(64) NOT NULL default '',
|
`SubPath` varchar(64) NOT NULL default '',
|
||||||
`Path` varchar(255),
|
`Path` varchar(255),
|
||||||
`Options` varchar(255) not null default '',
|
`Options` varchar(255) default '',
|
||||||
`User` varchar(64),
|
`User` varchar(64),
|
||||||
`Pass` varchar(64),
|
`Pass` varchar(64),
|
||||||
`Width` smallint(5) unsigned NOT NULL default '0',
|
`Width` smallint(5) unsigned NOT NULL default '0',
|
||||||
|
@ -602,6 +602,7 @@ INSERT INTO `Controls` VALUES (NULL,'Vivotek ePTZ','Remote','Vivotek_ePTZ',0,0,1
|
||||||
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,'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,'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);
|
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);
|
||||||
|
INSERT INTO `Controls` VALUES (NULL,'Maginon Supra IPC','cURL','MaginonIPC',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,4,0,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Add some monitor preset values
|
-- Add some monitor preset values
|
||||||
|
|
|
@ -6,3 +6,4 @@ usr/share/perl5/ZoneMinder.pm
|
||||||
usr/share/zoneminder/db
|
usr/share/zoneminder/db
|
||||||
usr/share/zoneminder/www
|
usr/share/zoneminder/www
|
||||||
etc/zm
|
etc/zm
|
||||||
|
etc/zm/conf.d/*
|
||||||
|
|
|
@ -18,7 +18,6 @@ override_dh_auto_configure:
|
||||||
-DZM_TMPDIR=/var/tmp/zm \
|
-DZM_TMPDIR=/var/tmp/zm \
|
||||||
-DZM_LOGDIR=/var/log/zm \
|
-DZM_LOGDIR=/var/log/zm \
|
||||||
-DZM_WEBDIR=/usr/share/zoneminder/www \
|
-DZM_WEBDIR=/usr/share/zoneminder/www \
|
||||||
-DZM_CONTENTDIR=/var/cache/zoneminder \
|
|
||||||
-DZM_CGIDIR=/usr/lib/zoneminder/cgi-bin \
|
-DZM_CGIDIR=/usr/lib/zoneminder/cgi-bin \
|
||||||
-DZM_WEB_USER=www-data \
|
-DZM_WEB_USER=www-data \
|
||||||
-DZM_WEB_GROUP=www-data \
|
-DZM_WEB_GROUP=www-data \
|
||||||
|
|
|
@ -9,15 +9,15 @@ else(ZM_TARGET_DISTRO MATCHES "^el")
|
||||||
message([WARNING] "Unknown Build Option Detected" ...)
|
message([WARNING] "Unknown Build Option Detected" ...)
|
||||||
endif(ZM_TARGET_DISTRO MATCHES "^el")
|
endif(ZM_TARGET_DISTRO MATCHES "^el")
|
||||||
|
|
||||||
if((ZM_TARGET_DISTRO STREQUAL "el6") AND (ZM_WEB_USER STREQUAL "nginx"))
|
if((NOT ZM_TARGET_DISTRO MATCHES "^fc") AND (ZM_WEB_USER STREQUAL "nginx"))
|
||||||
message([FATAL_ERROR] "Nginx is Not a Supported Build Option on EL6 Target Distro" ...)
|
message([FATAL_ERROR] "Experimental Nginx support is currently only supported on Fedora" ...)
|
||||||
endif((ZM_TARGET_DISTRO STREQUAL "el6") AND (ZM_WEB_USER STREQUAL "nginx"))
|
endif((NOT ZM_TARGET_DISTRO MATCHES "^fc") AND (ZM_WEB_USER STREQUAL "nginx"))
|
||||||
|
|
||||||
# Configure the zoneminder service files
|
# Configure the zoneminder service files
|
||||||
if(ZM_TARGET_DISTRO STREQUAL "el6")
|
if(ZM_TARGET_DISTRO STREQUAL "el6")
|
||||||
configure_file(sysvinit/zoneminder.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.sysvinit @ONLY)
|
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.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY)
|
||||||
configure_file(sysvinit/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
|
configure_file(apache/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
|
||||||
else(ZM_TARGET_DISTRO STREQUAL "el6")
|
else(ZM_TARGET_DISTRO STREQUAL "el6")
|
||||||
configure_file(systemd/zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY)
|
configure_file(systemd/zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY)
|
||||||
if(ZM_WEB_USER STREQUAL "nginx")
|
if(ZM_WEB_USER STREQUAL "nginx")
|
||||||
|
@ -28,7 +28,7 @@ else(ZM_TARGET_DISTRO STREQUAL "el6")
|
||||||
configure_file(nginx/README.Fedora ${CMAKE_CURRENT_SOURCE_DIR}/readme/README.Fedora COPYONLY)
|
configure_file(nginx/README.Fedora ${CMAKE_CURRENT_SOURCE_DIR}/readme/README.Fedora COPYONLY)
|
||||||
else(ZM_WEB_USER STREQUAL "nginx")
|
else(ZM_WEB_USER STREQUAL "nginx")
|
||||||
configure_file(systemd/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY)
|
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(apache/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
|
||||||
configure_file(systemd/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY)
|
configure_file(systemd/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY)
|
||||||
endif(ZM_WEB_USER STREQUAL "nginx")
|
endif(ZM_WEB_USER STREQUAL "nginx")
|
||||||
endif(ZM_TARGET_DISTRO STREQUAL "el6")
|
endif(ZM_TARGET_DISTRO STREQUAL "el6")
|
||||||
|
@ -55,10 +55,7 @@ install(DIRECTORY zoneminder DESTINATION /var/run DIRECTORY_PERMISSIONS OWNER_WR
|
||||||
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 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)
|
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
|
# Symlink the cake php temp folder to the ZoneMinder temp folder
|
||||||
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\")")
|
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
|
# Link to Cambozola
|
||||||
|
|
|
@ -16,7 +16,7 @@ Alias /zm "@ZM_WEBDIR@"
|
||||||
DirectoryIndex index.php
|
DirectoryIndex index.php
|
||||||
SSLRequireSSL
|
SSLRequireSSL
|
||||||
Options -Indexes +MultiViews +FollowSymLinks
|
Options -Indexes +MultiViews +FollowSymLinks
|
||||||
AllowOverride All
|
AllowOverride None
|
||||||
<IfModule mod_authz_core.c>
|
<IfModule mod_authz_core.c>
|
||||||
# Apache 2.4
|
# Apache 2.4
|
||||||
Require all granted
|
Require all granted
|
||||||
|
@ -31,7 +31,7 @@ Alias /zm "@ZM_WEBDIR@"
|
||||||
ScriptAlias /cgi-bin-zm "@ZM_CGIDIR@"
|
ScriptAlias /cgi-bin-zm "@ZM_CGIDIR@"
|
||||||
<Directory "@ZM_CGIDIR@">
|
<Directory "@ZM_CGIDIR@">
|
||||||
SSLRequireSSL
|
SSLRequireSSL
|
||||||
AllowOverride All
|
AllowOverride None
|
||||||
Options +ExecCGI +FollowSymLinks
|
Options +ExecCGI +FollowSymLinks
|
||||||
<IfModule mod_authz_core.c>
|
<IfModule mod_authz_core.c>
|
||||||
# Apache 2.4
|
# Apache 2.4
|
||||||
|
@ -44,3 +44,28 @@ ScriptAlias /cgi-bin-zm "@ZM_CGIDIR@"
|
||||||
</IfModule>
|
</IfModule>
|
||||||
</Directory>
|
</Directory>
|
||||||
|
|
||||||
|
# For better visibility, the following directives have been migrated from the
|
||||||
|
# default .htaccess files included with the CakePHP project.
|
||||||
|
# Parameters not set here are inherited from the parent directive above.
|
||||||
|
<Directory "@ZM_WEBDIR@/api">
|
||||||
|
RewriteEngine on
|
||||||
|
RewriteRule ^$ app/webroot/ [L]
|
||||||
|
RewriteRule (.*) app/webroot/$1 [L]
|
||||||
|
RewriteBase /zm/api
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
<Directory "@ZM_WEBDIR@/api/app">
|
||||||
|
RewriteEngine on
|
||||||
|
RewriteRule ^$ webroot/ [L]
|
||||||
|
RewriteRule (.*) webroot/$1 [L]
|
||||||
|
RewriteBase /zm/api
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
<Directory "@ZM_WEBDIR@/api/app/webroot">
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^ index.php [L]
|
||||||
|
RewriteBase /zm/api
|
||||||
|
</Directory>
|
||||||
|
|
|
@ -28,7 +28,7 @@ New installs
|
||||||
1. Unless you are already using MariaDB server, you need to ensure that the
|
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:
|
server is configured to start during boot and properly secured by running:
|
||||||
|
|
||||||
sudo dnf install mariadb-server
|
sudo yum install mariadb-server
|
||||||
sudo systemctl enable mariadb
|
sudo systemctl enable mariadb
|
||||||
sudo systemctl start mariadb.service
|
sudo systemctl start mariadb.service
|
||||||
mysql_secure_installation
|
mysql_secure_installation
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# ZoneMinder systemd unit file for CentOS 7
|
# ZoneMinder systemd unit file for RedHat distros and clones
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=ZoneMinder CCTV recording and security system
|
Description=ZoneMinder CCTV recording and security system
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
#
|
|
||||||
# ZoneMinder Apache configuration file
|
|
||||||
# With SSLRequire and HTTPS auto redirect
|
|
||||||
# Modify this configuration to suit your requirements
|
|
||||||
#
|
|
||||||
|
|
||||||
# Auto Redirect HTTP requests to HTTPS
|
|
||||||
RewriteEngine On
|
|
||||||
RewriteCond %{HTTPS} !=on
|
|
||||||
RewriteRule ^/?(zm)(.*) https://%{SERVER_NAME}/$1$2 [R,L]
|
|
||||||
|
|
||||||
Alias /zm "@ZM_WEBDIR@"
|
|
||||||
<Directory "@ZM_WEBDIR@">
|
|
||||||
SSLRequireSSL
|
|
||||||
Options -Indexes MultiViews FollowSymLinks
|
|
||||||
AllowOverride All
|
|
||||||
Order allow,deny
|
|
||||||
Allow from all
|
|
||||||
</Directory>
|
|
||||||
|
|
||||||
ScriptAlias /cgi-bin/zm "@ZM_CGIDIR@"
|
|
||||||
<Directory "@ZM_CGIDIR@">
|
|
||||||
SSLRequireSSL
|
|
||||||
AllowOverride All
|
|
||||||
Options ExecCGI FollowSymLinks
|
|
||||||
Order allow,deny
|
|
||||||
Allow from all
|
|
||||||
</Directory>
|
|
|
@ -109,6 +109,7 @@ Requires: perl(MIME::Lite)
|
||||||
Requires: perl(Net::SMTP)
|
Requires: perl(Net::SMTP)
|
||||||
Requires: perl(Net::FTP)
|
Requires: perl(Net::FTP)
|
||||||
Requires: perl(LWP::Protocol::https)
|
Requires: perl(LWP::Protocol::https)
|
||||||
|
Requires: ca-certificates
|
||||||
|
|
||||||
%{?with_init_systemd:Requires(post): systemd}
|
%{?with_init_systemd:Requires(post): systemd}
|
||||||
%{?with_init_systemd:Requires(post): systemd-sysv}
|
%{?with_init_systemd:Requires(post): systemd-sysv}
|
||||||
|
@ -162,7 +163,14 @@ too much degradation of performance.
|
||||||
%make_install
|
%make_install
|
||||||
|
|
||||||
# Remove unwanted files and folders
|
# 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 || :
|
find %{buildroot} \( -name .htaccess -or -name .editorconfig -or -name .packlist -or -name .git -or -name .gitignore -or -name .gitattributes -or -name .travis.yml \) -type f -delete > /dev/null 2>&1 || :
|
||||||
|
|
||||||
|
# Recursively change shebang in all relevant scripts and set execute permission
|
||||||
|
find %{buildroot}%{_datadir}/zoneminder/www/api \( -name cake -or -name cake.php \) -type f -exec sed -i 's\^#!/usr/bin/env bash$\#!/usr/bin/bash\' {} \; -exec %{__chmod} 755 {} \;
|
||||||
|
|
||||||
|
# Use the system cacert file rather then the one bundled with CakePHP
|
||||||
|
%{__rm} -f %{buildroot}%{_datadir}/zoneminder/www/api/lib/Cake/Config/cacert.pem
|
||||||
|
%{__ln_s} ../../../../../../../..%{_sysconfdir}/pki/tls/certs/ca-bundle.crt %{buildroot}%{_datadir}/zoneminder/www/api/lib/Cake/Config/cacert.pem
|
||||||
|
|
||||||
%post
|
%post
|
||||||
%if 0%{?with_init_sysv}
|
%if 0%{?with_init_sysv}
|
||||||
|
@ -278,12 +286,18 @@ rm -rf %{_docdir}/%{name}-%{version}
|
||||||
%files
|
%files
|
||||||
%license COPYING
|
%license COPYING
|
||||||
%doc AUTHORS README.md distros/redhat/readme/README.%{readme_suffix} distros/redhat/readme/README.https distros/redhat/jscalendar-doc
|
%doc AUTHORS README.md distros/redhat/readme/README.%{readme_suffix} distros/redhat/readme/README.https distros/redhat/jscalendar-doc
|
||||||
|
|
||||||
|
# We want these two folders to have "normal" read permission
|
||||||
|
# compared to the folder contents
|
||||||
%dir %{_sysconfdir}/zm
|
%dir %{_sysconfdir}/zm
|
||||||
%dir %{_sysconfdir}/zm/conf.d
|
%dir %{_sysconfdir}/zm/conf.d
|
||||||
|
|
||||||
|
# Config folder contents contain sensitive info
|
||||||
|
# and should not be readable by normal users
|
||||||
%{_sysconfdir}/zm/conf.d/README
|
%{_sysconfdir}/zm/conf.d/README
|
||||||
# Always overwrite zm.conf now that ZoneMinder supports conf.d folder
|
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/zm.conf
|
||||||
%attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/zm.conf
|
|
||||||
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/*.conf
|
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/*.conf
|
||||||
|
%ghost %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/zmcustom.conf
|
||||||
|
|
||||||
%config(noreplace) %attr(644,root,root) %{wwwconfdir}/zoneminder.conf
|
%config(noreplace) %attr(644,root,root) %{wwwconfdir}/zoneminder.conf
|
||||||
%config(noreplace) %{_sysconfdir}/logrotate.d/zoneminder
|
%config(noreplace) %{_sysconfdir}/logrotate.d/zoneminder
|
||||||
|
@ -297,6 +311,7 @@ rm -rf %{_docdir}/%{name}-%{version}
|
||||||
%{_unitdir}/zoneminder.service
|
%{_unitdir}/zoneminder.service
|
||||||
%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy
|
%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy
|
||||||
%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
|
%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
|
||||||
|
%{_bindir}/zmsystemctl.pl
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%if 0%{?with_init_sysv}
|
%if 0%{?with_init_sysv}
|
||||||
|
@ -318,7 +333,6 @@ rm -rf %{_docdir}/%{name}-%{version}
|
||||||
%{_bindir}/zmvideo.pl
|
%{_bindir}/zmvideo.pl
|
||||||
%{_bindir}/zmwatch.pl
|
%{_bindir}/zmwatch.pl
|
||||||
%{_bindir}/zmcamtool.pl
|
%{_bindir}/zmcamtool.pl
|
||||||
%{_bindir}/zmsystemctl.pl
|
|
||||||
%{_bindir}/zmtelemetry.pl
|
%{_bindir}/zmtelemetry.pl
|
||||||
%{_bindir}/zmx10.pl
|
%{_bindir}/zmx10.pl
|
||||||
%{_bindir}/zmonvif-probe.pl
|
%{_bindir}/zmonvif-probe.pl
|
||||||
|
|
|
@ -25,7 +25,6 @@ override_dh_auto_configure:
|
||||||
-DZM_SOCKDIR="/var/run/zm" \
|
-DZM_SOCKDIR="/var/run/zm" \
|
||||||
-DZM_TMPDIR="/tmp/zm" \
|
-DZM_TMPDIR="/tmp/zm" \
|
||||||
-DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \
|
-DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \
|
||||||
-DZM_CONTENTDIR="/var/cache/zoneminder" \
|
|
||||||
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
||||||
-DZM_DIR_IMAGES="/var/cache/zoneminder/images" \
|
-DZM_DIR_IMAGES="/var/cache/zoneminder/images" \
|
||||||
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms" \
|
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms" \
|
||||||
|
|
|
@ -4,3 +4,5 @@ var/cache/zoneminder/events
|
||||||
var/cache/zoneminder/images
|
var/cache/zoneminder/images
|
||||||
var/cache/zoneminder/temp
|
var/cache/zoneminder/temp
|
||||||
usr/share/zoneminder/db
|
usr/share/zoneminder/db
|
||||||
|
etc/zm
|
||||||
|
etc/zm/conf.d
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
etc/zm/zm.conf
|
etc/zm/zm.conf
|
||||||
|
etc/zm/conf.d/*
|
||||||
usr/bin
|
usr/bin
|
||||||
usr/lib/zoneminder
|
usr/lib/zoneminder
|
||||||
usr/share/polkit-1
|
usr/share/polkit-1
|
||||||
|
|
|
@ -1,4 +1 @@
|
||||||
/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
|
|
||||||
/tmp/zm /usr/share/zoneminder/www/api/app/tmp
|
/tmp/zm /usr/share/zoneminder/www/api/app/tmp
|
||||||
|
|
|
@ -4,51 +4,51 @@ set -e
|
||||||
|
|
||||||
if [ "$1" = "configure" ]; then
|
if [ "$1" = "configure" ]; then
|
||||||
|
|
||||||
. /etc/zm/zm.conf
|
. /etc/zm/zm.conf
|
||||||
|
|
||||||
# The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group
|
# The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group
|
||||||
chown www-data:root /var/log/zm
|
chown www-data:root /var/log/zm
|
||||||
chown www-data:www-data /var/lib/zm
|
chown www-data:www-data /var/lib/zm
|
||||||
if [ -z "$2" ]; then
|
if [ -z "$2" ]; then
|
||||||
chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/*
|
chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/*
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Do this every time the package is installed or upgraded
|
# Do this every time the package is installed or upgraded
|
||||||
|
# Ensure zoneminder is stopped
|
||||||
|
invoke-rc.d zoneminder stop || true
|
||||||
|
|
||||||
if [ "$ZM_DB_HOST" = "localhost" ]; then
|
if [ "$ZM_DB_HOST" = "localhost" ]; then
|
||||||
if [ -e "/etc/init.d/mysql" ]; then
|
if [ -e "/etc/init.d/mysql" ]; then
|
||||||
#
|
#
|
||||||
# Get mysql started if it isn't
|
# Get mysql started if it isn't
|
||||||
#
|
#
|
||||||
if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then
|
if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then
|
||||||
invoke-rc.d mysql start
|
invoke-rc.d mysql start
|
||||||
fi
|
fi
|
||||||
if $(/etc/init.d/mysql status >/dev/null 2>&1); then
|
if $(/etc/init.d/mysql status >/dev/null 2>&1); then
|
||||||
mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload
|
mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload
|
||||||
# test if database if already present...
|
# test if database if already present...
|
||||||
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
|
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
|
||||||
cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf
|
cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf
|
||||||
# This creates the user.
|
# This creates the user.
|
||||||
echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
|
echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
|
||||||
else
|
else
|
||||||
echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
|
echo "grant lock tables, alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure zoneminder is stopped
|
|
||||||
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.'
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo 'mysql not found, assuming remote server.'
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
zmupdate.pl --nointeractive
|
||||||
|
zmupdate.pl --nointeractive -f
|
||||||
|
else
|
||||||
|
echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.'
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)"
|
echo 'mysql not found, assuming remote server.'
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)"
|
||||||
|
fi
|
||||||
|
echo "Done Updating, starting ZoneMinder"
|
||||||
|
invoke-rc.d zoneminder start || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#DEBHELPER#
|
#DEBHELPER#
|
||||||
|
|
|
@ -58,7 +58,6 @@ override_dh_auto_configure:
|
||||||
-DZM_TMPDIR=/var/tmp/zm \
|
-DZM_TMPDIR=/var/tmp/zm \
|
||||||
-DZM_LOGDIR=/var/log/zm \
|
-DZM_LOGDIR=/var/log/zm \
|
||||||
-DZM_WEBDIR=/usr/share/zoneminder \
|
-DZM_WEBDIR=/usr/share/zoneminder \
|
||||||
-DZM_CONTENTDIR=/var/cache/zoneminder \
|
|
||||||
-DZM_CGIDIR=/usr/lib/cgi-bin \
|
-DZM_CGIDIR=/usr/lib/cgi-bin \
|
||||||
-DZM_WEB_USER=www-data \
|
-DZM_WEB_USER=www-data \
|
||||||
-DZM_WEB_GROUP=www-data \
|
-DZM_WEB_GROUP=www-data \
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
etc/zm
|
etc/zm
|
||||||
|
etc/zm/conf.d/*
|
||||||
usr/bin
|
usr/bin
|
||||||
usr/share/polkit-1/actions
|
usr/share/polkit-1/actions
|
||||||
usr/share/polkit-1/rules.d
|
usr/share/polkit-1/rules.d
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
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
|
|
|
@ -1 +0,0 @@
|
||||||
usr/lib/cgi-bin usr/share/zoneminder/cgi-bin
|
|
|
@ -1,16 +0,0 @@
|
||||||
Last-Update: 2015-08-16
|
|
||||||
Forwarded: no
|
|
||||||
Author: Dmitry Smirnov <onlyjob@member.fsf.org>
|
|
||||||
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
|
|
|
@ -25,7 +25,6 @@ override_dh_auto_configure:
|
||||||
-DZM_SOCKDIR="/var/run/zm" \
|
-DZM_SOCKDIR="/var/run/zm" \
|
||||||
-DZM_TMPDIR="/tmp/zm" \
|
-DZM_TMPDIR="/tmp/zm" \
|
||||||
-DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \
|
-DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \
|
||||||
-DZM_CONTENTDIR="/var/cache/zoneminder" \
|
|
||||||
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
||||||
-DZM_DIR_IMAGES="/var/cache/zoneminder/images" \
|
-DZM_DIR_IMAGES="/var/cache/zoneminder/images" \
|
||||||
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms"
|
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
etc/zm/zm.conf
|
etc/zm/zm.conf
|
||||||
|
etc/zm/conf.d/*
|
||||||
usr/bin
|
usr/bin
|
||||||
usr/lib/zoneminder
|
usr/lib/zoneminder
|
||||||
usr/share/polkit-1
|
usr/share/polkit-1
|
||||||
|
|
|
@ -1,4 +1 @@
|
||||||
/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
|
/var/tmp /usr/share/zoneminder/www/api/app/tmp
|
||||||
|
|
|
@ -21,6 +21,8 @@ if [ "$1" = "configure" ]; then
|
||||||
# Ensure zoneminder is stopped
|
# Ensure zoneminder is stopped
|
||||||
deb-systemd-invoke stop zoneminder.service || exit $?
|
deb-systemd-invoke stop zoneminder.service || exit $?
|
||||||
|
|
||||||
|
# Ensure zoneminder is stopped
|
||||||
|
deb-systemd-invoke stop zoneminder.service || exit $?
|
||||||
if [ "$ZM_DB_HOST" = "localhost" ]; then
|
if [ "$ZM_DB_HOST" = "localhost" ]; then
|
||||||
|
|
||||||
if [ -e "/etc/init.d/mysql" ]; then
|
if [ -e "/etc/init.d/mysql" ]; then
|
||||||
|
@ -46,10 +48,10 @@ if [ "$1" = "configure" ]; then
|
||||||
zmupdate.pl --nointeractive
|
zmupdate.pl --nointeractive
|
||||||
zmupdate.pl --nointeractive -f
|
zmupdate.pl --nointeractive -f
|
||||||
else
|
else
|
||||||
echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.'
|
echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.'
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo 'mysql not found, assuming remote server.'
|
echo 'mysql not found, assuming remote server.'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
|
@ -10,7 +10,7 @@ Background: RHEL, CentOS, and Clones
|
||||||
|
|
||||||
These distributions are classified as enterprise operating systems and have a long operating lifetime of many years. By design, they will not have the latest and greatest versions of any package. Instead, stable packages are the emphasis.
|
These distributions are classified as enterprise operating systems and have a long operating lifetime of many years. By design, they will not have the latest and greatest versions of any package. Instead, stable packages are the emphasis.
|
||||||
|
|
||||||
Replacing any core package in these distributions with a newer package from a third party is expressly verboten. The ZoneMinder development team will not do this, and neither should you. If you have the perception that you've got to have a newer version of mysql, gnome, apache, etc. then, rather than upgrade these packages, you should instead consider using a different distribution such as Fedora.
|
Replacing any core package in these distributions with a newer package from a third party is expressly verboten. The ZoneMinder development team will not do this, and neither should you. If you have the perception that you've got to have a newer version of php, mysql, gnome, apache, etc. then, rather than upgrade these packages, you should instead consider using a different distribution such as Fedora.
|
||||||
|
|
||||||
The ZoneMinder team will not provide support for systems which have had any core package replaced with a package from a third party.
|
The ZoneMinder team will not provide support for systems which have had any core package replaced with a package from a third party.
|
||||||
|
|
||||||
|
@ -23,39 +23,77 @@ Fedora has a short life-cycle of just 6 months. However, Fedora, and consequentl
|
||||||
|
|
||||||
If you desire newer packages than what is available in RHEL or CentOS, you should consider using Fedora.
|
If you desire newer packages than what is available in RHEL or CentOS, you should consider using Fedora.
|
||||||
|
|
||||||
Zmrepo – A ZoneMinder RPM Repository
|
How To Avoid Known Installation Problems
|
||||||
------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
Zmrepo is a turn key solution. It will install all of ZoneMinder's dependencies for you. This is the easiest and the recommended way to install ZoneMinder on any system running a Redhat based distribution.
|
The following notes are based on real problems which have occurred by those who came before you:
|
||||||
|
|
||||||
Zmrepo supports the two most recent, major releases of each Redhat based distro.
|
- Zmrepo assumes you have installed the underlying distribution **using the official installation media for that distro**. Third party "Spins" may not work correctly.
|
||||||
|
|
||||||
The following notes are based on real problems which have occurred:
|
|
||||||
|
|
||||||
- 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.
|
- 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.
|
||||||
|
|
||||||
- Be advised that you need to start with a clean system before using zmrepo.
|
- Be advised that you need to start with a clean system before installing ZoneMinder.
|
||||||
|
|
||||||
- If you have previously installed ZoneMinder from-source, then your system is **NOT** clean. You must manually search for and delete all ZoneMinder related files before using zmrepo (look under /usr/local). Make uninstall helps, but it will not do this for you correctly. You **WILL** have problems if you ignore this step.
|
- If you have previously installed ZoneMinder from-source, then your system is **NOT** clean. You must manually search for and delete all ZoneMinder related files first (look under /usr/local). Issuing a "make uninstall" helps, but it will not do this for you correctly. You **WILL** have problems if you ignore this step.
|
||||||
|
|
||||||
- It is not necessary, and not recommended, to install a LAMP stack ahead of time.
|
- Unlike Debian/Ubuntu distros, it is not necessary, and not recommended, to install a LAMP stack ahead of time.
|
||||||
|
|
||||||
- Disable other third party repos and uninstall any of ZoneMinder's third party dependencies, which might already be on the system, especially ffmpeg and vlc. Attempting to install dependencies yourself often causes problems.
|
- Disable any other third party repos and uninstall any of ZoneMinder's third party dependencies, which might already be on the system, especially ffmpeg and vlc. Attempting to install dependencies yourself often causes problems.
|
||||||
|
|
||||||
- Each ZoneMinder rpm includes a README file under /usr/share/doc. You must follow the all steps in this README file, precisely, each and every time ZoneMinder is installed or upgraded. **Failure to do so is guaranteed to result in a non-functional system.**
|
- Each ZoneMinder rpm includes a README file under /usr/share/doc. You must follow all the steps in this README file, precisely, each and every time ZoneMinder is installed or upgraded. **Failure to do so is guaranteed to result in a non-functional system.**
|
||||||
|
|
||||||
To begin the installation of ZoneMinder on your Redhat based distro, please navigate to: http://zmrepo.zoneminder.com
|
How to Install ZoneMinder
|
||||||
|
-------------------------
|
||||||
|
|
||||||
How to Build a (Custom) ZoneMinder Package
|
These instructions apply to all redhat distros and compatible clones, except for RHEL/CentOS 6.
|
||||||
|
|
||||||
|
ZoneMinder releases are now being hosted at RPM Fusion. New users should navigate the `RPM Fusion site <https://rpmfusion.org>`_ then follow the instructions to enable that repo. RHEL/CentOS users must also navaigate to the `EPEL Site <https://fedoraproject.org/wiki/EPEL>`_ and enable that repo as well. Once enabled, install ZoneMinder from the commandline:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
sudo dnf install zoneminder
|
||||||
|
|
||||||
|
Note that RHEL/CentOS 7 users should use yum instead of dnf.
|
||||||
|
|
||||||
|
Once ZoneMinder has been installed, it is critically important that you read the README file under /usr/share/doc/zoneminder. ZoneMinder will not run without completing the steps outlined in the README.
|
||||||
|
|
||||||
|
How to Install ZoneMinder on RHEL/CentOS 6
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
If you are looking to do development or the packages in zmrepo just don't suit you, then you should follow these steps to learn how to build your own ZoneMinder RPM.
|
We continue to encounter build problems, caused by the age of this distro. It is unforuntate, but we can see the writing on the wall. We do not have a date set, but the end of the line for this distros is near.
|
||||||
|
|
||||||
|
Please be advised that we do not recommend any new ZoneMinder installations using CentOS 6. However, for the time being, ZoneMinder rpms will continue to be hosted at `zmrepo <https://www.zoneminder.com>`_.
|
||||||
|
|
||||||
|
How to Install Nightly Development Builds
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
ZoneMinder development packages, which represent the most recent build from our master branch, are available from `zmrepo <https://www.zoneminder.com>`_.
|
||||||
|
|
||||||
|
The feedback we get from those who use these development packages is extremely helpful. However, please understand these packages are intended for testing the latest master branch only. They are not intended to be used on any production system. There will be new bugs, and new features may not be documented. This is bleeding edge, and there might be breakage. Please keep that in mind when using this repo. We know from our user forum that this can't be stated enough.
|
||||||
|
|
||||||
|
How to Change from Zmrepo to RPM Fusion
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
As mentioned above, the place to get the latest ZoneMinder release is now `RPM Fusion <https://rpmfusion.org>`_. If you are currently using ZoneMinder release packages from Zmrepo, then the following steps will change you over to RPM Fusion:
|
||||||
|
|
||||||
|
- Navigate to the `RPM Fusion site <https://rpmfusion.org>`_ and enable RPM Fusion on your system
|
||||||
|
- Now issue the following from the command line:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
sudo dnf remove zmrepo
|
||||||
|
sudo dnf update
|
||||||
|
|
||||||
|
Note that RHEL/CentOS 7 users should use yum instead of dnf.
|
||||||
|
|
||||||
|
How to Build a Your Own ZoneMinder Package
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
If you are looking to do development or the available packages just don't suit you, then you can follow these steps to build your own ZoneMinder RPM.
|
||||||
|
|
||||||
Background
|
Background
|
||||||
**********
|
**********
|
||||||
The following method documents how to build ZoneMinder into an RPM package, compatible with Fedora, Redhat, CentOS, and other compatible clones. This is exactly how the RPMS in zmrepo are built.
|
The following method documents how to build ZoneMinder into an RPM package, for Fedora, Redhat, CentOS, and other compatible clones. This is exactly how the RPMS in zmrepo are built.
|
||||||
|
|
||||||
The method documented below was chosen because:
|
The method documented below was chosen because:
|
||||||
|
|
||||||
|
@ -67,22 +105,20 @@ The method documented below was chosen because:
|
||||||
|
|
||||||
- Troubleshooting becomes easier if we are all building ZoneMinder the same way.
|
- Troubleshooting becomes easier if we are all building ZoneMinder the same way.
|
||||||
|
|
||||||
The build instructions below make use of a custom script called "buildzm.sh". Advanced users are encouraged to view the contents of this script. Notice that the script doesn't really do a whole lot. The goal of the script is to simply make the process a little easier for the first time user. Once you become familar with the build process, you can issue the mock commands found in the buildzm.sh script yourself if you so desire.
|
|
||||||
|
|
||||||
***IMPORTANT***
|
***IMPORTANT***
|
||||||
Certain commands in these instructions require root privileges while other commands do not. Pay close attention to this. If the instructions below state to issue a command without a “sudo” prefix, then you should *not* be root while issuing the command. Getting this incorrect will result in a failed build.
|
Certain commands in these instructions require root privileges while other commands do not. Pay close attention to this. If the instructions below state to issue a command without a “sudo” prefix, then you should *not* be root while issuing the command. Getting this incorrect will result in a failed build, or worse a broken system.
|
||||||
|
|
||||||
Set Up Your Environment
|
Set Up Your Environment
|
||||||
***********************
|
***********************
|
||||||
Before you begin, set up an rpmbuild environment by following `this guide <http://wiki.centos.org/HowTos/SetupRpmBuildEnvironment>`_ by the CentOS developers.
|
Before you begin, set up an rpmbuild environment by following `this guide <http://wiki.centos.org/HowTos/SetupRpmBuildEnvironment>`_ by the CentOS developers.
|
||||||
|
|
||||||
Next, navigate to `Zmrepo <http://zmrepo.zoneminder.com/>`_, and follow the instructions to enable zmrepo on your system.
|
In addition, make sure RPM Fusion is enabled as described in the previous section `How to Install ZoneMinder`_.
|
||||||
|
|
||||||
With zmrepo enabled, issue the following command:
|
With RPM Fusion enabled, issue the following command:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
sudo yum install zmrepo-mock-configs mock
|
sudo yum install mock-rpmfusion-free mock
|
||||||
|
|
||||||
|
|
||||||
Add your user account to the group mock:
|
Add your user account to the group mock:
|
||||||
|
@ -96,73 +132,67 @@ Your build environment is now set up.
|
||||||
|
|
||||||
Build from SRPM
|
Build from SRPM
|
||||||
***************
|
***************
|
||||||
To continue, you need a ZoneMinder SRPM. For starters, let's use one of the SRPMS from zmrepo. Go browse the `Zmrepo <http://zmrepo.zoneminder.com/>`_ site and choose an appropriate SRPM and place it into the ~/rpmbuild/SRPMS folder.
|
To continue, you need a ZoneMinder SRPM. If you wish to rebuild a ZoneMinder release, then browse the `RPM Fusion site <http://zmrepo.zoneminder.com/>`_. If instead you wish to rebuild the latest source rpm from our master branch then browse the `Zmrepo site <http://zmrepo.zoneminder.com/>`_.
|
||||||
|
|
||||||
For CentOS 7, I have chosen the following SRPM:
|
For this example, I'll use one of the source rpms from zmrepo:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
wget -P ~/rpmbuild/SRPMS http://zmrepo.zoneminder.com/el/7/SRPMS/zoneminder-1.28.1-2.el7.centos.src.rpm
|
wget -P ~/rpmbuild/SRPMS http://zmrepo.zoneminder.com/el/7/SRPMS/zoneminder-1.31.1-1.el7.centos.src.rpm
|
||||||
|
|
||||||
|
|
||||||
Now comes the fun part. To build ZoneMinder, issue the following command:
|
Now comes the fun part. To build ZoneMinder, issue the following command:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
buildzm.sh zmrepo-el7-x86_64 ~/rpmbuild/SRPMS/zoneminder-1.28.1-2.el7.centos.src.rpm
|
mock -r epel-7-x86_64-rpmfusion_free ~/rpmbuild/SRPMS/zoneminder-1.31.1-1.el7.centos.src.rpm
|
||||||
|
|
||||||
|
|
||||||
Want to build ZoneMinder for Fedora, instead of CentOS, from the same host? Once you download the Fedora SRPM, issue the following:
|
Want to build ZoneMinder for Fedora, instead of CentOS, from the same host? Once you download the Fedora SRPM, issue the following:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
buildzm.sh zmrepo-f21-x86_64 ~/rpmbuild/SRPMS/zoneminder-1.28.1-1.fc21.src.rpm
|
mock -r fedora-26-x86_64-rpmfusion_free ~/rpmbuild/SRPMS/zoneminder-1.31.1-1.el7.centos.src.rpm
|
||||||
|
|
||||||
Notice that the buildzm.sh tool requires the following parameters:
|
Notice that the mock tool requires the following parameters:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
buildzm.sh MOCKCONFIG ZONEMINDER_SRPM
|
mock -r MOCKCONFIG ZONEMINDER_SRPM
|
||||||
|
|
||||||
The list of available Mock config files are available here:
|
The list of available Mock config files are available here:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
ls /etc/mock/zmrepo*.cfg
|
ls /etc/mock/*rpmfusion_free.cfg
|
||||||
|
|
||||||
|
|
||||||
You choose the config file based on the desired distro (e.g. el6, el7, f20, f21) and basearch (e.g. x86, x86_64, arhmhfp). Notice that, when specifying the Mock config as a commandline parameter, you should leave off the ".cfg" filename extension.
|
You choose the config file based on the desired distro (e.g. el6, el7, f20, f21) and basearch (e.g. x86, x86_64, arhmhfp). Notice that, when specifying the Mock config as a commandline parameter, you should leave off the ".cfg" filename extension.
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
************
|
************
|
||||||
Once the build completes, you will be presented with a folder containing the RPM's that were built. Copy the newly built ZoneMinder RPM to the desired system, enable zmrepo per the instruction on the `Zmrepo <http://zmrepo.zoneminder.com/>`_
|
Once the build completes, you will be presented with a message stating where the newly built rpms can be found. It will look similar to this:
|
||||||
website, and then install the rpm by issuing the appropriate yum install command. Finish the installation by following the zoneminder setup instructions in the distro specific readme file, named README.{distroname}, which will be installed into the /usr/share/doc/zoneminder* folder.
|
|
||||||
|
|
||||||
Finally, you may want to consider editing the zmrepo repo file under /etc/yum.repos.d and placing an “exclude=zoneminder*” line into the config file. This will prevent your system from overwriting your manually built RPM with the ZoneMinder RPM found in the repo.
|
|
||||||
|
|
||||||
How to Modify the Source Prior to Build
|
|
||||||
***************************************
|
|
||||||
Before attempting this part of the instructions, make sure and follow the previous instructions for building one of the unmodified SRPMS from zmrepo. Knowing this part works will assist in troubleshooting should something go wrong.
|
|
||||||
|
|
||||||
These instructions may vary depending on what exactly you want to do. The following example assumes you want to build a development snapshot from the master branch.
|
|
||||||
|
|
||||||
From the previous instructions, we downloaded a CentOS 7 ZoneMinder SRPM and placed it into ~/rpmbuild/SRPMS. For this example, install it onto your system:
|
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
rpm -ivh ~/rpmbuild/SRPMS/zoneminder-1.28.1-2.el7.centos.src.rpm
|
INFO: Results and/or logs in: /var/lib/mock/fedora-26-x86_64/result
|
||||||
|
|
||||||
|
Copy the newly built ZoneMinder RPMs to the desired system, enable RPM Fusion as described in `How to Install ZoneMinder`_, and then install the rpm by issuing the appropriate yum/dnf install command. Finish the installation by following the zoneminder setup instructions in the distro specific readme file, named README.{distroname}, which will be installed into the /usr/share/doc/zoneminder* folder.
|
||||||
|
|
||||||
IMPORTANT: This operation must be done with your normal user account. Do *not* perform this command as root.
|
Finally, you may want to consider editing the rpmfusion repo file under /etc/yum.repos.d and placing an “exclude=zoneminder*” line into the config file. This will prevent your system from overwriting your manually built RPM with the ZoneMinder RPM found in the repo.
|
||||||
|
|
||||||
Make sure you have git installed:
|
How to Create Your Own Source RPM
|
||||||
|
*********************************
|
||||||
|
In the previous section we described how to rebuild an existing ZoneMinder SRPM. The instructions which follow show how to build the ZoneMinder git source tree into a source rpm, which can be used in the previous section to build an rpm.
|
||||||
|
|
||||||
|
Make sure git and rpmdevtools are installed:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
sudo yum install git
|
sudo yum install git rpmdevtools
|
||||||
|
|
||||||
|
|
||||||
Now clone the ZoneMinder git repository:
|
Now clone the ZoneMinder git repository from your home folder:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
@ -170,32 +200,62 @@ Now clone the ZoneMinder git repository:
|
||||||
git clone https://github.com/ZoneMinder/ZoneMinder
|
git clone https://github.com/ZoneMinder/ZoneMinder
|
||||||
cd ZoneMinder
|
cd ZoneMinder
|
||||||
|
|
||||||
This will create a sub-folder called ZoneMinder, which will contain the latest development.
|
This will create a sub-folder called ZoneMinder, which will contain the latest development source code.
|
||||||
|
|
||||||
We want to turn this into a tarball, but first we need to figure out what to name it. Look here:
|
If you have previsouly cloned the ZoneMinder git repo and wish to update it to the most recent, then issue these commands instead:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
ls ~/rpmbuild/SOURCES
|
cd ~\ZoneMinder
|
||||||
|
git pull origin master
|
||||||
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:
|
|
||||||
|
Get the crud submodule tarball:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
git archive --prefix=ZoneMinder-1.28.1/ -o ~/rpmbuild/SOURCES/zoneminder-1.28.1.tar.gz HEAD
|
spectool -f -g -R -s 1 ~/ZoneMinder/distros/redhat/zoneminder.spec
|
||||||
|
|
||||||
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.
|
At this point, you can make changes to the source code. Depending on what you want to do with those changes, you generally want to create a new branch first:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
cd ~\ZoneMinder
|
||||||
|
git checkout -b mynewbranch
|
||||||
|
|
||||||
|
Again, depending on what you want to do with those changes, you may want to commit your changes:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
cd ~\ZoneMinder
|
||||||
|
git add .
|
||||||
|
git commit
|
||||||
|
|
||||||
|
Once you have made your changes, it is time to turn your work into a new tarball, but first we need to look in the rpm specfile:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
less ~/ZoneMinder/distros/redhat/zoneminder.spec
|
||||||
|
|
||||||
|
Scroll down until you see the Version field. Note the value, which will be in the format x.xx.x. Now create the tarball with the following command:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
cd ~\ZoneMinder
|
||||||
|
git archive --prefix=ZoneMinder-1.31.1/ -o ~/rpmbuild/SOURCES/zoneminder-1.31.1.tar.gz HEAD
|
||||||
|
|
||||||
|
Replace "1.31.1" with the Version shown in the rpm specfile.
|
||||||
|
|
||||||
From the root of the local ZoneMinder git repo, execute the following:
|
From the root of the local ZoneMinder git repo, execute the following:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
cd ~\ZoneMinder
|
||||||
rpmbuild -bs --nodeps distros/redhat/zoneminder.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 create a source rpm and it will tell you where it was saved. For example:
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
You should now have a new SRPM under ~/rpmbuild/SRPMS. In our example, the SRPM is called zoneminder-1.28.1-2.el7.centos.src.rpm. Now follow the previous instructions that describe how to use the buildzm script, using ~/rpmbuild/SRPMS/zoneminder-1.28.1-2.el7.centos.src.rpm as the path to your SRPM.
|
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Wrote: /home/abauer/rpmbuild/SRPMS/zoneminder-1.31.1-1.fc26.src.rpm
|
||||||
|
|
||||||
|
Now follow the previous instructions `Build from SRPM`_ which describe how to build that source rpm into an rpm.
|
||||||
|
|
|
@ -72,6 +72,7 @@ Source Tab
|
||||||
FFmpeg
|
FFmpeg
|
||||||
^^^^^^
|
^^^^^^
|
||||||
This is the recommended source type for most modern ip cameras.
|
This is the recommended source type for most modern ip cameras.
|
||||||
|
|
||||||
Source Path
|
Source Path
|
||||||
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:
|
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:
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ How filters actually work
|
||||||
--------------------------
|
--------------------------
|
||||||
It is useful to know how filters actually work behind the scenes in ZoneMinder, in the event you find your filter not functioning as intended:
|
It is useful to know how filters actually work behind the scenes in ZoneMinder, in the event you find your filter not functioning as intended:
|
||||||
|
|
||||||
* the primary filter processing process in ZoneMinder is a perl file called ``zmfilter.pl``
|
* the primary filter processing process in ZoneMinder is a perl file called ``zmfilter.pl`` which retrieves filters from the Filters database table
|
||||||
* zmfilter.pl runs every FILTER_EXECUTE_INTERVAL seconds (default is 20s, can be changed in Options->System)
|
* zmfilter.pl runs every FILTER_EXECUTE_INTERVAL seconds (default is 20s, can be changed in Options->System)
|
||||||
* in each run, it goes through all the filters which are marked as "Run in Background" and if the conditions match performs the specified action
|
* in each run, it goes through all the filters which are marked as "Run in Background" and if the conditions match performs the specified action
|
||||||
* zmfilter.pl also reloads all the filters every FILTER_RELOAD_DELAY seconds (default is 300s/5mins, can be changed in Options->System)
|
* zmfilter.pl also reloads all the filters every FILTER_RELOAD_DELAY seconds (default is 300s/5mins, can be changed in Options->System)
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
@ZM_LOGDIR@/*.log {
|
@ZM_LOGDIR@/*.log {
|
||||||
missingok
|
missingok
|
||||||
notifempty
|
notifempty
|
||||||
|
delaycompress
|
||||||
|
compress
|
||||||
sharedscripts
|
sharedscripts
|
||||||
postrotate
|
postrotate
|
||||||
/bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
|
/bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
|
||||||
|
|
|
@ -250,16 +250,9 @@ sub profiles
|
||||||
|
|
||||||
my $profiles = $result->get_Profiles();
|
my $profiles = $result->get_Profiles();
|
||||||
|
|
||||||
foreach my $profile ( @{ $profiles } ) {
|
foreach my $profile ( @{ $profiles } ) {
|
||||||
|
|
||||||
my $token = $profile->attr()->get_token() ;
|
my $token = $profile->attr()->get_token() ;
|
||||||
print $token . ", " .
|
|
||||||
$profile->get_Name() . ", " .
|
|
||||||
$profile->get_VideoEncoderConfiguration()->get_Encoding() . ", " .
|
|
||||||
$profile->get_VideoEncoderConfiguration()->get_Resolution()->get_Width() . ", " .
|
|
||||||
$profile->get_VideoEncoderConfiguration()->get_Resolution()->get_Height() . ", " .
|
|
||||||
$profile->get_VideoEncoderConfiguration()->get_RateControl()->get_FrameRateLimit() .
|
|
||||||
", ";
|
|
||||||
|
|
||||||
# Specification gives conflicting values for unicast stream types, try both.
|
# Specification gives conflicting values for unicast stream types, try both.
|
||||||
# http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl#op.GetStreamUri
|
# http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl#op.GetStreamUri
|
||||||
|
@ -278,9 +271,18 @@ sub profiles
|
||||||
die $result if not $result;
|
die $result if not $result;
|
||||||
# print $result . "\n";
|
# print $result . "\n";
|
||||||
|
|
||||||
print $result->get_MediaUri()->get_Uri() .
|
my $VideoEncoderConfiguration = $profile->get_VideoEncoderConfiguration();
|
||||||
"\n";
|
print join(', ', $token,
|
||||||
}
|
$profile->get_Name(),
|
||||||
|
( $VideoEncoderConfiguration ? (
|
||||||
|
$VideoEncoderConfiguration->get_Encoding(),
|
||||||
|
$VideoEncoderConfiguration->get_Resolution()->get_Width(),
|
||||||
|
$VideoEncoderConfiguration->get_Resolution()->get_Height(),
|
||||||
|
$VideoEncoderConfiguration->get_RateControl()->get_FrameRateLimit(),
|
||||||
|
) : () ),
|
||||||
|
$result->get_MediaUri()->get_Uri() ,
|
||||||
|
). "\n";
|
||||||
|
} # end foreach profile
|
||||||
|
|
||||||
#
|
#
|
||||||
# use message parser without schema validation ???
|
# use message parser without schema validation ???
|
||||||
|
|
|
@ -33,8 +33,11 @@ FOREACH(PERLSCRIPT ${perlscripts})
|
||||||
ENDFOREACH(PERLSCRIPT ${perlscripts})
|
ENDFOREACH(PERLSCRIPT ${perlscripts})
|
||||||
|
|
||||||
# Install the perl scripts
|
# Install the perl scripts
|
||||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmsystemctl.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||||
if(NOT ZM_NO_X10)
|
if(NOT ZM_NO_X10)
|
||||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||||
endif(NOT ZM_NO_X10)
|
endif(NOT ZM_NO_X10)
|
||||||
|
|
||||||
|
if(WITH_SYSTEMD)
|
||||||
|
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmsystemctl.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||||
|
endif(WITH_SYSTEMD)
|
||||||
|
|
|
@ -101,8 +101,17 @@ BEGIN {
|
||||||
} else {
|
} else {
|
||||||
$socket = ";host=".$Config{ZM_DB_HOST};
|
$socket = ";host=".$Config{ZM_DB_HOST};
|
||||||
}
|
}
|
||||||
|
my $sslOptions = "";
|
||||||
|
if ( $Config{ZM_DB_SSL_CA_CERT} ) {
|
||||||
|
$sslOptions = ';'.join(';',
|
||||||
|
"mysql_ssl=1",
|
||||||
|
"mysql_ssl_ca_file=".$Config{ZM_DB_SSL_CA_CERT},
|
||||||
|
"mysql_ssl_client_key=".$Config{ZM_DB_SSL_CLIENT_KEY},
|
||||||
|
"mysql_ssl_client_cert=".$Config{ZM_DB_SSL_CLIENT_CERT}
|
||||||
|
);
|
||||||
|
}
|
||||||
my $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME}
|
my $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME}
|
||||||
.$socket
|
.$socket.$sslOptions
|
||||||
, $Config{ZM_DB_USER}
|
, $Config{ZM_DB_USER}
|
||||||
, $Config{ZM_DB_PASS}
|
, $Config{ZM_DB_PASS}
|
||||||
) or croak( "Can't connect to db" );
|
) or croak( "Can't connect to db" );
|
||||||
|
|
|
@ -0,0 +1,333 @@
|
||||||
|
# ==========================================================================
|
||||||
|
#
|
||||||
|
# ZoneMinder Maginon Supra IPC Camera Control Protocol Module,
|
||||||
|
# Copyright (C) 2017 Martin Gutenbrunner (martin.gutenbrunner@SPAMsonic.net)
|
||||||
|
#
|
||||||
|
# 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 the implementation of the Maginon Supra IPC camera
|
||||||
|
# procotol version.
|
||||||
|
#
|
||||||
|
package ZoneMinder::Control::MaginonIPC;
|
||||||
|
|
||||||
|
use 5.006;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
require ZoneMinder::Base;
|
||||||
|
require ZoneMinder::Control;
|
||||||
|
|
||||||
|
our @ISA = qw(ZoneMinder::Control);
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
#
|
||||||
|
# Maginon Supra IPC IP Camera Control Protocol
|
||||||
|
#
|
||||||
|
# ==========================================================================
|
||||||
|
|
||||||
|
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 );
|
||||||
|
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();
|
||||||
|
|
||||||
|
use LWP::UserAgent;
|
||||||
|
$self->{ua} = LWP::UserAgent->new;
|
||||||
|
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION );
|
||||||
|
|
||||||
|
$self->{state} = '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;
|
||||||
|
|
||||||
|
printMsg( $cmd, "Tx" );
|
||||||
|
|
||||||
|
my $url = "http://".$self->{Monitor}->{ControlAddress}."$cmd";
|
||||||
|
# Info($url);
|
||||||
|
my $req = HTTP::Request->new( GET=>$url );
|
||||||
|
my $res = $self->{ua}->request($req);
|
||||||
|
return( !undef );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub moveStop
|
||||||
|
{
|
||||||
|
Debug("moveStop");
|
||||||
|
my $self = shift;
|
||||||
|
my $params = shift;
|
||||||
|
my $cmd = "/decoder_control.cgi?command=1";
|
||||||
|
$self->sendCmd( $cmd );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub moveConUp
|
||||||
|
{
|
||||||
|
Debug("moveConUp");
|
||||||
|
my $self = shift;
|
||||||
|
my $params = shift;
|
||||||
|
my $cmd = "/decoder_control.cgi?command=0";
|
||||||
|
$self->sendCmd( $cmd );
|
||||||
|
my $autostop = $self->getParam( $params, 'autostop', 0 );
|
||||||
|
if ( $autostop && $self->{Monitor}->{AutoStopTimeout} )
|
||||||
|
{
|
||||||
|
usleep( $self->{Monitor}->{AutoStopTimeout} );
|
||||||
|
$self->moveStop( $params );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub moveConDown
|
||||||
|
{
|
||||||
|
Debug("moveConDown");
|
||||||
|
my $self = shift;
|
||||||
|
my $params = shift;
|
||||||
|
my $cmd = "/decoder_control.cgi?command=2";
|
||||||
|
$self->sendCmd( $cmd );
|
||||||
|
my $autostop = $self->getParam( $params, 'autostop', 0 );
|
||||||
|
if ( $autostop && $self->{Monitor}->{AutoStopTimeout} )
|
||||||
|
{
|
||||||
|
usleep( $self->{Monitor}->{AutoStopTimeout} );
|
||||||
|
$self->moveStop( $params );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub moveConLeft
|
||||||
|
{
|
||||||
|
Debug("moveConLeft");
|
||||||
|
my $self = shift;
|
||||||
|
my $params = shift;
|
||||||
|
my $cmd = "/decoder_control.cgi?command=4";
|
||||||
|
$self->sendCmd( $cmd );
|
||||||
|
my $autostop = $self->getParam( $params, 'autostop', 0 );
|
||||||
|
if ( $autostop && $self->{Monitor}->{AutoStopTimeout} )
|
||||||
|
{
|
||||||
|
usleep( $self->{Monitor}->{AutoStopTimeout} );
|
||||||
|
$self->moveStop( $params );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub moveConRight
|
||||||
|
{
|
||||||
|
Debug("moveConRight");
|
||||||
|
my $self = shift;
|
||||||
|
my $params = shift;
|
||||||
|
my $cmd = "/decoder_control.cgi?command=6";
|
||||||
|
$self->sendCmd( $cmd );
|
||||||
|
my $autostop = $self->getParam( $params, 'autostop', 0 );
|
||||||
|
if ( $autostop && $self->{Monitor}->{AutoStopTimeout} )
|
||||||
|
{
|
||||||
|
usleep( $self->{Monitor}->{AutoStopTimeout} );
|
||||||
|
$self->moveStop( $params );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub moveConUpRight
|
||||||
|
{
|
||||||
|
Debug("moveConUpRight");
|
||||||
|
my $self = shift;
|
||||||
|
my $params = shift;
|
||||||
|
my $cmd = "/decoder_control.cgi?command=91";
|
||||||
|
$self->sendCmd( $cmd );
|
||||||
|
my $autostop = $self->getParam( $params, 'autostop', 0 );
|
||||||
|
if ( $autostop && $self->{Monitor}->{AutoStopTimeout} )
|
||||||
|
{
|
||||||
|
usleep( $self->{Monitor}->{AutoStopTimeout} );
|
||||||
|
$self->moveStop( $params );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub moveConUpLeft
|
||||||
|
{
|
||||||
|
Debug("moveConUpLeft");
|
||||||
|
my $self = shift;
|
||||||
|
my $params = shift;
|
||||||
|
my $cmd = "/decoder_control.cgi?command=90";
|
||||||
|
$self->sendCmd( $cmd );
|
||||||
|
my $autostop = $self->getParam( $params, 'autostop', 0 );
|
||||||
|
if ( $autostop && $self->{Monitor}->{AutoStopTimeout} )
|
||||||
|
{
|
||||||
|
usleep( $self->{Monitor}->{AutoStopTimeout} );
|
||||||
|
$self->moveStop( $params );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub moveConDownRight
|
||||||
|
{
|
||||||
|
Debug("moveConDownRight");
|
||||||
|
my $self = shift;
|
||||||
|
my $params = shift;
|
||||||
|
my $cmd = "/decoder_control.cgi?command=93";
|
||||||
|
$self->sendCmd( $cmd );
|
||||||
|
my $autostop = $self->getParam( $params, 'autostop', 0 );
|
||||||
|
if ( $autostop && $self->{Monitor}->{AutoStopTimeout} )
|
||||||
|
{
|
||||||
|
usleep( $self->{Monitor}->{AutoStopTimeout} );
|
||||||
|
$self->moveStop( $params );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub moveConDownLeft
|
||||||
|
{
|
||||||
|
Debug("moveConDownLeft");
|
||||||
|
my $self = shift;
|
||||||
|
my $params = shift;
|
||||||
|
my $cmd = "/decoder_control.cgi?command=92";
|
||||||
|
$self->sendCmd( $cmd );
|
||||||
|
my $autostop = $self->getParam( $params, 'autostop', 0 );
|
||||||
|
if ( $autostop && $self->{Monitor}->{AutoStopTimeout} )
|
||||||
|
{
|
||||||
|
usleep( $self->{Monitor}->{AutoStopTimeout} );
|
||||||
|
$self->moveStop( $params );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub presetSet
|
||||||
|
{
|
||||||
|
my $self = shift;
|
||||||
|
my $params = shift;
|
||||||
|
my $preset = $self->getParam( $params, 'preset' );
|
||||||
|
Info( "Set Preset $preset" );
|
||||||
|
|
||||||
|
my $cmdNum;
|
||||||
|
if ($preset == 1) {
|
||||||
|
$cmdNum = 30;
|
||||||
|
} elsif ($preset == 2) {
|
||||||
|
$cmdNum = 32;
|
||||||
|
} elsif ($preset == 3) {
|
||||||
|
$cmdNum = 34;
|
||||||
|
} elsif ($preset == 4) {
|
||||||
|
$cmdNum = 36;
|
||||||
|
} else {
|
||||||
|
$cmdNum = 36;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $cmd = "/decoder_control.cgi?command=$cmdNum";
|
||||||
|
$self->sendCmd( $cmd );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub presetGoto
|
||||||
|
{
|
||||||
|
my $self = shift;
|
||||||
|
my $params = shift;
|
||||||
|
my $preset = $self->getParam( $params, 'preset' );
|
||||||
|
Info( "Goto Preset $preset" );
|
||||||
|
|
||||||
|
my $cmdNum;
|
||||||
|
if ($preset == 1) {
|
||||||
|
$cmdNum = 31;
|
||||||
|
} elsif ($preset == 2) {
|
||||||
|
$cmdNum = 33;
|
||||||
|
} elsif ($preset == 3) {
|
||||||
|
$cmdNum = 35;
|
||||||
|
} elsif ($preset == 4) {
|
||||||
|
$cmdNum = 37;
|
||||||
|
} else {
|
||||||
|
$cmdNum = 37;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $cmd = "/decoder_control.cgi?command=$cmdNum";
|
||||||
|
$self->sendCmd( $cmd );
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
__END__
|
||||||
|
# Below is stub documentation for your module. You'd better edit it!
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
ZoneMinder::Control::MaginonIPC - Zoneminder PTZ control module for the Maginon Supra-IPC 40 IP Camera
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
use ZoneMinder::Control::MaginonIPC;
|
||||||
|
blah blah blah
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
This is for Zoneminder PTZ control module for the Maginon Supra-IPC 40 camera. It probably also works with other models.
|
||||||
|
|
||||||
|
=head2 EXPORT
|
||||||
|
|
||||||
|
None by default.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
=head1 SEE ALSO
|
||||||
|
|
||||||
|
www.zoneminder.com
|
||||||
|
|
||||||
|
=head1 AUTHOR
|
||||||
|
|
||||||
|
Martin Gutenbrunner, E<lt>martin.gutenbrunner@gmx.atE<gt>
|
||||||
|
|
||||||
|
=head1 COPYRIGHT AND LICENSE
|
||||||
|
|
||||||
|
Copyright (C) 2017 by Martin Gutenbrunner
|
||||||
|
|
||||||
|
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
|
|
@ -90,8 +90,19 @@ sub zmDbConnect {
|
||||||
} else {
|
} else {
|
||||||
$socket = ";host=".$Config{ZM_DB_HOST};
|
$socket = ";host=".$Config{ZM_DB_HOST};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my $sslOptions = "";
|
||||||
|
if ( $Config{ZM_DB_SSL_CA_CERT} ) {
|
||||||
|
$sslOptions = ';'.join(';',
|
||||||
|
"mysql_ssl=1",
|
||||||
|
"mysql_ssl_ca_file=".$Config{ZM_DB_SSL_CA_CERT},
|
||||||
|
"mysql_ssl_client_key=".$Config{ZM_DB_SSL_CLIENT_KEY},
|
||||||
|
"mysql_ssl_client_cert=".$Config{ZM_DB_SSL_CLIENT_CERT}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME}
|
$dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME}
|
||||||
.$socket . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '' )
|
.$socket . $sslOptions . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '' )
|
||||||
, $Config{ZM_DB_USER}
|
, $Config{ZM_DB_USER}
|
||||||
, $Config{ZM_DB_PASS}
|
, $Config{ZM_DB_PASS}
|
||||||
);
|
);
|
||||||
|
|
|
@ -41,7 +41,7 @@ our @ISA = qw(Exporter ZoneMinder::Base);
|
||||||
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
|
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
|
||||||
# will save memory.
|
# will save memory.
|
||||||
our %EXPORT_TAGS = (
|
our %EXPORT_TAGS = (
|
||||||
'constants' => [ qw(
|
constants => [ qw(
|
||||||
DEBUG
|
DEBUG
|
||||||
INFO
|
INFO
|
||||||
WARNING
|
WARNING
|
||||||
|
@ -50,7 +50,7 @@ our %EXPORT_TAGS = (
|
||||||
PANIC
|
PANIC
|
||||||
NOLOG
|
NOLOG
|
||||||
) ],
|
) ],
|
||||||
'functions' => [ qw(
|
functions => [ qw(
|
||||||
logInit
|
logInit
|
||||||
logReinit
|
logReinit
|
||||||
logTerm
|
logTerm
|
||||||
|
@ -72,13 +72,14 @@ our %EXPORT_TAGS = (
|
||||||
Panic
|
Panic
|
||||||
) ]
|
) ]
|
||||||
);
|
);
|
||||||
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
|
|
||||||
|
|
||||||
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
|
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
|
||||||
|
|
||||||
our @EXPORT = qw();
|
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
|
||||||
|
|
||||||
our $VERSION = $ZoneMinder::Base::VERSION;
|
our @EXPORT = qw();
|
||||||
|
|
||||||
|
our $VERSION = $ZoneMinder::Base::VERSION;
|
||||||
|
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
#
|
#
|
||||||
|
@ -86,43 +87,43 @@ our %EXPORT_TAGS = (
|
||||||
#
|
#
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
|
|
||||||
use ZoneMinder::Config qw(:all);
|
use ZoneMinder::Config qw(:all);
|
||||||
|
|
||||||
use DBI;
|
use DBI;
|
||||||
use Carp;
|
use Carp;
|
||||||
use POSIX;
|
use POSIX;
|
||||||
use IO::Handle;
|
use IO::Handle;
|
||||||
use Data::Dumper;
|
use Data::Dumper;
|
||||||
use Time::HiRes qw/gettimeofday/;
|
use Time::HiRes qw/gettimeofday/;
|
||||||
use Sys::Syslog;
|
use Sys::Syslog;
|
||||||
|
|
||||||
use constant {
|
use constant {
|
||||||
DEBUG => 1,
|
DEBUG => 1,
|
||||||
INFO => 0,
|
INFO => 0,
|
||||||
WARNING => -1,
|
WARNING => -1,
|
||||||
ERROR => -2,
|
ERROR => -2,
|
||||||
FATAL => -3,
|
FATAL => -3,
|
||||||
PANIC => -4,
|
PANIC => -4,
|
||||||
NOLOG => -5
|
NOLOG => -5
|
||||||
};
|
};
|
||||||
|
|
||||||
our %codes = (
|
our %codes = (
|
||||||
&DEBUG => "DBG",
|
&DEBUG => 'DBG',
|
||||||
&INFO => "INF",
|
&INFO => 'INF',
|
||||||
&WARNING => "WAR",
|
&WARNING => 'WAR',
|
||||||
&ERROR => "ERR",
|
&ERROR => 'ERR',
|
||||||
&FATAL => "FAT",
|
&FATAL => 'FAT',
|
||||||
&PANIC => "PNC",
|
&PANIC => 'PNC',
|
||||||
&NOLOG => "OFF"
|
&NOLOG => 'OFF'
|
||||||
);
|
);
|
||||||
|
|
||||||
our %priorities = (
|
our %priorities = (
|
||||||
&DEBUG => "debug",
|
&DEBUG => 'debug',
|
||||||
&INFO => "info",
|
&INFO => 'info',
|
||||||
&WARNING => "warning",
|
&WARNING => 'warning',
|
||||||
&ERROR => "err",
|
&ERROR => 'err',
|
||||||
&FATAL => "err",
|
&FATAL => 'err',
|
||||||
&PANIC => "err"
|
&PANIC => 'err'
|
||||||
);
|
);
|
||||||
|
|
||||||
our $logger;
|
our $logger;
|
||||||
|
@ -134,10 +135,10 @@ sub new {
|
||||||
|
|
||||||
$this->{initialised} = undef;
|
$this->{initialised} = undef;
|
||||||
|
|
||||||
#$this->{id} = "zmundef";
|
#$this->{id} = 'zmundef';
|
||||||
( $this->{id} ) = $0 =~ m|^(?:.*/)?([^/]+?)(?:\.[^/.]+)?$|;
|
( $this->{id} ) = $0 =~ m|^(?:.*/)?([^/]+?)(?:\.[^/.]+)?$|;
|
||||||
$this->{idRoot} = $this->{id};
|
$this->{idRoot} = $this->{id};
|
||||||
$this->{idArgs} = "";
|
$this->{idArgs} = '';
|
||||||
|
|
||||||
$this->{level} = INFO;
|
$this->{level} = INFO;
|
||||||
$this->{termLevel} = NOLOG;
|
$this->{termLevel} = NOLOG;
|
||||||
|
@ -151,7 +152,7 @@ sub new {
|
||||||
|
|
||||||
( $this->{fileName} = $0 ) =~ s|^.*/||;
|
( $this->{fileName} = $0 ) =~ s|^.*/||;
|
||||||
$this->{logPath} = $Config{ZM_PATH_LOGS};
|
$this->{logPath} = $Config{ZM_PATH_LOGS};
|
||||||
$this->{logFile} = $this->{logPath}."/".$this->{id}.".log";
|
$this->{logFile} = $this->{logPath}.'/'.$this->{id}.".log";
|
||||||
|
|
||||||
$this->{trace} = 0;
|
$this->{trace} = 0;
|
||||||
|
|
||||||
|
@ -196,7 +197,7 @@ sub initialise( @ ) {
|
||||||
$this->{logPath} = $options{logPath} if ( defined($options{logPath}) );
|
$this->{logPath} = $options{logPath} if ( defined($options{logPath}) );
|
||||||
|
|
||||||
my $tempLogFile;
|
my $tempLogFile;
|
||||||
$tempLogFile = $this->{logPath}."/".$this->{id}.".log";
|
$tempLogFile = $this->{logPath}.'/'.$this->{id}.".log";
|
||||||
$tempLogFile = $options{logFile} if ( defined($options{logFile}) );
|
$tempLogFile = $options{logFile} if ( defined($options{logFile}) );
|
||||||
if ( my $logFile = $this->getTargettedEnv('LOG_FILE') ) {
|
if ( my $logFile = $this->getTargettedEnv('LOG_FILE') ) {
|
||||||
$tempLogFile = $logFile;
|
$tempLogFile = $logFile;
|
||||||
|
@ -231,7 +232,6 @@ sub initialise( @ ) {
|
||||||
|
|
||||||
my $level;
|
my $level;
|
||||||
$tempLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL')) );
|
$tempLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL')) );
|
||||||
|
|
||||||
$tempTermLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_TERM')) );
|
$tempTermLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_TERM')) );
|
||||||
$tempDatabaseLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_DATABASE')) );
|
$tempDatabaseLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_DATABASE')) );
|
||||||
$tempFileLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_FILE')) );
|
$tempFileLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_FILE')) );
|
||||||
|
@ -240,9 +240,9 @@ sub initialise( @ ) {
|
||||||
if ( $Config{ZM_LOG_DEBUG} ) {
|
if ( $Config{ZM_LOG_DEBUG} ) {
|
||||||
foreach my $target ( split( /\|/, $Config{ZM_LOG_DEBUG_TARGET} ) ) {
|
foreach my $target ( split( /\|/, $Config{ZM_LOG_DEBUG_TARGET} ) ) {
|
||||||
if ( $target eq $this->{id}
|
if ( $target eq $this->{id}
|
||||||
|| $target eq "_".$this->{id}
|
|| $target eq '_'.$this->{id}
|
||||||
|| $target eq $this->{idRoot}
|
|| $target eq $this->{idRoot}
|
||||||
|| $target eq "_".$this->{idRoot}
|
|| $target eq '_'.$this->{idRoot}
|
||||||
|| $target eq ""
|
|| $target eq ""
|
||||||
) {
|
) {
|
||||||
if ( $Config{ZM_LOG_DEBUG_LEVEL} > NOLOG ) {
|
if ( $Config{ZM_LOG_DEBUG_LEVEL} > NOLOG ) {
|
||||||
|
@ -271,13 +271,13 @@ sub initialise( @ ) {
|
||||||
|
|
||||||
$this->{initialised} = !undef;
|
$this->{initialised} = !undef;
|
||||||
|
|
||||||
Debug( "LogOpts: level=".$codes{$this->{level}}
|
Debug( 'LogOpts: level='.$codes{$this->{level}}
|
||||||
."/".$codes{$this->{effectiveLevel}}
|
.'/'.$codes{$this->{effectiveLevel}}
|
||||||
.", screen=".$codes{$this->{termLevel}}
|
.', screen='.$codes{$this->{termLevel}}
|
||||||
.", database=".$codes{$this->{databaseLevel}}
|
.', database='.$codes{$this->{databaseLevel}}
|
||||||
.", logfile=".$codes{$this->{fileLevel}}
|
.', logfile='.$codes{$this->{fileLevel}}
|
||||||
."->".$this->{logFile}
|
.'->'.$this->{logFile}
|
||||||
.", syslog=".$codes{$this->{syslogLevel}}
|
.', syslog='.$codes{$this->{syslogLevel}}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,24 +293,28 @@ sub terminate {
|
||||||
sub reinitialise {
|
sub reinitialise {
|
||||||
my $this = shift;
|
my $this = shift;
|
||||||
|
|
||||||
|
# So if the logger is initialized, we just return. Since the logger is NORMALLY initialized... the rest of this function never executes.
|
||||||
return unless ( $this->{initialised} );
|
return unless ( $this->{initialised} );
|
||||||
|
|
||||||
# Bit of a nasty hack to reopen connections to log files and the DB
|
# Bit of a nasty hack to reopen connections to log files and the DB
|
||||||
my $syslogLevel = $this->syslogLevel();
|
my $syslogLevel = $this->syslogLevel();
|
||||||
$this->syslogLevel( NOLOG );
|
$this->syslogLevel( NOLOG );
|
||||||
|
$this->syslogLevel( $syslogLevel ) if ( $syslogLevel > NOLOG );
|
||||||
|
|
||||||
my $logfileLevel = $this->fileLevel();
|
my $logfileLevel = $this->fileLevel();
|
||||||
$this->fileLevel( NOLOG );
|
$this->fileLevel( NOLOG );
|
||||||
|
$this->fileLevel( $logfileLevel ) if ( $logfileLevel > NOLOG );
|
||||||
|
|
||||||
my $databaseLevel = $this->databaseLevel();
|
my $databaseLevel = $this->databaseLevel();
|
||||||
$this->databaseLevel( NOLOG );
|
$this->databaseLevel( NOLOG );
|
||||||
|
$this->databaseLevel( $databaseLevel ) if ( $databaseLevel > NOLOG );
|
||||||
|
|
||||||
my $screenLevel = $this->termLevel();
|
my $screenLevel = $this->termLevel();
|
||||||
$this->termLevel( NOLOG );
|
$this->termLevel( NOLOG );
|
||||||
|
$this->termLevel( $screenLevel ) if ( $screenLevel > 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 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Prevents undefined logging levels
|
||||||
sub limit {
|
sub limit {
|
||||||
my $this = shift;
|
my $this = shift;
|
||||||
my $level = shift;
|
my $level = shift;
|
||||||
|
@ -322,11 +326,11 @@ sub limit {
|
||||||
sub getTargettedEnv {
|
sub getTargettedEnv {
|
||||||
my $this = shift;
|
my $this = shift;
|
||||||
my $name = shift;
|
my $name = shift;
|
||||||
my $envName = $name."_".$this->{id};
|
my $envName = $name.'_'.$this->{id};
|
||||||
my $value;
|
my $value;
|
||||||
$value = $ENV{$envName} if ( defined($ENV{$envName}) );
|
$value = $ENV{$envName} if ( defined($ENV{$envName}) );
|
||||||
if ( !defined($value) && $this->{id} ne $this->{idRoot} ) {
|
if ( !defined($value) && $this->{id} ne $this->{idRoot} ) {
|
||||||
$envName = $name."_".$this->{idRoot};
|
$envName = $name.'_'.$this->{idRoot};
|
||||||
$value = $ENV{$envName} if ( defined($ENV{$envName}) );
|
$value = $ENV{$envName} if ( defined($ENV{$envName}) );
|
||||||
}
|
}
|
||||||
if ( !defined($value) ) {
|
if ( !defined($value) ) {
|
||||||
|
@ -371,12 +375,16 @@ sub level {
|
||||||
my $level = shift;
|
my $level = shift;
|
||||||
if ( defined($level) ) {
|
if ( defined($level) ) {
|
||||||
$this->{level} = $this->limit( $level );
|
$this->{level} = $this->limit( $level );
|
||||||
|
|
||||||
|
# effectiveLevel is the highest logging level used by any of the outputs.
|
||||||
$this->{effectiveLevel} = NOLOG;
|
$this->{effectiveLevel} = NOLOG;
|
||||||
$this->{effectiveLevel} = $this->{termLevel} if ( $this->{termLevel} > $this->{effectiveLevel} );
|
$this->{effectiveLevel} = $this->{termLevel} if ( $this->{termLevel} > $this->{effectiveLevel} );
|
||||||
$this->{effectiveLevel} = $this->{databaseLevel} if ( $this->{databaseLevel} > $this->{effectiveLevel} );
|
$this->{effectiveLevel} = $this->{databaseLevel} if ( $this->{databaseLevel} > $this->{effectiveLevel} );
|
||||||
$this->{effectiveLevel} = $this->{fileLevel} if ( $this->{fileLevel} > $this->{effectiveLevel} );
|
$this->{effectiveLevel} = $this->{fileLevel} if ( $this->{fileLevel} > $this->{effectiveLevel} );
|
||||||
$this->{effectiveLevel} = $this->{syslogLevel} if ( $this->{syslogLevel} > $this->{level} );
|
$this->{effectiveLevel} = $this->{syslogLevel} if ( $this->{syslogLevel} > $this->{effectiveLevel} );
|
||||||
$this->{effectiveLevel} = $this->{level} if ( $this->{effectiveLevel} > $this->{level} );
|
|
||||||
|
# ICON: I am remarking this out because I don't see the point of having an effective level, if we are just going to set it to level.
|
||||||
|
#$this->{effectiveLevel} = $this->{level} if ( $this->{level} > $this->{effectiveLevel} );
|
||||||
}
|
}
|
||||||
return( $this->{level} );
|
return( $this->{level} );
|
||||||
}
|
}
|
||||||
|
@ -396,6 +404,7 @@ sub termLevel {
|
||||||
my $this = shift;
|
my $this = shift;
|
||||||
my $termLevel = shift;
|
my $termLevel = shift;
|
||||||
if ( defined($termLevel) ) {
|
if ( defined($termLevel) ) {
|
||||||
|
# What is the point of this next lint if we are just going to overwrite it with the next line? I propose we move it down one line or remove it altogether
|
||||||
$termLevel = NOLOG if ( !$this->{hasTerm} );
|
$termLevel = NOLOG if ( !$this->{hasTerm} );
|
||||||
$termLevel = $this->limit( $termLevel );
|
$termLevel = $this->limit( $termLevel );
|
||||||
if ( $this->{termLevel} != $termLevel ) {
|
if ( $this->{termLevel} != $termLevel ) {
|
||||||
|
@ -425,8 +434,17 @@ sub databaseLevel {
|
||||||
} else {
|
} else {
|
||||||
$socket = ";host=".$Config{ZM_DB_HOST};
|
$socket = ";host=".$Config{ZM_DB_HOST};
|
||||||
}
|
}
|
||||||
|
my $sslOptions = "";
|
||||||
|
if ( $Config{ZM_DB_SSL_CA_CERT} ) {
|
||||||
|
$sslOptions = ';'.join(';',
|
||||||
|
"mysql_ssl=1",
|
||||||
|
"mysql_ssl_ca_file=".$Config{ZM_DB_SSL_CA_CERT},
|
||||||
|
"mysql_ssl_client_key=".$Config{ZM_DB_SSL_CLIENT_KEY},
|
||||||
|
"mysql_ssl_client_cert=".$Config{ZM_DB_SSL_CLIENT_CERT}
|
||||||
|
);
|
||||||
|
}
|
||||||
$this->{dbh} = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME}
|
$this->{dbh} = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME}
|
||||||
.$socket
|
.$socket.$sslOptions
|
||||||
, $Config{ZM_DB_USER}
|
, $Config{ZM_DB_USER}
|
||||||
, $Config{ZM_DB_PASS}
|
, $Config{ZM_DB_PASS}
|
||||||
);
|
);
|
||||||
|
@ -490,7 +508,7 @@ sub syslogLevel {
|
||||||
|
|
||||||
sub openSyslog {
|
sub openSyslog {
|
||||||
my $this = shift;
|
my $this = shift;
|
||||||
openlog( $this->{id}, "pid", "local1" );
|
openlog( $this->{id}, 'pid', "local1" );
|
||||||
}
|
}
|
||||||
|
|
||||||
sub closeSyslog {
|
sub closeSyslog {
|
||||||
|
@ -523,6 +541,7 @@ sub openFile {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->fileLevel( NOLOG );
|
$this->fileLevel( NOLOG );
|
||||||
|
$this->termLevel( INFO );
|
||||||
Error( "Can't open log file '".$this->{logFile}."': $!" );
|
Error( "Can't open log file '".$this->{logFile}."': $!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -544,10 +563,8 @@ sub logPrint {
|
||||||
|
|
||||||
my ($seconds, $microseconds) = gettimeofday();
|
my ($seconds, $microseconds) = gettimeofday();
|
||||||
my $message = sprintf(
|
my $message = sprintf(
|
||||||
"%s.%06d %s[%d].%s [%s]"
|
'%s.%06d %s[%d].%s [%s]'
|
||||||
, strftime( "%x %H:%M:%S"
|
, strftime( '%x %H:%M:%S' ,localtime( $seconds ) )
|
||||||
,localtime( $seconds )
|
|
||||||
)
|
|
||||||
, $microseconds
|
, $microseconds
|
||||||
, $this->{id}
|
, $this->{id}
|
||||||
, $$
|
, $$
|
||||||
|
@ -564,7 +581,7 @@ sub logPrint {
|
||||||
}
|
}
|
||||||
print( $LOGFILE $message ) if ( $level <= $this->{fileLevel} );
|
print( $LOGFILE $message ) if ( $level <= $this->{fileLevel} );
|
||||||
if ( $level <= $this->{databaseLevel} ) {
|
if ( $level <= $this->{databaseLevel} ) {
|
||||||
my $sql = "insert into Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, NULL )";
|
my $sql = 'insert into Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, NULL )';
|
||||||
$this->{sth} = $this->{dbh}->prepare_cached( $sql );
|
$this->{sth} = $this->{dbh}->prepare_cached( $sql );
|
||||||
if ( !$this->{sth} ) {
|
if ( !$this->{sth} ) {
|
||||||
$this->{databaseLevel} = NOLOG;
|
$this->{databaseLevel} = NOLOG;
|
||||||
|
@ -589,7 +606,7 @@ sub logPrint {
|
||||||
|
|
||||||
sub logInit( ;@ ) {
|
sub logInit( ;@ ) {
|
||||||
my %options = @_ ? @_ : ();
|
my %options = @_ ? @_ : ();
|
||||||
$logger = ZoneMinder::Logger->new() if ( !$logger );
|
$logger = ZoneMinder::Logger->new() if !$logger;
|
||||||
$logger->initialise( %options );
|
$logger->initialise( %options );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -646,14 +663,14 @@ sub logSyslogLevel {
|
||||||
sub Mark {
|
sub Mark {
|
||||||
my $level = shift;
|
my $level = shift;
|
||||||
$level = DEBUG unless( defined($level) );
|
$level = DEBUG unless( defined($level) );
|
||||||
my $tag = "Mark";
|
my $tag = 'Mark';
|
||||||
fetch()->logPrint( $level, $tag );
|
fetch()->logPrint( $level, $tag );
|
||||||
}
|
}
|
||||||
|
|
||||||
sub Dump {
|
sub Dump {
|
||||||
my $var = shift;
|
my $var = shift;
|
||||||
my $label = shift;
|
my $label = shift;
|
||||||
$label = "VAR" unless( defined($label) );
|
$label = 'VAR' unless( defined($label) );
|
||||||
fetch()->logPrint( DEBUG, Data::Dumper->Dump( [ $var ], [ $label ] ) );
|
fetch()->logPrint( DEBUG, Data::Dumper->Dump( [ $var ], [ $label ] ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -695,10 +712,10 @@ ZoneMinder::Logger - ZoneMinder Logger module
|
||||||
use ZoneMinder::Logger;
|
use ZoneMinder::Logger;
|
||||||
use ZoneMinder::Logger qw(:all);
|
use ZoneMinder::Logger qw(:all);
|
||||||
|
|
||||||
logInit( "myproc", DEBUG );
|
logInit( 'myproc', DEBUG );
|
||||||
|
|
||||||
Debug( "This is what is happening" );
|
Debug( 'This is what is happening' );
|
||||||
Info( "Something interesting is happening" );
|
Info( 'Something interesting is happening' );
|
||||||
Warning( "Something might be going wrong." );
|
Warning( "Something might be going wrong." );
|
||||||
Error( "Something has gone wrong!!" );
|
Error( "Something has gone wrong!!" );
|
||||||
Fatal( "Something has gone badly wrong, gotta stop!!" );
|
Fatal( "Something has gone badly wrong, gotta stop!!" );
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program; if not, write to the Free Software
|
# 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.
|
||||||
#
|
#
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
#
|
#
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program; if not, write to the Free Software
|
# 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.
|
||||||
#
|
#
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
#
|
#
|
||||||
|
|
|
@ -21,6 +21,304 @@
|
||||||
#
|
#
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use bytes;
|
||||||
|
|
||||||
|
@EXTRA_PERL_LIB@
|
||||||
|
use ZoneMinder;
|
||||||
|
use DBI;
|
||||||
|
use Getopt::Long;
|
||||||
|
use autouse 'Pod::Usage'=>qw(pod2usage);
|
||||||
|
use LWP::UserAgent;
|
||||||
|
use Sys::MemInfo qw(totalmem);
|
||||||
|
use Sys::CPU qw(cpu_count);
|
||||||
|
use POSIX qw(strftime uname);
|
||||||
|
|
||||||
|
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
|
||||||
|
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
||||||
|
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
||||||
|
|
||||||
|
use constant CHECK_INTERVAL => (14*24*60*60); # Interval between version checks
|
||||||
|
|
||||||
|
# Setting these as contants for now.
|
||||||
|
# Alternatively, we can put these in the dB and then retrieve using the Config hash.
|
||||||
|
use constant ZM_TELEMETRY_SERVER_ENDPOINT => 'https://zmanon:2b2d0b4skps@telemetry.zoneminder.com/zmtelemetry/testing5';
|
||||||
|
|
||||||
|
if ( $Config{ZM_TELEMETRY_DATA} ) {
|
||||||
|
print( 'Update agent starting at '.strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" );
|
||||||
|
|
||||||
|
my $lastCheck = $Config{ZM_TELEMETRY_LAST_UPLOAD};
|
||||||
|
|
||||||
|
while( 1 ) {
|
||||||
|
my $now = time();
|
||||||
|
if ( ($now-$lastCheck) > CHECK_INTERVAL ) {
|
||||||
|
Info( 'Collecting data to send to ZoneMinder Telemetry server.' );
|
||||||
|
my $dbh = zmDbConnect();
|
||||||
|
# Build the telemetry hash
|
||||||
|
# We should keep *BSD systems in mind when calling system commands
|
||||||
|
my %telemetry;
|
||||||
|
$telemetry{uuid} = getUUID($dbh);
|
||||||
|
$telemetry{ip} = getIP();
|
||||||
|
$telemetry{timestamp} = strftime( '%Y-%m-%dT%H:%M:%S%z', localtime() );
|
||||||
|
$telemetry{monitor_count} = countQuery($dbh,'Monitors');
|
||||||
|
$telemetry{event_count} = countQuery($dbh,'Events');
|
||||||
|
$telemetry{architecture} = runSysCmd('uname -p');
|
||||||
|
($telemetry{kernel}, $telemetry{distro}, $telemetry{version}) = getDistro();
|
||||||
|
$telemetry{zm_version} = ZoneMinder::Base::ZM_VERSION;
|
||||||
|
$telemetry{system_memory} = totalmem();
|
||||||
|
$telemetry{processor_count} = cpu_count();
|
||||||
|
$telemetry{monitors} = getMonitorRef($dbh);
|
||||||
|
|
||||||
|
Info( 'Sending data to ZoneMinder Telemetry server.' );
|
||||||
|
|
||||||
|
my $result = jsonEncode( \%telemetry );
|
||||||
|
|
||||||
|
if ( sendData($result) ) {
|
||||||
|
$lastCheck = $now;
|
||||||
|
|
||||||
|
my $sql = q`update Config set Value = ? where Name = 'ZM_TELEMETRY_LAST_UPLOAD'`;
|
||||||
|
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||||
|
my $res = $sth->execute( "$lastCheck" ) or die( "Can't execute: ".$sth->errstr() );
|
||||||
|
$sth->finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sleep( 3600 );
|
||||||
|
}
|
||||||
|
print( 'Update agent exiting at '.strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" );
|
||||||
|
}
|
||||||
|
|
||||||
|
###############
|
||||||
|
# SUBROUTINES #
|
||||||
|
###############
|
||||||
|
|
||||||
|
# Find, verify, then run the supplied system command
|
||||||
|
sub runSysCmd {
|
||||||
|
my $msg = shift;
|
||||||
|
my @arguments = split(/ /,$msg);
|
||||||
|
chomp($arguments[0]);
|
||||||
|
my $path = qx( which $arguments[0] );
|
||||||
|
|
||||||
|
my $status = $? >> 8;
|
||||||
|
my $result = '';
|
||||||
|
if ( !$path || $status ) {
|
||||||
|
Warning( "Cannot find the $arguments[0] executable." );
|
||||||
|
} else {
|
||||||
|
chomp($path);
|
||||||
|
$arguments[0] = $path;
|
||||||
|
my $cmd = join(' ',@arguments);
|
||||||
|
$result = qx( $cmd );
|
||||||
|
chomp($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Upload message data to ZoneMinder telemetry server
|
||||||
|
sub sendData {
|
||||||
|
my $msg = shift;
|
||||||
|
|
||||||
|
my $ua = LWP::UserAgent->new;
|
||||||
|
my $server_endpoint = ZM_TELEMETRY_SERVER_ENDPOINT;
|
||||||
|
|
||||||
|
if ( $Config{ZM_UPDATE_CHECK_PROXY} ) {
|
||||||
|
$ua->proxy( 'https', $Config{ZM_UPDATE_CHECK_PROXY} );
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug("Posting telemetry data to: $server_endpoint");
|
||||||
|
|
||||||
|
# set custom HTTP request header fields
|
||||||
|
my $req = HTTP::Request->new(POST => $server_endpoint);
|
||||||
|
$req->header('content-type' => 'application/x-www-form-urlencoded');
|
||||||
|
$req->header('content-length' => length($msg));
|
||||||
|
$req->header('connection' => 'Close');
|
||||||
|
|
||||||
|
$req->content($msg);
|
||||||
|
|
||||||
|
my $resp = $ua->request($req);
|
||||||
|
my $resp_msg = $resp->decoded_content;
|
||||||
|
my $resp_code = $resp->code;
|
||||||
|
if ($resp->is_success) {
|
||||||
|
Info('Telemetry data uploaded successfully.');
|
||||||
|
Debug("Telemetry server upload success response message: $resp_msg");
|
||||||
|
} else {
|
||||||
|
Warning("Telemetry server returned HTTP POST error code: $resp_code");
|
||||||
|
Debug("Telemetry server upload failure response message: $resp_msg");
|
||||||
|
}
|
||||||
|
return $resp->is_success;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Retrieves the UUID from the database. Creates a new UUID if one does not exist.
|
||||||
|
sub getUUID {
|
||||||
|
my $dbh = shift;
|
||||||
|
my $uuid= "";
|
||||||
|
|
||||||
|
# Verify the current UUID is valid and not nil
|
||||||
|
if (( $Config{ZM_TELEMETRY_UUID} =~ /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i ) && ( $Config{ZM_TELEMETRY_UUID} ne '00000000-0000-0000-0000-000000000000' )) {
|
||||||
|
$uuid = $Config{ZM_TELEMETRY_UUID};
|
||||||
|
} else {
|
||||||
|
my $sql = 'SELECT uuid()';
|
||||||
|
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() );
|
||||||
|
$uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array();
|
||||||
|
$sth->finish();
|
||||||
|
|
||||||
|
$sql = q`UPDATE Config set Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'`;
|
||||||
|
$sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||||
|
$res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() );
|
||||||
|
$sth->finish();
|
||||||
|
}
|
||||||
|
Debug("Using UUID of: $uuid");
|
||||||
|
|
||||||
|
return $uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Retrieves the local server's external IP address
|
||||||
|
sub getIP {
|
||||||
|
my $ipaddr = '0.0.0.0';
|
||||||
|
my $ua = LWP::UserAgent->new;
|
||||||
|
my $server_endpoint = 'https://wiki.zoneminder.com/ip.php';
|
||||||
|
|
||||||
|
my $req = HTTP::Request->new(GET => $server_endpoint);
|
||||||
|
my $resp = $ua->request($req);
|
||||||
|
|
||||||
|
if ($resp->is_success) {
|
||||||
|
$ipaddr = $resp->decoded_content;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug("Found external ip address of: $ipaddr");
|
||||||
|
|
||||||
|
return $ipaddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
# As the name implies, just your average mysql count query
|
||||||
|
sub countQuery {
|
||||||
|
my $dbh = shift;
|
||||||
|
my $table = shift;
|
||||||
|
|
||||||
|
my $sql = "SELECT count(*) FROM $table";
|
||||||
|
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 $count = $sth->fetchrow_array();
|
||||||
|
$sth->finish();
|
||||||
|
|
||||||
|
return $count
|
||||||
|
}
|
||||||
|
|
||||||
|
# Returns a reference to an array of hashes containing data from all monitors
|
||||||
|
sub getMonitorRef {
|
||||||
|
my $dbh = shift;
|
||||||
|
|
||||||
|
my $sql = 'SELECT Id,Name,Type,Function,Width,Height,Colours,MaxFPS,AlarmMaxFPS 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 $arrayref = $sth->fetchall_arrayref({});
|
||||||
|
|
||||||
|
return $arrayref;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub getDistro {
|
||||||
|
my $kernel = '';
|
||||||
|
my $distro = '';
|
||||||
|
my $version = '';
|
||||||
|
my @uname = uname();
|
||||||
|
|
||||||
|
if ( $uname[0] =~ /Linux/ ) {
|
||||||
|
Debug('Linux distro detected.');
|
||||||
|
($kernel, $distro, $version) = linuxDistro();
|
||||||
|
} elsif ( $uname[0] =~ /.*BSD/ ) {
|
||||||
|
Debug('BSD distro detected.');
|
||||||
|
$kernel = $uname[3];
|
||||||
|
$distro = $uname[0];
|
||||||
|
$version = $uname[2];
|
||||||
|
} elsif ( $uname[0] =~ /Darwin/ ) {
|
||||||
|
Debug('Mac OS distro detected.');
|
||||||
|
$kernel = $uname[3];
|
||||||
|
$distro = runSysCmd('sw_vers -productName');
|
||||||
|
$version = runSysCmd('sw_vers -productVersion');
|
||||||
|
} elsif ( $uname[0] =~ /SunOS|Solaris/ ) {
|
||||||
|
Debug('Sun Solaris detected.');
|
||||||
|
$kernel = $uname[3];
|
||||||
|
$distro = $uname[1];
|
||||||
|
$version = $uname[2];
|
||||||
|
} else {
|
||||||
|
Warning('ZoneMinder was unable to determine the host system. Please report.');
|
||||||
|
$kernel = 'Unknown';
|
||||||
|
$distro = 'Unknown';
|
||||||
|
$version = 'Unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($kernel, $distro, $version);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub linuxDistro {
|
||||||
|
my @uname = uname();
|
||||||
|
my $kernel = $uname[2];
|
||||||
|
my $distro = 'Unknown Linux Distro';
|
||||||
|
my $version = 'Unknown Linux Version';
|
||||||
|
my $found = 0;
|
||||||
|
|
||||||
|
# os-release is the standard for many new distros based on systemd
|
||||||
|
if ( -f '/etc/os-release' ) {
|
||||||
|
open(my $RELFILE,'<','/etc/os-release') or die( "Can't Open file: $!\n" );
|
||||||
|
while (<$RELFILE>) {
|
||||||
|
if ( /^NAME=(")?(.*)(?(1)\1|).*$/ ) {
|
||||||
|
$distro = $2;
|
||||||
|
$found = 1;
|
||||||
|
}
|
||||||
|
if ( /^VERSION_ID=(")?(.*)(?(1)\1|).*$/ ) {
|
||||||
|
$version = $2;
|
||||||
|
$found = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close $RELFILE;
|
||||||
|
# exists on many distros but does not always contain useful information, such as redhat
|
||||||
|
} elsif ( -f '/etc/lsb-release' ) {
|
||||||
|
open(my $RELFILE,'<','/etc/lsb-release') or die( "Can't Open file: $!\n" );
|
||||||
|
while (<$RELFILE>) {
|
||||||
|
if ( /^DISTRIB_DESCRIPTION=(")?(.*)(?(1)\1|).*$/ ) {
|
||||||
|
$distro = $2;
|
||||||
|
$found = 1;
|
||||||
|
}
|
||||||
|
if ( /^DISTRIB_RELEASE=(")?(.*)(?(1)\1|).*$/ ) {
|
||||||
|
$version = $2;
|
||||||
|
$found = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close $RELFILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
# If all else fails, search through a list of known release files until we find one
|
||||||
|
if ( !$found ) {
|
||||||
|
my @releasefile = ('/etc/SuSE-release', '/etc/redhat-release', '/etc/redhat_version',
|
||||||
|
'/etc/fedora-release', '/etc/slackware-release', '/etc/slackware-version',
|
||||||
|
'/etc/debian_release', '/etc/debian_version', '/etc/mandrake-release',
|
||||||
|
'/etc/yellowdog-release', '/etc/gentoo-release');
|
||||||
|
foreach (@releasefile) {
|
||||||
|
if ( -f $_ ) {
|
||||||
|
open(my $RELFILE,'<',$_) or die( "Can't Open file: $!\n" );
|
||||||
|
while (<$RELFILE>) {
|
||||||
|
if ( /(.*).* (\d+\.?\d*) .*/ ) {
|
||||||
|
$distro = $1;
|
||||||
|
$version = $2;
|
||||||
|
$found = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close $RELFILE;
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !$found ) {
|
||||||
|
Warning('ZoneMinder was unable to determine Linux distro. Please report.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($kernel, $distro, $version);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
__END__
|
||||||
|
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
|
|
||||||
zmtelemetry.pl - Send usage information to the ZoneMinder development team
|
zmtelemetry.pl - Send usage information to the ZoneMinder development team
|
||||||
|
@ -44,300 +342,3 @@ console under Options.
|
||||||
none currently
|
none currently
|
||||||
|
|
||||||
=cut
|
=cut
|
||||||
use strict;
|
|
||||||
use bytes;
|
|
||||||
|
|
||||||
@EXTRA_PERL_LIB@
|
|
||||||
use ZoneMinder;
|
|
||||||
use DBI;
|
|
||||||
use Getopt::Long;
|
|
||||||
use autouse 'Pod::Usage'=>qw(pod2usage);
|
|
||||||
use LWP::UserAgent;
|
|
||||||
use Sys::MemInfo qw(totalmem);
|
|
||||||
use Sys::CPU qw(cpu_count);
|
|
||||||
use POSIX qw(strftime uname);
|
|
||||||
|
|
||||||
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
|
|
||||||
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
|
||||||
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
|
||||||
|
|
||||||
use constant CHECK_INTERVAL => (14*24*60*60); # Interval between version checks
|
|
||||||
|
|
||||||
# Setting these as contants for now.
|
|
||||||
# Alternatively, we can put these in the dB and then retrieve using the Config hash.
|
|
||||||
use constant ZM_TELEMETRY_SERVER_ENDPOINT => 'https://zmanon:2b2d0b4skps@telemetry.zoneminder.com/zmtelemetry/testing5';
|
|
||||||
|
|
||||||
if ( $Config{ZM_TELEMETRY_DATA} )
|
|
||||||
{
|
|
||||||
print( "Update agent starting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" );
|
|
||||||
|
|
||||||
my $lastCheck = $Config{ZM_TELEMETRY_LAST_UPLOAD};
|
|
||||||
|
|
||||||
while( 1 ) {
|
|
||||||
my $now = time();
|
|
||||||
if ( ($now-$lastCheck) > CHECK_INTERVAL ) {
|
|
||||||
Info( "Collecting data to send to ZoneMinder Telemetry server." );
|
|
||||||
my $dbh = zmDbConnect();
|
|
||||||
# Build the telemetry hash
|
|
||||||
# We should keep *BSD systems in mind when calling system commands
|
|
||||||
my %telemetry;
|
|
||||||
$telemetry{uuid} = getUUID($dbh);
|
|
||||||
$telemetry{ip} = getIP();
|
|
||||||
$telemetry{timestamp} = strftime( "%Y-%m-%dT%H:%M:%S%z", localtime() );
|
|
||||||
$telemetry{monitor_count} = countQuery($dbh,"Monitors");
|
|
||||||
$telemetry{event_count} = countQuery($dbh,"Events");
|
|
||||||
$telemetry{architecture} = runSysCmd("uname -p");
|
|
||||||
($telemetry{kernel}, $telemetry{distro}, $telemetry{version}) = getDistro();
|
|
||||||
$telemetry{zm_version} = ZoneMinder::Base::ZM_VERSION;
|
|
||||||
$telemetry{system_memory} = totalmem();
|
|
||||||
$telemetry{processor_count} = cpu_count();
|
|
||||||
$telemetry{monitors} = getMonitorRef($dbh);
|
|
||||||
|
|
||||||
Info( "Sending data to ZoneMinder Telemetry server." );
|
|
||||||
|
|
||||||
my $result = jsonEncode( \%telemetry );
|
|
||||||
|
|
||||||
if ( sendData($result) ) {
|
|
||||||
$lastCheck = $now;
|
|
||||||
|
|
||||||
my $sql = "update Config set Value = ? where Name = 'ZM_TELEMETRY_LAST_UPLOAD'";
|
|
||||||
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
||||||
my $res = $sth->execute( "$lastCheck" ) or die( "Can't execute: ".$sth->errstr() );
|
|
||||||
$sth->finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sleep( 3600 );
|
|
||||||
}
|
|
||||||
print( "Update agent exiting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" );
|
|
||||||
}
|
|
||||||
|
|
||||||
###############
|
|
||||||
# SUBROUTINES #
|
|
||||||
###############
|
|
||||||
|
|
||||||
# Find, verify, then run the supplied system command
|
|
||||||
sub runSysCmd {
|
|
||||||
my $msg = shift;
|
|
||||||
my @arguments = split(/ /,$msg);
|
|
||||||
chomp($arguments[0]);
|
|
||||||
my $path = qx( which $arguments[0] );
|
|
||||||
|
|
||||||
my $status = $? >> 8;
|
|
||||||
my $result = "";
|
|
||||||
if ( !$path || $status ) {
|
|
||||||
Warning( "Cannot find the $arguments[0] executable." );
|
|
||||||
} else {
|
|
||||||
chomp($path);
|
|
||||||
$arguments[0] = $path;
|
|
||||||
my $cmd = join(" ",@arguments);
|
|
||||||
$result = qx( $cmd );
|
|
||||||
chomp($result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Upload message data to ZoneMinder telemetry server
|
|
||||||
sub sendData {
|
|
||||||
my $msg = shift;
|
|
||||||
|
|
||||||
my $ua = LWP::UserAgent->new;
|
|
||||||
my $server_endpoint = ZM_TELEMETRY_SERVER_ENDPOINT;
|
|
||||||
|
|
||||||
if ( $Config{ZM_UPDATE_CHECK_PROXY} ) {
|
|
||||||
$ua->proxy( "https", $Config{ZM_UPDATE_CHECK_PROXY} );
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug("Posting telemetry data to: $server_endpoint");
|
|
||||||
|
|
||||||
# set custom HTTP request header fields
|
|
||||||
my $req = HTTP::Request->new(POST => $server_endpoint);
|
|
||||||
$req->header('content-type' => 'application/x-www-form-urlencoded');
|
|
||||||
$req->header('content-length' => length($msg));
|
|
||||||
$req->header('connection' => 'Close');
|
|
||||||
|
|
||||||
$req->content($msg);
|
|
||||||
|
|
||||||
my $resp = $ua->request($req);
|
|
||||||
my $resp_msg = $resp->decoded_content;
|
|
||||||
my $resp_code = $resp->code;
|
|
||||||
if ($resp->is_success) {
|
|
||||||
Info("Telemetry data uploaded successfully.");
|
|
||||||
Debug("Telemetry server upload success response message: $resp_msg");
|
|
||||||
} else {
|
|
||||||
Warning("Telemetry server returned HTTP POST error code: $resp_code");
|
|
||||||
Debug("Telemetry server upload failure response message: $resp_msg");
|
|
||||||
}
|
|
||||||
return $resp->is_success;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Retrieves the UUID from the database. Creates a new UUID if one does not exist.
|
|
||||||
sub getUUID {
|
|
||||||
my $dbh = shift;
|
|
||||||
my $uuid= "";
|
|
||||||
|
|
||||||
# Verify the current UUID is valid and not nil
|
|
||||||
if (( $Config{ZM_TELEMETRY_UUID} =~ /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i ) && ( $Config{ZM_TELEMETRY_UUID} ne "00000000-0000-0000-0000-000000000000" )) {
|
|
||||||
$uuid = $Config{ZM_TELEMETRY_UUID};
|
|
||||||
} else {
|
|
||||||
my $sql = "SELECT uuid()";
|
|
||||||
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() );
|
|
||||||
$uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array();
|
|
||||||
$sth->finish();
|
|
||||||
|
|
||||||
$sql = "UPDATE Config set Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'";
|
|
||||||
$sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
||||||
$res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() );
|
|
||||||
$sth->finish();
|
|
||||||
}
|
|
||||||
Debug("Using UUID of: $uuid");
|
|
||||||
|
|
||||||
return $uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Retrieves the local server's external IP address
|
|
||||||
sub getIP {
|
|
||||||
my $ipaddr = "0.0.0.0";
|
|
||||||
my $ua = LWP::UserAgent->new;
|
|
||||||
my $server_endpoint = "https://wiki.zoneminder.com/ip.php";
|
|
||||||
|
|
||||||
my $req = HTTP::Request->new(GET => $server_endpoint);
|
|
||||||
my $resp = $ua->request($req);
|
|
||||||
|
|
||||||
if ($resp->is_success) {
|
|
||||||
$ipaddr = $resp->decoded_content;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug("Found external ip address of: $ipaddr");
|
|
||||||
|
|
||||||
return $ipaddr;
|
|
||||||
}
|
|
||||||
|
|
||||||
# As the name implies, just your average mysql count query
|
|
||||||
sub countQuery {
|
|
||||||
my $dbh = shift;
|
|
||||||
my $table = shift;
|
|
||||||
|
|
||||||
my $sql = "SELECT count(*) FROM $table";
|
|
||||||
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 $count = $sth->fetchrow_array();
|
|
||||||
$sth->finish();
|
|
||||||
|
|
||||||
return $count
|
|
||||||
}
|
|
||||||
|
|
||||||
# Returns a reference to an array of hashes containing data from all monitors
|
|
||||||
sub getMonitorRef {
|
|
||||||
my $dbh = shift;
|
|
||||||
|
|
||||||
my $sql = "SELECT Id,Name,Type,Function,Width,Height,Colours,MaxFPS,AlarmMaxFPS 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 $arrayref = $sth->fetchall_arrayref({});
|
|
||||||
|
|
||||||
return $arrayref;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub getDistro {
|
|
||||||
my $kernel = "";
|
|
||||||
my $distro = "";
|
|
||||||
my $version = "";
|
|
||||||
my @uname = uname();
|
|
||||||
|
|
||||||
if ( $uname[0] =~ /Linux/ ) {
|
|
||||||
Debug("Linux distro detected.");
|
|
||||||
($kernel, $distro, $version) = linuxDistro();
|
|
||||||
} elsif ( $uname[0] =~ /.*BSD/ ) {
|
|
||||||
Debug("BSD distro detected.");
|
|
||||||
$kernel = $uname[3];
|
|
||||||
$distro = $uname[0];
|
|
||||||
$version = $uname[2];
|
|
||||||
} elsif ( $uname[0] =~ /Darwin/ ) {
|
|
||||||
Debug("Mac OS distro detected.");
|
|
||||||
$kernel = $uname[3];
|
|
||||||
$distro = runSysCmd("sw_vers -productName");
|
|
||||||
$version = runSysCmd("sw_vers -productVersion");
|
|
||||||
} elsif ( $uname[0] =~ /SunOS|Solaris/ ) {
|
|
||||||
Debug("Sun Solaris detected.");
|
|
||||||
$kernel = $uname[3];
|
|
||||||
$distro = $uname[1];
|
|
||||||
$version = $uname[2];
|
|
||||||
} else {
|
|
||||||
Warning("ZoneMinder was unable to determine the host system. Please report.");
|
|
||||||
$kernel = "Unknown";
|
|
||||||
$distro = "Unknown";
|
|
||||||
$version = "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
return ($kernel, $distro, $version);
|
|
||||||
}
|
|
||||||
|
|
||||||
sub linuxDistro {
|
|
||||||
my @uname = uname();
|
|
||||||
my $kernel = $uname[2];
|
|
||||||
my $distro = "Unknown Linux Distro";
|
|
||||||
my $version = "Unknown Linux Version";
|
|
||||||
my $found = 0;
|
|
||||||
|
|
||||||
# os-release is the standard for many new distros based on systemd
|
|
||||||
if ( -f "/etc/os-release" ) {
|
|
||||||
open(my $RELFILE,"<","/etc/os-release") or die( "Can't Open file: $!\n" );
|
|
||||||
while (<$RELFILE>) {
|
|
||||||
if ( /^NAME=(")?(.*)(?(1)\1|).*$/ ) {
|
|
||||||
$distro = $2;
|
|
||||||
$found = 1;
|
|
||||||
}
|
|
||||||
if ( /^VERSION_ID=(")?(.*)(?(1)\1|).*$/ ) {
|
|
||||||
$version = $2;
|
|
||||||
$found = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close $RELFILE;
|
|
||||||
# exists on many distros but does not always contain useful information, such as redhat
|
|
||||||
} elsif ( -f "/etc/lsb-release" ) {
|
|
||||||
open(my $RELFILE,"<","/etc/lsb-release") or die( "Can't Open file: $!\n" );
|
|
||||||
while (<$RELFILE>) {
|
|
||||||
if ( /^DISTRIB_DESCRIPTION=(")?(.*)(?(1)\1|).*$/ ) {
|
|
||||||
$distro = $2;
|
|
||||||
$found = 1;
|
|
||||||
}
|
|
||||||
if ( /^DISTRIB_RELEASE=(")?(.*)(?(1)\1|).*$/ ) {
|
|
||||||
$version = $2;
|
|
||||||
$found = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close $RELFILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
# If all else fails, search through a list of known release files until we find one
|
|
||||||
if ( !$found ) {
|
|
||||||
my @releasefile = ("/etc/SuSE-release", "/etc/redhat-release", "/etc/redhat_version",
|
|
||||||
"/etc/fedora-release", "/etc/slackware-release", "/etc/slackware-version",
|
|
||||||
"/etc/debian_release", "/etc/debian_version", "/etc/mandrake-release",
|
|
||||||
"/etc/yellowdog-release", "/etc/gentoo-release");
|
|
||||||
foreach (@releasefile) {
|
|
||||||
if ( -f $_ ) {
|
|
||||||
open(my $RELFILE,"<",$_) or die( "Can't Open file: $!\n" );
|
|
||||||
while (<$RELFILE>) {
|
|
||||||
if ( /(.*).* (\d+\.?\d*) .*/ ) {
|
|
||||||
$distro = $1;
|
|
||||||
$version = $2;
|
|
||||||
$found = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close $RELFILE;
|
|
||||||
last;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !$found ) {
|
|
||||||
Warning("ZoneMinder was unable to determine Linux distro. Please report.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ($kernel, $distro, $version);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
#
|
#
|
||||||
# ZoneMinder External Trigger Script, $Date: 2008-07-25 10:48:16 +0100 (Fri, 25 Jul 2008) $, $Revision: 2612 $
|
# ZoneMinder External Trigger Script
|
||||||
# Copyright (C) 2001-2008 Philip Coombes
|
# Copyright (C) 2001-2008 Philip Coombes
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or
|
# This program is free software; you can redistribute it and/or
|
||||||
|
@ -21,6 +21,428 @@
|
||||||
#
|
#
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use bytes;
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
#
|
||||||
|
# User config
|
||||||
|
#
|
||||||
|
# ==========================================================================
|
||||||
|
|
||||||
|
use constant MAX_CONNECT_DELAY => 10;
|
||||||
|
use constant MONITOR_RELOAD_INTERVAL => 300;
|
||||||
|
use constant SELECT_TIMEOUT => 0.25;
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
#
|
||||||
|
# Channel/Connection Modules
|
||||||
|
#
|
||||||
|
# ==========================================================================
|
||||||
|
|
||||||
|
@EXTRA_PERL_LIB@
|
||||||
|
use ZoneMinder;
|
||||||
|
use ZoneMinder::Trigger::Channel::Inet;
|
||||||
|
use ZoneMinder::Trigger::Channel::Unix;
|
||||||
|
use ZoneMinder::Trigger::Channel::Serial;
|
||||||
|
use ZoneMinder::Trigger::Connection;
|
||||||
|
|
||||||
|
my @connections;
|
||||||
|
push( @connections,
|
||||||
|
ZoneMinder::Trigger::Connection->new(
|
||||||
|
name=>'Chan1 TCP on port 6802',
|
||||||
|
channel=>ZoneMinder::Trigger::Channel::Inet->new( port=>6802 ),
|
||||||
|
mode=>'rw'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
push( @connections,
|
||||||
|
ZoneMinder::Trigger::Connection->new(
|
||||||
|
name=>'Chan2 Unix Socket at ' . $Config{ZM_PATH_SOCKS}.'/zmtrigger.sock',
|
||||||
|
channel=>ZoneMinder::Trigger::Channel::Unix->new(
|
||||||
|
path=>$Config{ZM_PATH_SOCKS}.'/zmtrigger.sock'
|
||||||
|
),
|
||||||
|
mode=>'rw'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
#push( @connections, ZoneMinder::Trigger::Connection->new( name=>'Chan3', channel=>ZoneMinder::Trigger::Channel::File->new( path=>'/tmp/zmtrigger.out' ), mode=>'w' ) );
|
||||||
|
#push( @connections, ZoneMinder::Trigger::Connection->new( name=>'Chan4', channel=>ZoneMinder::Trigger::Channel::Serial->new( path=>'/dev/ttyS0' ), mode=>'rw' ) );
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
#
|
||||||
|
# Don't change anything from here on down
|
||||||
|
#
|
||||||
|
# ==========================================================================
|
||||||
|
|
||||||
|
use DBI;
|
||||||
|
#use Socket;
|
||||||
|
use autouse 'Data::Dumper'=>qw(Dumper);
|
||||||
|
use POSIX qw( EINTR );
|
||||||
|
use Time::HiRes qw( usleep );
|
||||||
|
|
||||||
|
$| = 1;
|
||||||
|
|
||||||
|
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
|
||||||
|
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
||||||
|
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
||||||
|
|
||||||
|
logInit();
|
||||||
|
logSetSignal();
|
||||||
|
|
||||||
|
Info( "Trigger daemon starting\n" );
|
||||||
|
|
||||||
|
my $dbh = zmDbConnect();
|
||||||
|
|
||||||
|
my $base_rin = '';
|
||||||
|
foreach my $connection ( @connections ) {
|
||||||
|
Info( "Opening connection '$connection->{name}'\n" );
|
||||||
|
$connection->open();
|
||||||
|
}
|
||||||
|
|
||||||
|
my @in_select_connections = grep { $_->input() && $_->selectable() } @connections;
|
||||||
|
my @in_poll_connections = grep { $_->input() && !$_->selectable() } @connections;
|
||||||
|
my @out_connections = grep { $_->output() } @connections;
|
||||||
|
|
||||||
|
foreach my $connection ( @in_select_connections ) {
|
||||||
|
vec( $base_rin, $connection->fileno(), 1 ) = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
my %spawned_connections;
|
||||||
|
my %monitors;
|
||||||
|
my $monitor_reload_time = 0;
|
||||||
|
my $needsReload = 0;
|
||||||
|
loadMonitors();
|
||||||
|
|
||||||
|
$! = undef;
|
||||||
|
my $rin = '';
|
||||||
|
my $win = $rin;
|
||||||
|
my $ein = $win;
|
||||||
|
my $timeout = SELECT_TIMEOUT;
|
||||||
|
my %actions;
|
||||||
|
while( 1 ) {
|
||||||
|
$rin = $base_rin;
|
||||||
|
# Add the file descriptors of any spawned connections
|
||||||
|
foreach my $fileno ( keys(%spawned_connections) ) {
|
||||||
|
vec( $rin, $fileno, 1 ) = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $nfound = select( my $rout = $rin, undef, my $eout = $ein, $timeout );
|
||||||
|
if ( $nfound > 0 ) {
|
||||||
|
Debug( "Got input from $nfound connections\n" );
|
||||||
|
foreach my $connection ( @in_select_connections ) {
|
||||||
|
if ( vec( $rout, $connection->fileno(), 1 ) ) {
|
||||||
|
Debug( 'Got input from connection '
|
||||||
|
.$connection->name()
|
||||||
|
.' ('
|
||||||
|
.$connection->fileno()
|
||||||
|
.")\n"
|
||||||
|
);
|
||||||
|
if ( $connection->spawns() ) {
|
||||||
|
my $new_connection = $connection->accept();
|
||||||
|
$spawned_connections{$new_connection->fileno()} = $new_connection;
|
||||||
|
Debug( 'Added new spawned connection ('
|
||||||
|
.$new_connection->fileno()
|
||||||
|
.'), '
|
||||||
|
.int(keys(%spawned_connections))
|
||||||
|
." spawned connections\n"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
my $messages = $connection->getMessages();
|
||||||
|
if ( defined($messages) ) {
|
||||||
|
foreach my $message ( @$messages ) {
|
||||||
|
handleMessage( $connection, $message );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} # end foreach connection
|
||||||
|
foreach my $connection ( values(%spawned_connections) ) {
|
||||||
|
if ( vec( $rout, $connection->fileno(), 1 ) ) {
|
||||||
|
Debug( 'Got input from spawned connection '
|
||||||
|
.$connection->name()
|
||||||
|
.' ('
|
||||||
|
.$connection->fileno()
|
||||||
|
.")\n"
|
||||||
|
);
|
||||||
|
my $messages = $connection->getMessages();
|
||||||
|
if ( defined($messages) ) {
|
||||||
|
foreach my $message ( @$messages ) {
|
||||||
|
handleMessage( $connection, $message );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete( $spawned_connections{$connection->fileno()} );
|
||||||
|
Debug( 'Removed spawned connection ('
|
||||||
|
.$connection->fileno()
|
||||||
|
.'), '
|
||||||
|
.int(keys(%spawned_connections))
|
||||||
|
." spawned connections\n"
|
||||||
|
);
|
||||||
|
$connection->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} # end foreach spawned connection
|
||||||
|
} elsif ( $nfound < 0 ) {
|
||||||
|
if ( $! == EINTR ) {
|
||||||
|
# Do nothing
|
||||||
|
} else {
|
||||||
|
Fatal( "Can't select: $!" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check polled connections
|
||||||
|
foreach my $connection ( @in_poll_connections ) {
|
||||||
|
my $messages = $connection->getMessages();
|
||||||
|
if ( defined($messages) ) {
|
||||||
|
foreach my $message ( @$messages ) {
|
||||||
|
handleMessage( $connection, $message );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for alarms that might have happened
|
||||||
|
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',
|
||||||
|
'shared_data:last_event'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
#print( "$monitor->{Id}: S:$state, LE:$last_event\n" );
|
||||||
|
#print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}\n" );
|
||||||
|
if ( $state == STATE_ALARM || $state == STATE_ALERT ) {
|
||||||
|
# In alarm state
|
||||||
|
if ( !defined($monitor->{LastEvent})
|
||||||
|
|| ($last_event != $monitor->{LastEvent})
|
||||||
|
) {
|
||||||
|
# A new event
|
||||||
|
push( @out_messages, $monitor->{Id}."|on|".time()."|".$last_event );
|
||||||
|
} else {
|
||||||
|
# The same one as last time, so ignore it
|
||||||
|
# Do nothing
|
||||||
|
}
|
||||||
|
} elsif (
|
||||||
|
($state == STATE_IDLE && $monitor->{LastState} != STATE_IDLE)
|
||||||
|
||
|
||||||
|
($state == STATE_TAPE && $monitor->{LastState} != STATE_TAPE)
|
||||||
|
) {
|
||||||
|
# Out of alarm state
|
||||||
|
push( @out_messages, $monitor->{Id}.'|off|'.time().'|'.$last_event );
|
||||||
|
} elsif (
|
||||||
|
defined($monitor->{LastEvent})
|
||||||
|
&&
|
||||||
|
($last_event != $monitor->{LastEvent})
|
||||||
|
) {
|
||||||
|
# We've missed a whole event
|
||||||
|
push( @out_messages, $monitor->{Id}.'|on|'.time().'|'.$last_event );
|
||||||
|
push( @out_messages, $monitor->{Id}.'|off|'.time().'|'.$last_event );
|
||||||
|
}
|
||||||
|
$monitor->{LastState} = $state;
|
||||||
|
$monitor->{LastEvent} = $last_event;
|
||||||
|
}
|
||||||
|
foreach my $connection ( @out_connections ) {
|
||||||
|
if ( $connection->canWrite() ) {
|
||||||
|
$connection->putMessages( \@out_messages );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach my $connection ( values(%spawned_connections) ) {
|
||||||
|
if ( $connection->canWrite() ) {
|
||||||
|
$connection->putMessages( \@out_messages );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug( "Checking for timed actions\n" ) if int(keys(%actions));
|
||||||
|
my $now = time();
|
||||||
|
foreach my $action_time ( sort( grep { $_ < $now } keys( %actions ) ) ) {
|
||||||
|
Info( "Found actions expiring at $action_time\n" );
|
||||||
|
foreach my $action ( @{$actions{$action_time}} ) {
|
||||||
|
my $connection = $action->{connection};
|
||||||
|
my $message = $action->{message};
|
||||||
|
Info( "Found action '$message'\n" );
|
||||||
|
handleMessage( $connection, $message );
|
||||||
|
}
|
||||||
|
delete( $actions{$action_time} );
|
||||||
|
}
|
||||||
|
|
||||||
|
# Allow connections to do their own timed actions
|
||||||
|
foreach my $connection ( @connections ) {
|
||||||
|
my $messages = $connection->timedActions();
|
||||||
|
if ( defined($messages) ) {
|
||||||
|
foreach my $message ( @$messages ) {
|
||||||
|
handleMessage( $connection, $message );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach my $connection ( values(%spawned_connections) ) {
|
||||||
|
my $messages = $connection->timedActions();
|
||||||
|
if ( defined($messages) ) {
|
||||||
|
foreach my $message ( @$messages ) {
|
||||||
|
handleMessage( $connection, $message );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# If necessary reload monitors
|
||||||
|
if ( $needsReload || ((time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL )) {
|
||||||
|
foreach my $monitor ( values(%monitors) ) {
|
||||||
|
# Free up any used memory handle
|
||||||
|
zmMemInvalidate( $monitor );
|
||||||
|
}
|
||||||
|
loadMonitors();
|
||||||
|
$needsReload = 0;
|
||||||
|
}
|
||||||
|
# zmDbConnect will ping and reconnect if neccessary
|
||||||
|
$dbh = zmDbConnect();
|
||||||
|
} # end while ( 1 )
|
||||||
|
Info( "Trigger daemon exiting\n" );
|
||||||
|
exit;
|
||||||
|
|
||||||
|
sub loadMonitors {
|
||||||
|
Debug( "Loading monitors\n" );
|
||||||
|
$monitor_reload_time = time();
|
||||||
|
|
||||||
|
my %new_monitors = ();
|
||||||
|
|
||||||
|
my $sql = "SELECT * FROM Monitors
|
||||||
|
WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )".
|
||||||
|
( $Config{ZM_SERVER_ID} ? 'AND ServerId=?' : '' )
|
||||||
|
;
|
||||||
|
my $sth = $dbh->prepare_cached( $sql )
|
||||||
|
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||||
|
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
|
||||||
|
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||||
|
while( my $monitor = $sth->fetchrow_hashref() ) {
|
||||||
|
# Check shared memory ok
|
||||||
|
if ( !zmMemVerify( $monitor ) ) {
|
||||||
|
zmMemInvalidate( $monitor );
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( defined($monitors{$monitor->{Id}}->{LastState}) ) {
|
||||||
|
$monitor->{LastState} = $monitors{$monitor->{Id}}->{LastState};
|
||||||
|
} else {
|
||||||
|
$monitor->{LastState} = zmGetMonitorState( $monitor );
|
||||||
|
}
|
||||||
|
if ( defined($monitors{$monitor->{Id}}->{LastEvent}) ) {
|
||||||
|
$monitor->{LastEvent} = $monitors{$monitor->{Id}}->{LastEvent};
|
||||||
|
} else {
|
||||||
|
$monitor->{LastEvent} = zmGetLastEvent( $monitor );
|
||||||
|
}
|
||||||
|
$new_monitors{$monitor->{Id}} = $monitor;
|
||||||
|
} # end foreach monitor
|
||||||
|
%monitors = %new_monitors;
|
||||||
|
} # end sub loadMonitors
|
||||||
|
|
||||||
|
sub handleMessage {
|
||||||
|
my $connection = shift;
|
||||||
|
my $message = shift;
|
||||||
|
|
||||||
|
my ( $id, $action, $score, $cause, $text, $showtext )
|
||||||
|
= split( /\|/, $message );
|
||||||
|
$score = 0 if !defined($score);
|
||||||
|
$cause = '' if !defined($cause);
|
||||||
|
$text = '' if !defined($text);
|
||||||
|
|
||||||
|
my $monitor = $monitors{$id};
|
||||||
|
if ( !$monitor ) {
|
||||||
|
Warning( "Can't find monitor '$id' for message '$message'\n" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Debug( "Found monitor for id '$id'\n" );
|
||||||
|
|
||||||
|
next if !zmMemVerify( $monitor );
|
||||||
|
|
||||||
|
Debug( "Handling action '$action'\n" );
|
||||||
|
if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ ) {
|
||||||
|
my $state = $1;
|
||||||
|
my $delay = $2;
|
||||||
|
if ( $state eq 'enable' ) {
|
||||||
|
zmMonitorEnable( $monitor );
|
||||||
|
} else {
|
||||||
|
zmMonitorDisable( $monitor );
|
||||||
|
}
|
||||||
|
# Force a reload
|
||||||
|
$monitor_reload_time = 0;
|
||||||
|
Info( "Set monitor to $state\n" );
|
||||||
|
if ( $delay ) {
|
||||||
|
my $action_text = $id."|".( ($state eq 'enable')
|
||||||
|
? 'disable'
|
||||||
|
: 'enable'
|
||||||
|
);
|
||||||
|
handleDelay($delay, $connection, $action_text);
|
||||||
|
}
|
||||||
|
} elsif ( $action =~ /^(on|off)(?:[ \+](\d+))?$/ ) {
|
||||||
|
next if !$monitor->{Enabled};
|
||||||
|
|
||||||
|
my $trigger = $1;
|
||||||
|
my $delay = $2;
|
||||||
|
my $trigger_data;
|
||||||
|
if ( $trigger eq 'on' ) {
|
||||||
|
zmTriggerEventOn( $monitor, $score, $cause, $text );
|
||||||
|
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
|
||||||
|
Info( "Trigger '$trigger' '$cause'\n" );
|
||||||
|
if ( $delay ) {
|
||||||
|
my $action_text = $id.'|cancel';
|
||||||
|
handleDelay($delay, $connection, $action_text);
|
||||||
|
}
|
||||||
|
} elsif ( $trigger eq 'off' ) {
|
||||||
|
if ( $delay ) {
|
||||||
|
my $action_text = $id.'|off|0|'.$cause.'|'.$text;
|
||||||
|
handleDelay($delay, $connection, $action_text);
|
||||||
|
} else {
|
||||||
|
my $last_event = zmGetLastEvent( $monitor );
|
||||||
|
zmTriggerEventOff( $monitor );
|
||||||
|
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
|
||||||
|
Info( "Trigger '$trigger'\n" );
|
||||||
|
# Wait til it's finished
|
||||||
|
while( zmInAlarm( $monitor )
|
||||||
|
&& ($last_event == zmGetLastEvent( $monitor ))
|
||||||
|
) {
|
||||||
|
# Tenth of a second
|
||||||
|
usleep( 100000 );
|
||||||
|
}
|
||||||
|
zmTriggerEventCancel( $monitor );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elsif( $action eq 'cancel' ) {
|
||||||
|
zmTriggerEventCancel( $monitor );
|
||||||
|
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
|
||||||
|
Info( "Cancelled event\n" );
|
||||||
|
} elsif( $action eq 'show' ) {
|
||||||
|
zmTriggerShowtext( $monitor, $showtext );
|
||||||
|
Info( "Updated show text to '$showtext'\n" );
|
||||||
|
} else {
|
||||||
|
Error( "Unrecognised action '$action' in message '$message'\n" );
|
||||||
|
}
|
||||||
|
} # end sub handleMessage
|
||||||
|
|
||||||
|
sub handleDelay {
|
||||||
|
my $delay = shift;
|
||||||
|
my $connection = shift;
|
||||||
|
my $action_text = shift;
|
||||||
|
|
||||||
|
my $action_time = time()+$delay;
|
||||||
|
my $action_array = $actions{$action_time};
|
||||||
|
if ( !$action_array ) {
|
||||||
|
$action_array = $actions{$action_time} = [];
|
||||||
|
}
|
||||||
|
push @$action_array, {
|
||||||
|
connection => $connection,
|
||||||
|
message => $action_text
|
||||||
|
};
|
||||||
|
Debug( "Added timed event '$action_text', expires at $action_time (+$delay secs)\n" );
|
||||||
|
}
|
||||||
|
1;
|
||||||
|
__END__
|
||||||
|
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
|
|
||||||
zmtrigger.pl - ZoneMinder External Trigger Script
|
zmtrigger.pl - ZoneMinder External Trigger Script
|
||||||
|
@ -104,504 +526,6 @@ likely to be easier.
|
||||||
|
|
||||||
3|on+10|1|motion|text|showtext
|
3|on+10|1|motion|text|showtext
|
||||||
|
|
||||||
Triggers "alarm" on camera #3 for 10 seconds with score=1, cause="motion".
|
Triggers 'alarm' on camera #3 for 10 seconds with score=1, cause='motion'.
|
||||||
|
|
||||||
=cut
|
=cut
|
||||||
use strict;
|
|
||||||
use bytes;
|
|
||||||
|
|
||||||
# ==========================================================================
|
|
||||||
#
|
|
||||||
# User config
|
|
||||||
#
|
|
||||||
# ==========================================================================
|
|
||||||
|
|
||||||
use constant MAX_CONNECT_DELAY => 10;
|
|
||||||
use constant MONITOR_RELOAD_INTERVAL => 300;
|
|
||||||
use constant SELECT_TIMEOUT => 0.25;
|
|
||||||
|
|
||||||
# ==========================================================================
|
|
||||||
#
|
|
||||||
# Channel/Connection Modules
|
|
||||||
#
|
|
||||||
# ==========================================================================
|
|
||||||
|
|
||||||
@EXTRA_PERL_LIB@
|
|
||||||
use ZoneMinder;
|
|
||||||
use ZoneMinder::Trigger::Channel::Inet;
|
|
||||||
use ZoneMinder::Trigger::Channel::Unix;
|
|
||||||
use ZoneMinder::Trigger::Channel::Serial;
|
|
||||||
use ZoneMinder::Trigger::Connection;
|
|
||||||
|
|
||||||
my @connections;
|
|
||||||
push( @connections,
|
|
||||||
ZoneMinder::Trigger::Connection->new(
|
|
||||||
name=>"Chan1 TCP on port 6802",
|
|
||||||
channel=>ZoneMinder::Trigger::Channel::Inet->new( port=>6802 ),
|
|
||||||
mode=>"rw"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
push( @connections,
|
|
||||||
ZoneMinder::Trigger::Connection->new(
|
|
||||||
name=>"Chan2 Unix Socket at " . $Config{ZM_PATH_SOCKS}.'/zmtrigger.sock',
|
|
||||||
channel=>ZoneMinder::Trigger::Channel::Unix->new(
|
|
||||||
path=>$Config{ZM_PATH_SOCKS}.'/zmtrigger.sock'
|
|
||||||
),
|
|
||||||
mode=>"rw"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
#push( @connections, ZoneMinder::Trigger::Connection->new( name=>"Chan3", channel=>ZoneMinder::Trigger::Channel::File->new( path=>'/tmp/zmtrigger.out' ), mode=>"w" ) );
|
|
||||||
#push( @connections, ZoneMinder::Trigger::Connection->new( name=>"Chan4", channel=>ZoneMinder::Trigger::Channel::Serial->new( path=>'/dev/ttyS0' ), mode=>"rw" ) );
|
|
||||||
|
|
||||||
# ==========================================================================
|
|
||||||
#
|
|
||||||
# Don't change anything from here on down
|
|
||||||
#
|
|
||||||
# ==========================================================================
|
|
||||||
|
|
||||||
use DBI;
|
|
||||||
#use Socket;
|
|
||||||
use autouse 'Data::Dumper'=>qw(Dumper);
|
|
||||||
use POSIX qw( EINTR );
|
|
||||||
use Time::HiRes qw( usleep );
|
|
||||||
|
|
||||||
$| = 1;
|
|
||||||
|
|
||||||
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
|
|
||||||
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
|
||||||
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
|
||||||
|
|
||||||
logInit();
|
|
||||||
logSetSignal();
|
|
||||||
|
|
||||||
Info( "Trigger daemon starting\n" );
|
|
||||||
|
|
||||||
my $dbh = zmDbConnect();
|
|
||||||
|
|
||||||
my $base_rin = '';
|
|
||||||
foreach my $connection ( @connections )
|
|
||||||
{
|
|
||||||
Info( "Opening connection '$connection->{name}'\n" );
|
|
||||||
$connection->open();
|
|
||||||
}
|
|
||||||
|
|
||||||
my @in_select_connections = grep { $_->input() && $_->selectable() } @connections;
|
|
||||||
my @in_poll_connections = grep { $_->input() && !$_->selectable() } @connections;
|
|
||||||
my @out_connections = grep { $_->output() } @connections;
|
|
||||||
|
|
||||||
foreach my $connection ( @in_select_connections )
|
|
||||||
{
|
|
||||||
vec( $base_rin, $connection->fileno(), 1 ) = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
my %spawned_connections;
|
|
||||||
my %monitors;
|
|
||||||
my $monitor_reload_time = 0;
|
|
||||||
my $needsReload = 0;
|
|
||||||
loadMonitors();
|
|
||||||
|
|
||||||
|
|
||||||
$! = undef;
|
|
||||||
my $rin = '';
|
|
||||||
my $win = $rin;
|
|
||||||
my $ein = $win;
|
|
||||||
my $timeout = SELECT_TIMEOUT;
|
|
||||||
my %actions;
|
|
||||||
while( 1 )
|
|
||||||
{
|
|
||||||
$rin = $base_rin;
|
|
||||||
# Add the file descriptors of any spawned connections
|
|
||||||
foreach my $fileno ( keys(%spawned_connections) )
|
|
||||||
{
|
|
||||||
vec( $rin, $fileno, 1 ) = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $nfound = select( my $rout = $rin, undef, my $eout = $ein, $timeout );
|
|
||||||
if ( $nfound > 0 )
|
|
||||||
{
|
|
||||||
Debug( "Got input from $nfound connections\n" );
|
|
||||||
foreach my $connection ( @in_select_connections )
|
|
||||||
{
|
|
||||||
if ( vec( $rout, $connection->fileno(), 1 ) )
|
|
||||||
{
|
|
||||||
Debug( "Got input from connection "
|
|
||||||
.$connection->name()
|
|
||||||
." ("
|
|
||||||
.$connection->fileno()
|
|
||||||
.")\n"
|
|
||||||
);
|
|
||||||
if ( $connection->spawns() )
|
|
||||||
{
|
|
||||||
my $new_connection = $connection->accept();
|
|
||||||
$spawned_connections{$new_connection->fileno()} = $new_connection;
|
|
||||||
Debug( "Added new spawned connection ("
|
|
||||||
.$new_connection->fileno()
|
|
||||||
."), "
|
|
||||||
.int(keys(%spawned_connections))
|
|
||||||
." spawned connections\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
my $messages = $connection->getMessages();
|
|
||||||
if ( defined($messages) )
|
|
||||||
{
|
|
||||||
foreach my $message ( @$messages )
|
|
||||||
{
|
|
||||||
handleMessage( $connection, $message );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach my $connection ( values(%spawned_connections) )
|
|
||||||
{
|
|
||||||
if ( vec( $rout, $connection->fileno(), 1 ) )
|
|
||||||
{
|
|
||||||
Debug( "Got input from spawned connection "
|
|
||||||
.$connection->name()
|
|
||||||
." ("
|
|
||||||
.$connection->fileno()
|
|
||||||
.")\n"
|
|
||||||
);
|
|
||||||
my $messages = $connection->getMessages();
|
|
||||||
if ( defined($messages) )
|
|
||||||
{
|
|
||||||
foreach my $message ( @$messages )
|
|
||||||
{
|
|
||||||
handleMessage( $connection, $message );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
delete( $spawned_connections{$connection->fileno()} );
|
|
||||||
Debug( "Removed spawned connection ("
|
|
||||||
.$connection->fileno()
|
|
||||||
."), "
|
|
||||||
.int(keys(%spawned_connections))
|
|
||||||
." spawned connections\n"
|
|
||||||
);
|
|
||||||
$connection->close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elsif ( $nfound < 0 )
|
|
||||||
{
|
|
||||||
if ( $! == EINTR )
|
|
||||||
{
|
|
||||||
# Do nothing
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Fatal( "Can't select: $!" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check polled connections
|
|
||||||
foreach my $connection ( @in_poll_connections )
|
|
||||||
{
|
|
||||||
my $messages = $connection->getMessages();
|
|
||||||
if ( defined($messages) )
|
|
||||||
{
|
|
||||||
foreach my $message ( @$messages )
|
|
||||||
{
|
|
||||||
handleMessage( $connection, $message );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check for alarms that might have happened
|
|
||||||
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",
|
|
||||||
"shared_data:last_event"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
#print( "$monitor->{Id}: S:$state, LE:$last_event\n" );
|
|
||||||
#print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}\n" );
|
|
||||||
if ( $state == STATE_ALARM
|
|
||||||
|| $state == STATE_ALERT
|
|
||||||
) # In alarm state
|
|
||||||
{
|
|
||||||
if ( !defined($monitor->{LastEvent})
|
|
||||||
|| ($last_event != $monitor->{LastEvent})
|
|
||||||
) # A new event
|
|
||||||
{
|
|
||||||
push( @out_messages, $monitor->{Id}."|on|".time()."|".$last_event );
|
|
||||||
}
|
|
||||||
else # The same one as last time, so ignore it
|
|
||||||
{
|
|
||||||
# Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elsif ( ($state == STATE_IDLE
|
|
||||||
&& $monitor->{LastState} != STATE_IDLE
|
|
||||||
)
|
|
||||||
|| ($state == STATE_TAPE
|
|
||||||
&& $monitor->{LastState} != STATE_TAPE
|
|
||||||
)
|
|
||||||
) # Out of alarm state
|
|
||||||
{
|
|
||||||
push( @out_messages, $monitor->{Id}."|off|".time()."|".$last_event );
|
|
||||||
}
|
|
||||||
elsif ( defined($monitor->{LastEvent})
|
|
||||||
&& ($last_event != $monitor->{LastEvent})
|
|
||||||
) # We've missed a whole event
|
|
||||||
{
|
|
||||||
push( @out_messages, $monitor->{Id}."|on|".time()."|".$last_event );
|
|
||||||
push( @out_messages, $monitor->{Id}."|off|".time()."|".$last_event );
|
|
||||||
}
|
|
||||||
$monitor->{LastState} = $state;
|
|
||||||
$monitor->{LastEvent} = $last_event;
|
|
||||||
}
|
|
||||||
foreach my $connection ( @out_connections )
|
|
||||||
{
|
|
||||||
if ( $connection->canWrite() )
|
|
||||||
{
|
|
||||||
$connection->putMessages( \@out_messages );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach my $connection ( values(%spawned_connections) )
|
|
||||||
{
|
|
||||||
if ( $connection->canWrite() )
|
|
||||||
{
|
|
||||||
$connection->putMessages( \@out_messages );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug( "Checking for timed actions\n" )
|
|
||||||
if ( int(keys(%actions)) );
|
|
||||||
my $now = time();
|
|
||||||
foreach my $action_time ( sort( grep { $_ < $now } keys( %actions ) ) )
|
|
||||||
{
|
|
||||||
Info( "Found actions expiring at $action_time\n" );
|
|
||||||
foreach my $action ( @{$actions{$action_time}} )
|
|
||||||
{
|
|
||||||
my $connection = $action->{connection};
|
|
||||||
my $message = $action->{message};
|
|
||||||
Info( "Found action '$message'\n" );
|
|
||||||
handleMessage( $connection, $message );
|
|
||||||
}
|
|
||||||
delete( $actions{$action_time} );
|
|
||||||
}
|
|
||||||
|
|
||||||
# Allow connections to do their own timed actions
|
|
||||||
foreach my $connection ( @connections )
|
|
||||||
{
|
|
||||||
my $messages = $connection->timedActions();
|
|
||||||
if ( defined($messages) )
|
|
||||||
{
|
|
||||||
foreach my $message ( @$messages )
|
|
||||||
{
|
|
||||||
handleMessage( $connection, $message );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach my $connection ( values(%spawned_connections) )
|
|
||||||
{
|
|
||||||
my $messages = $connection->timedActions();
|
|
||||||
if ( defined($messages) )
|
|
||||||
{
|
|
||||||
foreach my $message ( @$messages )
|
|
||||||
{
|
|
||||||
handleMessage( $connection, $message );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# If necessary reload monitors
|
|
||||||
if ( $needsReload || ((time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ))
|
|
||||||
{
|
|
||||||
foreach my $monitor ( values(%monitors) )
|
|
||||||
{
|
|
||||||
# Free up any used memory handle
|
|
||||||
zmMemInvalidate( $monitor );
|
|
||||||
}
|
|
||||||
loadMonitors();
|
|
||||||
$needsReload = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Info( "Trigger daemon exiting\n" );
|
|
||||||
exit;
|
|
||||||
|
|
||||||
sub loadMonitors
|
|
||||||
{
|
|
||||||
Debug( "Loading monitors\n" );
|
|
||||||
$monitor_reload_time = time();
|
|
||||||
|
|
||||||
my %new_monitors = ();
|
|
||||||
|
|
||||||
my $sql = "SELECT * FROM Monitors
|
|
||||||
WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )".
|
|
||||||
( $Config{ZM_SERVER_ID} ? 'AND ServerId=?' : '' )
|
|
||||||
;
|
|
||||||
my $sth = $dbh->prepare_cached( $sql )
|
|
||||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
||||||
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
|
|
||||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
|
||||||
while( my $monitor = $sth->fetchrow_hashref() )
|
|
||||||
{
|
|
||||||
# Check shared memory ok
|
|
||||||
if ( !zmMemVerify( $monitor ) ) {
|
|
||||||
zmMemInvalidate( $monitor );
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( defined($monitors{$monitor->{Id}}->{LastState}) )
|
|
||||||
{
|
|
||||||
$monitor->{LastState} = $monitors{$monitor->{Id}}->{LastState};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$monitor->{LastState} = zmGetMonitorState( $monitor );
|
|
||||||
}
|
|
||||||
if ( defined($monitors{$monitor->{Id}}->{LastEvent}) )
|
|
||||||
{
|
|
||||||
$monitor->{LastEvent} = $monitors{$monitor->{Id}}->{LastEvent};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$monitor->{LastEvent} = zmGetLastEvent( $monitor );
|
|
||||||
}
|
|
||||||
$new_monitors{$monitor->{Id}} = $monitor;
|
|
||||||
}
|
|
||||||
%monitors = %new_monitors;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub handleMessage
|
|
||||||
{
|
|
||||||
my $connection = shift;
|
|
||||||
my $message = shift;
|
|
||||||
|
|
||||||
my ( $id, $action, $score, $cause, $text, $showtext )
|
|
||||||
= split( /\|/, $message );
|
|
||||||
$score = 0 if ( !defined($score) );
|
|
||||||
$cause = "" if ( !defined($cause) );
|
|
||||||
$text = "" if ( !defined($text) );
|
|
||||||
|
|
||||||
my $monitor = $monitors{$id};
|
|
||||||
if ( !$monitor )
|
|
||||||
{
|
|
||||||
Warning( "Can't find monitor '$id' for message '$message'\n" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Debug( "Found monitor for id '$id'\n" );
|
|
||||||
|
|
||||||
next if ( !zmMemVerify( $monitor ) );
|
|
||||||
|
|
||||||
Debug( "Handling action '$action'\n" );
|
|
||||||
if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ )
|
|
||||||
{
|
|
||||||
my $state = $1;
|
|
||||||
my $delay = $2;
|
|
||||||
if ( $state eq "enable" )
|
|
||||||
{
|
|
||||||
zmMonitorEnable( $monitor );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
zmMonitorDisable( $monitor );
|
|
||||||
}
|
|
||||||
# Force a reload
|
|
||||||
$monitor_reload_time = 0;
|
|
||||||
Info( "Set monitor to $state\n" );
|
|
||||||
if ( $delay )
|
|
||||||
{
|
|
||||||
my $action_text = $id."|".( ($state eq "enable")
|
|
||||||
? "disable"
|
|
||||||
: "enable"
|
|
||||||
);
|
|
||||||
handleDelay($delay, $connection, $action_text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elsif ( $action =~ /^(on|off)(?:[ \+](\d+))?$/ )
|
|
||||||
{
|
|
||||||
next if ( !$monitor->{Enabled} );
|
|
||||||
|
|
||||||
my $trigger = $1;
|
|
||||||
my $delay = $2;
|
|
||||||
my $trigger_data;
|
|
||||||
if ( $trigger eq "on" )
|
|
||||||
{
|
|
||||||
zmTriggerEventOn( $monitor, $score, $cause, $text );
|
|
||||||
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
|
|
||||||
Info( "Trigger '$trigger' '$cause'\n" );
|
|
||||||
if ( $delay )
|
|
||||||
{
|
|
||||||
my $action_text = $id."|cancel";
|
|
||||||
handleDelay($delay, $connection, $action_text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elsif ( $trigger eq "off" )
|
|
||||||
{
|
|
||||||
if ( $delay )
|
|
||||||
{
|
|
||||||
my $action_text = $id."|off|0|".$cause."|".$text;
|
|
||||||
handleDelay($delay, $connection, $action_text);
|
|
||||||
} else {
|
|
||||||
my $last_event = zmGetLastEvent( $monitor );
|
|
||||||
zmTriggerEventOff( $monitor );
|
|
||||||
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
|
|
||||||
Info( "Trigger '$trigger'\n" );
|
|
||||||
# Wait til it's finished
|
|
||||||
while( zmInAlarm( $monitor )
|
|
||||||
&& ($last_event == zmGetLastEvent( $monitor ))
|
|
||||||
)
|
|
||||||
{
|
|
||||||
# Tenth of a second
|
|
||||||
usleep( 100000 );
|
|
||||||
}
|
|
||||||
zmTriggerEventCancel( $monitor );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elsif( $action eq "cancel" )
|
|
||||||
{
|
|
||||||
zmTriggerEventCancel( $monitor );
|
|
||||||
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
|
|
||||||
Info( "Cancelled event\n" );
|
|
||||||
}
|
|
||||||
elsif( $action eq "show" )
|
|
||||||
{
|
|
||||||
zmTriggerShowtext( $monitor, $showtext );
|
|
||||||
Info( "Updated show text to '$showtext'\n" );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Error( "Unrecognised action '$action' in message '$message'\n" );
|
|
||||||
}
|
|
||||||
} # end sub handleMessage
|
|
||||||
|
|
||||||
sub handleDelay
|
|
||||||
{
|
|
||||||
my $delay = shift;
|
|
||||||
my $connection = shift;
|
|
||||||
my $action_text = shift;
|
|
||||||
|
|
||||||
my $action_time = time()+$delay;
|
|
||||||
my $action_array = $actions{$action_time};
|
|
||||||
if ( !$action_array )
|
|
||||||
{
|
|
||||||
$action_array = $actions{$action_time} = [];
|
|
||||||
}
|
|
||||||
push( @$action_array, { connection=>$connection,
|
|
||||||
message=>$action_text
|
|
||||||
}
|
|
||||||
);
|
|
||||||
Debug( "Added timed event '$action_text', expires at $action_time (+$delay secs)\n" );
|
|
||||||
}
|
|
||||||
1;
|
|
||||||
__END__
|
|
||||||
|
|
|
@ -27,12 +27,14 @@ zmvideo.pl - ZoneMinder Video Creation Script
|
||||||
|
|
||||||
=head1 SYNOPSIS
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
zmvideo.pl [ -e <event_id>,--event=<event_id> | --filter=<filter name> ] [--format <format>]
|
zmvideo.pl [ -e <event_id>,--event=<event_id> | --filter_name=<filter name> | --filter_id=<filter id> ]
|
||||||
[--rate=<rate>]
|
[--concat=filename]
|
||||||
[--scale=<scale>]
|
[--format <format>]
|
||||||
[--fps=<fps>]
|
[--rate=<rate>]
|
||||||
[--size=<size>]
|
[--scale=<scale>]
|
||||||
[--overwrite]
|
[--fps=<fps>]
|
||||||
|
[--size=<size>]
|
||||||
|
[--overwrite]
|
||||||
|
|
||||||
=head1 DESCRIPTION
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
@ -94,20 +96,11 @@ my $size = '';
|
||||||
my $overwrite = 0;
|
my $overwrite = 0;
|
||||||
my $version = 0;
|
my $version = 0;
|
||||||
|
|
||||||
my @formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} );
|
|
||||||
for ( my $i = 0; $i < @formats; $i++ )
|
|
||||||
{
|
|
||||||
if ( $i =~ /^(.+)\*$/ )
|
|
||||||
{
|
|
||||||
$format = $formats[$i] = $1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GetOptions(
|
GetOptions(
|
||||||
'concat|c:s' =>\$concat_name,
|
'concat|c:s' =>\$concat_name,
|
||||||
'event|e=i' =>\$event_id,
|
'event|e=i' =>\$event_id,
|
||||||
'filter_name=s' =>\$filter_name,
|
'filter_name=s' =>\$filter_name,
|
||||||
'filter_id=i' =>\$filter_id,
|
'filter_id=i' =>\$filter_id,
|
||||||
'format|f=s' =>\$format,
|
'format|f=s' =>\$format,
|
||||||
'rate|r=f' =>\$rate,
|
'rate|r=f' =>\$rate,
|
||||||
'scale|s=f' =>\$scale,
|
'scale|s=f' =>\$scale,
|
||||||
|
@ -115,51 +108,51 @@ GetOptions(
|
||||||
'size|S=s' =>\$size,
|
'size|S=s' =>\$size,
|
||||||
'overwrite' =>\$overwrite,
|
'overwrite' =>\$overwrite,
|
||||||
'version' =>\$version
|
'version' =>\$version
|
||||||
) or pod2usage(-exitstatus => -1);
|
) or pod2usage(-exitstatus => -1);
|
||||||
|
|
||||||
if ( $version ) {
|
if ( $version ) {
|
||||||
print ZoneMinder::Base::ZM_VERSION . "\n";
|
print ZoneMinder::Base::ZM_VERSION . "\n";
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !( $filter_id or $filter_name or $event_id ) || $event_id < 0 )
|
if ( !( $filter_id or $filter_name or $event_id ) || ($event_id and ( $event_id < 0 ) ) ) {
|
||||||
{
|
print( STDERR "Please give a valid event id or filter name\n" );
|
||||||
print( STDERR "Please give a valid event id or filter name\n" );
|
pod2usage(-exitstatus => -1);
|
||||||
pod2usage(-exitstatus => -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! $Config{ZM_OPT_FFMPEG} )
|
if ( ! $Config{ZM_OPT_FFMPEG} ) {
|
||||||
{
|
print( STDERR "Mpeg encoding is not currently enabled\n" );
|
||||||
print( STDERR "Mpeg encoding is not currently enabled\n" );
|
exit(-1);
|
||||||
exit(-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !$rate && !$fps )
|
my @formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} );
|
||||||
{
|
for ( my $i = 0; $i < @formats; $i++ ) {
|
||||||
$rate = 1;
|
if ( $i =~ /^(.+)\*$/ ) {
|
||||||
|
$format = $formats[$i] = $1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !$scale && !$size )
|
if ( !$rate && !$fps ) {
|
||||||
{
|
$rate = 1;
|
||||||
$scale = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $rate && ($rate < 0.25 || $rate > 100) )
|
if ( !$scale && !$size ) {
|
||||||
{
|
$scale = 1;
|
||||||
print( STDERR "Rate is out of range, 0.25 >= rate <= 100\n" );
|
|
||||||
pod2usage(-exitstatus => -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $scale && ($scale < 0.25 || $scale > 4) )
|
if ( $rate && ($rate < 0.25 || $rate > 100) ) {
|
||||||
{
|
print( STDERR "Rate is out of range, 0.25 >= rate <= 100\n" );
|
||||||
print( STDERR "Scale is out of range, 0.25 >= scale <= 4\n" );
|
pod2usage(-exitstatus => -1);
|
||||||
pod2usage(-exitstatus => -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $fps && ($fps > 30) )
|
if ( $scale && ($scale < 0.25 || $scale > 4) ) {
|
||||||
{
|
print( STDERR "Scale is out of range, 0.25 >= scale <= 4\n" );
|
||||||
print( STDERR "FPS is out of range, <= 30\n" );
|
pod2usage(-exitstatus => -1);
|
||||||
pod2usage(-exitstatus => -1);
|
}
|
||||||
|
|
||||||
|
if ( $fps && ($fps > 30) ) {
|
||||||
|
print( STDERR "FPS is out of range, <= 30\n" );
|
||||||
|
pod2usage(-exitstatus => -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
my ( $detaint_format ) = $format =~ /^(\w+)$/;
|
my ( $detaint_format ) = $format =~ /^(\w+)$/;
|
||||||
|
@ -181,79 +174,85 @@ my $cwd = getcwd;
|
||||||
my $video_name;
|
my $video_name;
|
||||||
my @event_ids;
|
my @event_ids;
|
||||||
if ( $event_id ) {
|
if ( $event_id ) {
|
||||||
@event_ids = ( $event_id );
|
@event_ids = ( $event_id );
|
||||||
|
|
||||||
} elsif ( $filter_name or $filter_id ) {
|
} elsif ( $filter_name or $filter_id ) {
|
||||||
my $Filter = ZoneMinder::Filter->find_one(
|
my $Filter = ZoneMinder::Filter->find_one(
|
||||||
($filter_name ? ( Name => $filter_name ) : () ),
|
($filter_name ? ( Name => $filter_name ) : () ),
|
||||||
($filter_id ? ( Id => $filter_name ) : () ),
|
($filter_id ? ( Id => $filter_name ) : () ),
|
||||||
);
|
);
|
||||||
if ( ! $Filter ) {
|
if ( ! $Filter ) {
|
||||||
Fatal("Filter $filter_name $filter_id not found.");
|
Fatal("Filter $filter_name $filter_id not found.");
|
||||||
}
|
}
|
||||||
@event_ids = map { $_->{Id} }$Filter->Execute();
|
@event_ids = map { $_->{Id} } $Filter->Execute();
|
||||||
Fatal( "No events found for $filter_name") if ! @event_ids;
|
if ( ! @event_ids ) {
|
||||||
$concat_name = $filter_name if $concat_name eq '';
|
Fatal( "No events found for $filter_name")
|
||||||
|
} else {
|
||||||
|
Debug(@event_ids . " events found for $filter_name");
|
||||||
|
}
|
||||||
|
$concat_name = $filter_name if $concat_name eq '';
|
||||||
}
|
}
|
||||||
|
|
||||||
my $sql = " SELECT max(F.Delta)-min(F.Delta) as FullLength,
|
my $sql = " SELECT (SELECT max(Delta) FROM Frames WHERE EventId=Events.Id)-(SELECT min(Delta) FROM Frames WHERE EventId=Events.Id) as FullLength,
|
||||||
E.*,
|
Events.*,
|
||||||
unix_timestamp(E.StartTime) as Time,
|
unix_timestamp(Events.StartTime) as Time,
|
||||||
M.Name as MonitorName,
|
M.Name as MonitorName,
|
||||||
M.Width as MonitorWidth,
|
M.Width as MonitorWidth,
|
||||||
M.Height as MonitorHeight,
|
M.Height as MonitorHeight,
|
||||||
M.Palette
|
M.Palette
|
||||||
FROM Frames as F
|
FROM Events
|
||||||
INNER JOIN Events as E on F.EventId = E.Id
|
INNER JOIN Monitors as M on Events.MonitorId = M.Id
|
||||||
INNER JOIN Monitors as M on E.MonitorId = M.Id
|
WHERE Events.Id = ?
|
||||||
WHERE EventId = ?
|
";
|
||||||
GROUP BY F.EventId"
|
|
||||||
;
|
|
||||||
my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||||
|
Debug($sql);
|
||||||
|
|
||||||
my @video_files;
|
my @video_files;
|
||||||
foreach my $event_id ( @event_ids ) {
|
foreach my $event_id ( @event_ids ) {
|
||||||
|
my $res = $sth->execute( $event_id )
|
||||||
|
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||||
|
my $event = $sth->fetchrow_hashref();
|
||||||
|
|
||||||
my $res = $sth->execute( $event_id )
|
my $Event = new ZoneMinder::Event( $$event{Id}, $event );
|
||||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
my $video_file = $Event->GenerateVideo( $rate, $fps, $scale, $size, $overwrite, $format );
|
||||||
my $event = $sth->fetchrow_hashref();
|
if ( $video_file ) {
|
||||||
|
push @video_files, $video_file;
|
||||||
my $Event = new ZoneMinder::Event( $$event{Id}, $event );
|
print( STDOUT $video_file."\n" );
|
||||||
my $video_file = $Event->GenerateVideo( $rate, $fps, $scale, $size, $overwrite, $format );
|
} else {
|
||||||
if ( $video_file ) {
|
Warning("No video file generated for event $event_id");
|
||||||
push @video_files, $video_file;
|
}
|
||||||
print( STDOUT $video_file."\n" );
|
|
||||||
}
|
|
||||||
} # end foreach event_id
|
} # end foreach event_id
|
||||||
|
|
||||||
if ( $concat_name ) {
|
if ( $concat_name ) {
|
||||||
($cwd) = $cwd =~ /(.*)/; # detaint
|
($cwd) = $cwd =~ /(.*)/; # detaint
|
||||||
chdir( $cwd );
|
chdir( $cwd );
|
||||||
($concat_name ) = $concat_name =~ /([\-A-Za-z0-9_\.]*)/;
|
($concat_name ) = $concat_name =~ /([\-A-Za-z0-9_\.]*)/;
|
||||||
my $concat_list_file = "/tmp/$concat_name.concat.lst";
|
my $concat_list_file = "/tmp/$concat_name.concat.lst";
|
||||||
|
|
||||||
my $video_file = $concat_name . '.'. $detaint_format;
|
my $video_file = $concat_name . '.'. $detaint_format;
|
||||||
|
|
||||||
open( my $fd, '>', $concat_list_file ) or die "Can't open $concat_list_file: $!";
|
open( my $fd, '>', $concat_list_file ) or die "Can't open $concat_list_file: $!";
|
||||||
foreach ( @video_files ) {
|
foreach ( @video_files ) {
|
||||||
print $fd "file '$_'\n";
|
print $fd "file '$_'\n";
|
||||||
}
|
}
|
||||||
close $fd;
|
close $fd;
|
||||||
my $command = $Config{ZM_PATH_FFMPEG}
|
my $command = $Config{ZM_PATH_FFMPEG}
|
||||||
. " -f concat -i $concat_list_file -c copy "
|
. " -f concat -safe 0 -i $concat_list_file -c copy "
|
||||||
." '$video_file' > ffmpeg.log 2>&1"
|
.$Config{ZM_FFMPEG_OUTPUT_OPTIONS}
|
||||||
;
|
." '$video_file' > $Config{ZM_PATH_LOGS}/ffmpeg_${concat_name}.log 2>&1"
|
||||||
Debug( $command."\n" );
|
;
|
||||||
my $output = qx($command);
|
Debug( $command."\n" );
|
||||||
|
my $output = qx($command);
|
||||||
|
|
||||||
my $status = $? >> 8;
|
my $status = $? >> 8;
|
||||||
|
|
||||||
unlink $concat_list_file;
|
unlink $concat_list_file;
|
||||||
if ( $status )
|
if ( $status ) {
|
||||||
{
|
Error( "Unable to generate video, check $Config{ZM_PATH_LOGS}/ffmpeg_${concat_name}.log for details");
|
||||||
Error( "Unable to generate video, check /ffmpeg.log for details");
|
exit(-1);
|
||||||
exit(-1);
|
}
|
||||||
}
|
|
||||||
print( STDOUT $video_file."\n" );
|
print( STDOUT $video_file."\n" );
|
||||||
}
|
}
|
||||||
exit( 0 );
|
exit( 0 );
|
||||||
|
|
||||||
|
__END__
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY)
|
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)
|
# 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_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)
|
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_eventstream.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.
|
# A fix for cmake recompiling the source files for every target.
|
||||||
add_library(zm STATIC ${ZM_BIN_SRC_FILES})
|
add_library(zm STATIC ${ZM_BIN_SRC_FILES})
|
||||||
|
|
|
@ -150,6 +150,12 @@ void process_configfile( char* configFile) {
|
||||||
staticConfig.DB_USER = std::string(val_ptr);
|
staticConfig.DB_USER = std::string(val_ptr);
|
||||||
else if ( strcasecmp( name_ptr, "ZM_DB_PASS" ) == 0 )
|
else if ( strcasecmp( name_ptr, "ZM_DB_PASS" ) == 0 )
|
||||||
staticConfig.DB_PASS = std::string(val_ptr);
|
staticConfig.DB_PASS = std::string(val_ptr);
|
||||||
|
else if ( strcasecmp( name_ptr, "ZM_DB_SSL_CA_CERT" ) == 0 )
|
||||||
|
staticConfig.DB_SSL_CA_CERT = std::string(val_ptr);
|
||||||
|
else if ( strcasecmp( name_ptr, "ZM_DB_SSL_CLIENT_KEY" ) == 0 )
|
||||||
|
staticConfig.DB_SSL_CLIENT_KEY = std::string(val_ptr);
|
||||||
|
else if ( strcasecmp( name_ptr, "ZM_DB_SSL_CLIENT_CERT" ) == 0 )
|
||||||
|
staticConfig.DB_SSL_CLIENT_CERT = std::string(val_ptr);
|
||||||
else if ( strcasecmp( name_ptr, "ZM_PATH_WEB" ) == 0 )
|
else if ( strcasecmp( name_ptr, "ZM_PATH_WEB" ) == 0 )
|
||||||
staticConfig.PATH_WEB = std::string(val_ptr);
|
staticConfig.PATH_WEB = std::string(val_ptr);
|
||||||
else if ( strcasecmp( name_ptr, "ZM_SERVER_HOST" ) == 0 )
|
else if ( strcasecmp( name_ptr, "ZM_SERVER_HOST" ) == 0 )
|
||||||
|
|
|
@ -67,6 +67,9 @@ struct StaticConfig
|
||||||
std::string DB_NAME;
|
std::string DB_NAME;
|
||||||
std::string DB_USER;
|
std::string DB_USER;
|
||||||
std::string DB_PASS;
|
std::string DB_PASS;
|
||||||
|
std::string DB_SSL_CA_CERT;
|
||||||
|
std::string DB_SSL_CLIENT_KEY;
|
||||||
|
std::string DB_SSL_CLIENT_CERT;
|
||||||
std::string PATH_WEB;
|
std::string PATH_WEB;
|
||||||
std::string SERVER_NAME;
|
std::string SERVER_NAME;
|
||||||
unsigned int SERVER_ID;
|
unsigned int SERVER_ID;
|
||||||
|
|
|
@ -37,6 +37,8 @@ void zmDbConnect()
|
||||||
my_bool reconnect = 1;
|
my_bool reconnect = 1;
|
||||||
if ( mysql_options( &dbconn, MYSQL_OPT_RECONNECT, &reconnect ) )
|
if ( mysql_options( &dbconn, MYSQL_OPT_RECONNECT, &reconnect ) )
|
||||||
Fatal( "Can't set database auto reconnect option: %s", mysql_error( &dbconn ) );
|
Fatal( "Can't set database auto reconnect option: %s", mysql_error( &dbconn ) );
|
||||||
|
if ( !staticConfig.DB_SSL_CA_CERT.empty() )
|
||||||
|
mysql_ssl_set( &dbconn, staticConfig.DB_SSL_CLIENT_KEY.c_str(), staticConfig.DB_SSL_CLIENT_CERT.c_str(), staticConfig.DB_SSL_CA_CERT.c_str(), NULL, NULL );
|
||||||
std::string::size_type colonIndex = staticConfig.DB_HOST.find( ":" );
|
std::string::size_type colonIndex = staticConfig.DB_HOST.find( ":" );
|
||||||
if ( colonIndex == std::string::npos )
|
if ( colonIndex == std::string::npos )
|
||||||
{
|
{
|
||||||
|
|
783
src/zm_event.cpp
783
src/zm_event.cpp
|
@ -31,21 +31,10 @@
|
||||||
#include "zm.h"
|
#include "zm.h"
|
||||||
#include "zm_db.h"
|
#include "zm_db.h"
|
||||||
#include "zm_time.h"
|
#include "zm_time.h"
|
||||||
#include "zm_mpeg.h"
|
|
||||||
#include "zm_signal.h"
|
#include "zm_signal.h"
|
||||||
#include "zm_event.h"
|
#include "zm_event.h"
|
||||||
#include "zm_monitor.h"
|
#include "zm_monitor.h"
|
||||||
|
|
||||||
// sendfile tricks
|
|
||||||
extern "C"
|
|
||||||
{
|
|
||||||
#include "zm_sendfile.h"
|
|
||||||
}
|
|
||||||
|
|
||||||
#if HAVE_SYS_SENDFILE_H
|
|
||||||
#include <sys/sendfile.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//#define USE_PREPARED_SQL 1
|
//#define USE_PREPARED_SQL 1
|
||||||
|
|
||||||
bool Event::initialised = false;
|
bool Event::initialised = false;
|
||||||
|
@ -583,775 +572,3 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
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 ) ) {
|
|
||||||
Error( "Can't run query: %s", mysql_error( &dbconn ) );
|
|
||||||
exit( mysql_errno( &dbconn ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
MYSQL_RES *result = mysql_store_result( &dbconn );
|
|
||||||
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 ) ) {
|
|
||||||
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
|
|
||||||
exit( mysql_errno( &dbconn ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
int init_event_id = atoi( dbrow[0] );
|
|
||||||
|
|
||||||
mysql_free_result( result );
|
|
||||||
|
|
||||||
loadEventData( init_event_id );
|
|
||||||
|
|
||||||
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++ ) {
|
|
||||||
//Info( "eft %d > et %d", 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 );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Debug( 3, "Skipping %ld frames", event_data->frame_count );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EventStream::loadInitialEventData( int init_event_id, unsigned int init_frame_id ) {
|
|
||||||
loadEventData( init_event_id );
|
|
||||||
|
|
||||||
if ( init_frame_id ) {
|
|
||||||
curr_stream_time = event_data->frames[init_frame_id-1].timestamp;
|
|
||||||
curr_frame_id = init_frame_id;
|
|
||||||
} else {
|
|
||||||
curr_stream_time = event_data->start_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
return( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EventStream::loadEventData( int event_id ) {
|
|
||||||
static char sql[ZM_SQL_MED_BUFSIZ];
|
|
||||||
|
|
||||||
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 ) ) {
|
|
||||||
Error( "Can't run query: %s", mysql_error( &dbconn ) );
|
|
||||||
exit( mysql_errno( &dbconn ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
MYSQL_RES *result = mysql_store_result( &dbconn );
|
|
||||||
if ( !result ) {
|
|
||||||
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
|
|
||||||
exit( mysql_errno( &dbconn ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ) ) {
|
|
||||||
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
|
|
||||||
exit( mysql_errno( &dbconn ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
delete event_data;
|
|
||||||
event_data = new EventData;
|
|
||||||
event_data->event_id = event_id;
|
|
||||||
event_data->monitor_id = atoi( dbrow[0] );
|
|
||||||
event_data->start_time = atoi(dbrow[2]);
|
|
||||||
if ( config.use_deep_storage ) {
|
|
||||||
struct tm *event_time = localtime( &event_data->start_time );
|
|
||||||
if ( staticConfig.DIR_EVENTS.c_str()[0] == '/' )
|
|
||||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", staticConfig.DIR_EVENTS.c_str(), 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(), staticConfig.DIR_EVENTS.c_str(), 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 {
|
|
||||||
if ( staticConfig.DIR_EVENTS.c_str()[0] == '/' )
|
|
||||||
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%ld", staticConfig.DIR_EVENTS.c_str(), 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(), staticConfig.DIR_EVENTS.c_str(), event_data->monitor_id, event_data->event_id );
|
|
||||||
}
|
|
||||||
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 ) ) {
|
|
||||||
Error( "Can't run query: %s", mysql_error( &dbconn ) );
|
|
||||||
exit( mysql_errno( &dbconn ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
result = mysql_store_result( &dbconn );
|
|
||||||
if ( !result ) {
|
|
||||||
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
|
|
||||||
exit( mysql_errno( &dbconn ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
event_data->n_frames = mysql_num_rows( result );
|
|
||||||
|
|
||||||
event_data->frames = new FrameData[event_data->frame_count];
|
|
||||||
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 ) ) ) {
|
|
||||||
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++ ) {
|
|
||||||
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;
|
|
||||||
event_data->frames[i-1].in_db = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
event_data->frames[id-1].timestamp = timestamp;
|
|
||||||
event_data->frames[id-1].offset = (time_t)(event_data->frames[id-1].timestamp-event_data->start_time);
|
|
||||||
event_data->frames[id-1].delta = id>1?frame_delta:0.0;
|
|
||||||
event_data->frames[id-1].in_db = true;
|
|
||||||
last_id = id;
|
|
||||||
last_delta = delta;
|
|
||||||
last_timestamp = timestamp;
|
|
||||||
}
|
|
||||||
if ( mysql_errno( &dbconn ) ) {
|
|
||||||
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
|
|
||||||
exit( mysql_errno( &dbconn ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
//for ( int i = 0; i < 250; i++ )
|
|
||||||
//{
|
|
||||||
//Info( "%d -> %d @ %f (%d)", i+1, event_data->frames[i].timestamp, event_data->frames[i].delta, event_data->frames[i].in_db );
|
|
||||||
//}
|
|
||||||
|
|
||||||
mysql_free_result( result );
|
|
||||||
|
|
||||||
if ( forceEventChange || mode == MODE_ALL_GAPLESS ) {
|
|
||||||
if ( replay_rate > 0 )
|
|
||||||
curr_stream_time = event_data->frames[0].timestamp;
|
|
||||||
else
|
|
||||||
curr_stream_time = event_data->frames[event_data->frame_count-1].timestamp;
|
|
||||||
}
|
|
||||||
Debug( 2, "Event:%ld, Frames:%ld, Duration: %.2f", event_data->event_id, event_data->frame_count, event_data->duration );
|
|
||||||
|
|
||||||
return( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
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] ) {
|
|
||||||
case CMD_PAUSE :
|
|
||||||
{
|
|
||||||
Debug( 1, "Got PAUSE command" );
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) ) {
|
|
||||||
Debug(1, "Was in single_mode, and last frame, so jumping to 1st frame");
|
|
||||||
curr_frame_id = 1;
|
|
||||||
} else {
|
|
||||||
Debug(1, "mode is %s, current frame is %d, frame count is %d", (mode == MODE_SINGLE ? "single" : "not single" ), curr_frame_id, event_data->frame_count );
|
|
||||||
}
|
|
||||||
|
|
||||||
replay_rate = ZM_RATE_BASE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CMD_VARPLAY :
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
case CMD_STOP :
|
|
||||||
{
|
|
||||||
Debug( 1, "Got STOP command" );
|
|
||||||
|
|
||||||
// Clear paused flag
|
|
||||||
paused = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CMD_FASTFWD :
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
case CMD_FASTREV :
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
send_frame = true;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
send_frame = true;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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 );
|
|
||||||
send_frame = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CMD_QUERY :
|
|
||||||
{
|
|
||||||
Debug( 1, "Got QUERY command, sending STATUS" );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CMD_QUIT :
|
|
||||||
{
|
|
||||||
Info ("User initiated exit - CMD_QUIT");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default :
|
|
||||||
{
|
|
||||||
// Do nothing, for now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
struct {
|
|
||||||
int event;
|
|
||||||
int progress;
|
|
||||||
int rate;
|
|
||||||
int zoom;
|
|
||||||
bool paused;
|
|
||||||
} status_data;
|
|
||||||
|
|
||||||
status_data.event = event_data->event_id;
|
|
||||||
status_data.progress = (int)event_data->frames[curr_frame_id-1].offset;
|
|
||||||
status_data.rate = replay_rate;
|
|
||||||
status_data.zoom = zoom;
|
|
||||||
status_data.paused = paused;
|
|
||||||
Debug( 2, "Event:%d, Paused:%d, progress:%d Rate:%d, Zoom:%d",
|
|
||||||
status_data.event,
|
|
||||||
status_data.paused,
|
|
||||||
status_data.progress,
|
|
||||||
status_data.rate,
|
|
||||||
status_data.zoom
|
|
||||||
);
|
|
||||||
|
|
||||||
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 ( errno != EAGAIN )
|
|
||||||
{
|
|
||||||
Error( "Can't sendto on sd %d: %s", sd, strerror(errno) );
|
|
||||||
exit( -1 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// quit after sending a status, if this was a quit request
|
|
||||||
if ((MsgCommand)msg->msg_data[0]==CMD_QUIT)
|
|
||||||
exit(0);
|
|
||||||
|
|
||||||
updateFrameRate( (double)event_data->frame_count/event_data->duration );
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventStream::checkEventLoaded() {
|
|
||||||
bool reload_event = false;
|
|
||||||
static char sql[ZM_SQL_SML_BUFSIZ];
|
|
||||||
|
|
||||||
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 ) {
|
|
||||||
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 ) {
|
|
||||||
//Info( "SQL:%s", 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 ) {
|
|
||||||
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 ) ) {
|
|
||||||
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
|
|
||||||
exit( mysql_errno( &dbconn ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( dbrow ) {
|
|
||||||
int event_id = atoi(dbrow[0]);
|
|
||||||
Debug( 1, "Loading new event %d", event_id );
|
|
||||||
|
|
||||||
loadEventData( event_id );
|
|
||||||
|
|
||||||
Debug( 2, "Current frame id = %d", curr_frame_id );
|
|
||||||
if ( replay_rate < 0 )
|
|
||||||
curr_frame_id = event_data->frame_count;
|
|
||||||
else
|
|
||||||
curr_frame_id = 1;
|
|
||||||
Debug( 2, "New frame id = %d", curr_frame_id );
|
|
||||||
} else {
|
|
||||||
if ( curr_frame_id <= 0 )
|
|
||||||
curr_frame_id = 1;
|
|
||||||
else
|
|
||||||
curr_frame_id = event_data->frame_count;
|
|
||||||
paused = true;
|
|
||||||
}
|
|
||||||
mysql_free_result( result );
|
|
||||||
forceEventChange = false;
|
|
||||||
} else {
|
|
||||||
if ( curr_frame_id <= 0 )
|
|
||||||
curr_frame_id = 1;
|
|
||||||
else
|
|
||||||
curr_frame_id = event_data->frame_count;
|
|
||||||
paused = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 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 ) {
|
|
||||||
Image image( filepath );
|
|
||||||
|
|
||||||
Image *send_image = prepareImage( &image );
|
|
||||||
|
|
||||||
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
|
|
||||||
#endif // HAVE_LIBAVCODEC
|
|
||||||
{
|
|
||||||
static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE];
|
|
||||||
|
|
||||||
int img_buffer_size = 0;
|
|
||||||
uint8_t *img_buffer = temp_img_buffer;
|
|
||||||
|
|
||||||
bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE));
|
|
||||||
|
|
||||||
fprintf( stdout, "--ZoneMinderFrame\r\n" );
|
|
||||||
|
|
||||||
if ( type != STREAM_JPEG )
|
|
||||||
send_raw = false;
|
|
||||||
|
|
||||||
if ( send_raw ) {
|
|
||||||
fdj = fopen( filepath, "rb" );
|
|
||||||
if ( !fdj ) {
|
|
||||||
Error( "Can't open %s: %s", filepath, strerror(errno) );
|
|
||||||
return( false );
|
|
||||||
}
|
|
||||||
#if HAVE_SENDFILE
|
|
||||||
if( fstat(fileno(fdj),&filestat) < 0 ) {
|
|
||||||
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 );
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
Image image( filepath );
|
|
||||||
|
|
||||||
Image *send_image = prepareImage( &image );
|
|
||||||
|
|
||||||
switch( type ) {
|
|
||||||
case STREAM_JPEG :
|
|
||||||
send_image->EncodeJpeg( img_buffer, &img_buffer_size );
|
|
||||||
break;
|
|
||||||
case STREAM_ZIP :
|
|
||||||
#if HAVE_ZLIB_H
|
|
||||||
unsigned long zip_buffer_size;
|
|
||||||
send_image->Zip( img_buffer, &zip_buffer_size );
|
|
||||||
img_buffer_size = zip_buffer_size;
|
|
||||||
break;
|
|
||||||
#else
|
|
||||||
Error("zlib is required for zipped images. Falling back to raw image");
|
|
||||||
type = STREAM_RAW;
|
|
||||||
#endif // HAVE_ZLIB_H
|
|
||||||
case STREAM_RAW :
|
|
||||||
img_buffer = (uint8_t*)(send_image->Buffer());
|
|
||||||
img_buffer_size = send_image->Size();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Fatal( "Unexpected frame type %d", type );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch( type ) {
|
|
||||||
case STREAM_JPEG :
|
|
||||||
fprintf( stdout, "Content-Type: image/jpeg\r\n" );
|
|
||||||
break;
|
|
||||||
case STREAM_RAW :
|
|
||||||
fprintf( stdout, "Content-Type: image/x-rgb\r\n" );
|
|
||||||
break;
|
|
||||||
case STREAM_ZIP :
|
|
||||||
fprintf( stdout, "Content-Type: image/x-rgbz\r\n" );
|
|
||||||
break;
|
|
||||||
default :
|
|
||||||
Fatal( "Unexpected frame type %d", type );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf( stdout, "\r\n\r\n" );
|
|
||||||
fflush( stdout );
|
|
||||||
}
|
|
||||||
last_frame_sent = TV_2_FLOAT( now );
|
|
||||||
return( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventStream::runStream() {
|
|
||||||
Event::Initialise();
|
|
||||||
|
|
||||||
openComms();
|
|
||||||
|
|
||||||
checkInitialised();
|
|
||||||
|
|
||||||
updateFrameRate( (double)event_data->frame_count/event_data->duration );
|
|
||||||
|
|
||||||
if ( type == STREAM_JPEG )
|
|
||||||
fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" );
|
|
||||||
|
|
||||||
if ( !event_data ) {
|
|
||||||
sendTextFrame( "No event data found" );
|
|
||||||
exit( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
while( !zm_terminate ) {
|
|
||||||
gettimeofday( &now, NULL );
|
|
||||||
|
|
||||||
unsigned int delta_us = 0;
|
|
||||||
send_frame = false;
|
|
||||||
|
|
||||||
// commands may set send_frame to true
|
|
||||||
while(checkCommandQueue());
|
|
||||||
|
|
||||||
if ( step != 0 )
|
|
||||||
curr_frame_id += step;
|
|
||||||
|
|
||||||
checkEventLoaded();
|
|
||||||
|
|
||||||
// Get current frame data
|
|
||||||
FrameData *frame_data = &event_data->frames[curr_frame_id-1];
|
|
||||||
|
|
||||||
//Info( "cst:%.2f", curr_stream_time );
|
|
||||||
//Info( "cfid:%d", curr_frame_id );
|
|
||||||
//Info( "fdt:%d", frame_data->timestamp );
|
|
||||||
if ( ! paused ) {
|
|
||||||
bool in_event = true;
|
|
||||||
double time_to_event = 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 ) {
|
|
||||||
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 ) {
|
|
||||||
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
|
|
||||||
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 ) )
|
|
||||||
zm_terminate = true;
|
|
||||||
}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
usleep( STREAM_PAUSE_WAIT );
|
|
||||||
//curr_stream_time += (replay_rate>0?1:-1) * ((1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000));
|
|
||||||
curr_stream_time += (1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000);
|
|
||||||
//}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Figure out if we should send this frame
|
|
||||||
// If we are streaming and this frame is due to be sent
|
|
||||||
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);
|
|
||||||
send_frame = true;
|
|
||||||
}
|
|
||||||
} else if ( step != 0 ) {
|
|
||||||
// We are paused and are just stepping forward or backward one frame
|
|
||||||
step = 0;
|
|
||||||
send_frame = true;
|
|
||||||
} else if ( ! send_frame ) {
|
|
||||||
// We are paused, and doing nothing
|
|
||||||
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
|
|
||||||
if ( actual_delta_time > MAX_STREAM_DELAY ) {
|
|
||||||
// Send keepalive
|
|
||||||
Debug( 2, "Sending keepalive frame" );
|
|
||||||
send_frame = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( send_frame )
|
|
||||||
if ( !sendFrame( delta_us ) )
|
|
||||||
zm_terminate = true;
|
|
||||||
|
|
||||||
curr_stream_time = frame_data->timestamp;
|
|
||||||
|
|
||||||
if ( !paused ) {
|
|
||||||
curr_frame_id += replay_rate>0?1:-1;
|
|
||||||
if ( send_frame && type != STREAM_MPEG ) {
|
|
||||||
Debug( 3, "dUs: %d", delta_us );
|
|
||||||
if ( delta_us )
|
|
||||||
usleep( delta_us );
|
|
||||||
}
|
|
||||||
} 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;
|
|
||||||
#endif // HAVE_LIBAVCODEC
|
|
||||||
|
|
||||||
closeComms();
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,817 @@
|
||||||
|
//
|
||||||
|
// ZoneMinder Event Class 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
#include <sys/ipc.h>
|
||||||
|
#include <sys/msg.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <glob.h>
|
||||||
|
|
||||||
|
#include "zm.h"
|
||||||
|
#include "zm_db.h"
|
||||||
|
#include "zm_time.h"
|
||||||
|
#include "zm_mpeg.h"
|
||||||
|
#include "zm_signal.h"
|
||||||
|
#include "zm_event.h"
|
||||||
|
#include "zm_monitor.h"
|
||||||
|
|
||||||
|
// sendfile tricks
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#include "zm_sendfile.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ) ) {
|
||||||
|
Error( "Can't run query: %s", mysql_error( &dbconn ) );
|
||||||
|
exit( mysql_errno( &dbconn ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
MYSQL_RES *result = mysql_store_result( &dbconn );
|
||||||
|
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 ) ) {
|
||||||
|
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
|
||||||
|
exit( mysql_errno( &dbconn ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
int init_event_id = atoi( dbrow[0] );
|
||||||
|
|
||||||
|
mysql_free_result( result );
|
||||||
|
|
||||||
|
loadEventData( init_event_id );
|
||||||
|
|
||||||
|
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++ ) {
|
||||||
|
//Info( "eft %d > et %d", 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 );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Debug( 3, "Skipping %ld frames", event_data->frame_count );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EventStream::loadInitialEventData( int init_event_id, unsigned int init_frame_id ) {
|
||||||
|
loadEventData( init_event_id );
|
||||||
|
|
||||||
|
if ( init_frame_id ) {
|
||||||
|
curr_stream_time = event_data->frames[init_frame_id-1].timestamp;
|
||||||
|
curr_frame_id = init_frame_id;
|
||||||
|
} else {
|
||||||
|
curr_stream_time = event_data->start_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
return( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EventStream::loadEventData( int event_id ) {
|
||||||
|
static char sql[ZM_SQL_MED_BUFSIZ];
|
||||||
|
|
||||||
|
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 ) ) {
|
||||||
|
Error( "Can't run query: %s", mysql_error( &dbconn ) );
|
||||||
|
exit( mysql_errno( &dbconn ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
MYSQL_RES *result = mysql_store_result( &dbconn );
|
||||||
|
if ( !result ) {
|
||||||
|
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
|
||||||
|
exit( mysql_errno( &dbconn ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ) ) {
|
||||||
|
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
|
||||||
|
exit( mysql_errno( &dbconn ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
delete event_data;
|
||||||
|
event_data = new EventData;
|
||||||
|
event_data->event_id = event_id;
|
||||||
|
event_data->monitor_id = atoi( dbrow[0] );
|
||||||
|
event_data->start_time = atoi(dbrow[2]);
|
||||||
|
if ( config.use_deep_storage ) {
|
||||||
|
struct tm *event_time = localtime( &event_data->start_time );
|
||||||
|
if ( staticConfig.DIR_EVENTS.c_str()[0] == '/' )
|
||||||
|
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", staticConfig.DIR_EVENTS.c_str(), 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(), staticConfig.DIR_EVENTS.c_str(), 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 {
|
||||||
|
if ( staticConfig.DIR_EVENTS.c_str()[0] == '/' )
|
||||||
|
snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%ld", staticConfig.DIR_EVENTS.c_str(), 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(), staticConfig.DIR_EVENTS.c_str(), event_data->monitor_id, event_data->event_id );
|
||||||
|
}
|
||||||
|
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 ) ) {
|
||||||
|
Error( "Can't run query: %s", mysql_error( &dbconn ) );
|
||||||
|
exit( mysql_errno( &dbconn ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
result = mysql_store_result( &dbconn );
|
||||||
|
if ( !result ) {
|
||||||
|
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
|
||||||
|
exit( mysql_errno( &dbconn ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
event_data->n_frames = mysql_num_rows( result );
|
||||||
|
|
||||||
|
event_data->frames = new FrameData[event_data->frame_count];
|
||||||
|
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 ) ) ) {
|
||||||
|
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++ ) {
|
||||||
|
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;
|
||||||
|
event_data->frames[i-1].in_db = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event_data->frames[id-1].timestamp = timestamp;
|
||||||
|
event_data->frames[id-1].offset = (time_t)(event_data->frames[id-1].timestamp-event_data->start_time);
|
||||||
|
event_data->frames[id-1].delta = id>1?frame_delta:0.0;
|
||||||
|
event_data->frames[id-1].in_db = true;
|
||||||
|
last_id = id;
|
||||||
|
last_delta = delta;
|
||||||
|
last_timestamp = timestamp;
|
||||||
|
}
|
||||||
|
if ( mysql_errno( &dbconn ) ) {
|
||||||
|
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
|
||||||
|
exit( mysql_errno( &dbconn ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
//for ( int i = 0; i < 250; i++ )
|
||||||
|
//{
|
||||||
|
//Info( "%d -> %d @ %f (%d)", i+1, event_data->frames[i].timestamp, event_data->frames[i].delta, event_data->frames[i].in_db );
|
||||||
|
//}
|
||||||
|
|
||||||
|
mysql_free_result( result );
|
||||||
|
|
||||||
|
if ( forceEventChange || mode == MODE_ALL_GAPLESS ) {
|
||||||
|
if ( replay_rate > 0 )
|
||||||
|
curr_stream_time = event_data->frames[0].timestamp;
|
||||||
|
else
|
||||||
|
curr_stream_time = event_data->frames[event_data->frame_count-1].timestamp;
|
||||||
|
}
|
||||||
|
Debug( 2, "Event:%ld, Frames:%ld, Duration: %.2f", event_data->event_id, event_data->frame_count, event_data->duration );
|
||||||
|
|
||||||
|
return( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
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] ) {
|
||||||
|
case CMD_PAUSE :
|
||||||
|
{
|
||||||
|
Debug( 1, "Got PAUSE command" );
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
Debug(1, "Was in single_mode, and last frame, so jumping to 1st frame");
|
||||||
|
curr_frame_id = 1;
|
||||||
|
} else {
|
||||||
|
Debug(1, "mode is %s, current frame is %d, frame count is %d", (mode == MODE_SINGLE ? "single" : "not single" ), curr_frame_id, event_data->frame_count );
|
||||||
|
}
|
||||||
|
|
||||||
|
replay_rate = ZM_RATE_BASE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMD_VARPLAY :
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
case CMD_STOP :
|
||||||
|
{
|
||||||
|
Debug( 1, "Got STOP command" );
|
||||||
|
|
||||||
|
// Clear paused flag
|
||||||
|
paused = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMD_FASTFWD :
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
case CMD_FASTREV :
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
send_frame = true;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
send_frame = true;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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 );
|
||||||
|
send_frame = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMD_QUERY :
|
||||||
|
{
|
||||||
|
Debug( 1, "Got QUERY command, sending STATUS" );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMD_QUIT :
|
||||||
|
{
|
||||||
|
Info ("User initiated exit - CMD_QUIT");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default :
|
||||||
|
{
|
||||||
|
// Do nothing, for now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct {
|
||||||
|
int event;
|
||||||
|
int progress;
|
||||||
|
int rate;
|
||||||
|
int zoom;
|
||||||
|
bool paused;
|
||||||
|
} status_data;
|
||||||
|
|
||||||
|
status_data.event = event_data->event_id;
|
||||||
|
status_data.progress = (int)event_data->frames[curr_frame_id-1].offset;
|
||||||
|
status_data.rate = replay_rate;
|
||||||
|
status_data.zoom = zoom;
|
||||||
|
status_data.paused = paused;
|
||||||
|
Debug( 2, "Event:%d, Paused:%d, progress:%d Rate:%d, Zoom:%d",
|
||||||
|
status_data.event,
|
||||||
|
status_data.paused,
|
||||||
|
status_data.progress,
|
||||||
|
status_data.rate,
|
||||||
|
status_data.zoom
|
||||||
|
);
|
||||||
|
|
||||||
|
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 ( errno != EAGAIN )
|
||||||
|
{
|
||||||
|
Error( "Can't sendto on sd %d: %s", sd, strerror(errno) );
|
||||||
|
exit( -1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// quit after sending a status, if this was a quit request
|
||||||
|
if ((MsgCommand)msg->msg_data[0]==CMD_QUIT)
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
updateFrameRate( (double)event_data->frame_count/event_data->duration );
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventStream::checkEventLoaded() {
|
||||||
|
bool reload_event = false;
|
||||||
|
static char sql[ZM_SQL_SML_BUFSIZ];
|
||||||
|
|
||||||
|
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 ) {
|
||||||
|
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 ) {
|
||||||
|
//Info( "SQL:%s", 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 ) {
|
||||||
|
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 ) ) {
|
||||||
|
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
|
||||||
|
exit( mysql_errno( &dbconn ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( dbrow ) {
|
||||||
|
int event_id = atoi(dbrow[0]);
|
||||||
|
Debug( 1, "Loading new event %d", event_id );
|
||||||
|
|
||||||
|
loadEventData( event_id );
|
||||||
|
|
||||||
|
Debug( 2, "Current frame id = %d", curr_frame_id );
|
||||||
|
if ( replay_rate < 0 )
|
||||||
|
curr_frame_id = event_data->frame_count;
|
||||||
|
else
|
||||||
|
curr_frame_id = 1;
|
||||||
|
Debug( 2, "New frame id = %d", curr_frame_id );
|
||||||
|
} else {
|
||||||
|
if ( curr_frame_id <= 0 )
|
||||||
|
curr_frame_id = 1;
|
||||||
|
else
|
||||||
|
curr_frame_id = event_data->frame_count;
|
||||||
|
paused = true;
|
||||||
|
}
|
||||||
|
mysql_free_result( result );
|
||||||
|
forceEventChange = false;
|
||||||
|
} else {
|
||||||
|
if ( curr_frame_id <= 0 )
|
||||||
|
curr_frame_id = 1;
|
||||||
|
else
|
||||||
|
curr_frame_id = event_data->frame_count;
|
||||||
|
paused = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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 ) {
|
||||||
|
Image image( filepath );
|
||||||
|
|
||||||
|
Image *send_image = prepareImage( &image );
|
||||||
|
|
||||||
|
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
|
||||||
|
#endif // HAVE_LIBAVCODEC
|
||||||
|
{
|
||||||
|
static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE];
|
||||||
|
|
||||||
|
int img_buffer_size = 0;
|
||||||
|
uint8_t *img_buffer = temp_img_buffer;
|
||||||
|
|
||||||
|
bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE));
|
||||||
|
|
||||||
|
fprintf( stdout, "--ZoneMinderFrame\r\n" );
|
||||||
|
|
||||||
|
if ( type != STREAM_JPEG )
|
||||||
|
send_raw = false;
|
||||||
|
|
||||||
|
if ( send_raw ) {
|
||||||
|
fdj = fopen( filepath, "rb" );
|
||||||
|
if ( !fdj ) {
|
||||||
|
Error( "Can't open %s: %s", filepath, strerror(errno) );
|
||||||
|
return( false );
|
||||||
|
}
|
||||||
|
#if HAVE_SENDFILE
|
||||||
|
if( fstat(fileno(fdj),&filestat) < 0 ) {
|
||||||
|
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 );
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
Image image( filepath );
|
||||||
|
|
||||||
|
Image *send_image = prepareImage( &image );
|
||||||
|
|
||||||
|
switch( type ) {
|
||||||
|
case STREAM_JPEG :
|
||||||
|
send_image->EncodeJpeg( img_buffer, &img_buffer_size );
|
||||||
|
break;
|
||||||
|
case STREAM_ZIP :
|
||||||
|
#if HAVE_ZLIB_H
|
||||||
|
unsigned long zip_buffer_size;
|
||||||
|
send_image->Zip( img_buffer, &zip_buffer_size );
|
||||||
|
img_buffer_size = zip_buffer_size;
|
||||||
|
break;
|
||||||
|
#else
|
||||||
|
Error("zlib is required for zipped images. Falling back to raw image");
|
||||||
|
type = STREAM_RAW;
|
||||||
|
#endif // HAVE_ZLIB_H
|
||||||
|
case STREAM_RAW :
|
||||||
|
img_buffer = (uint8_t*)(send_image->Buffer());
|
||||||
|
img_buffer_size = send_image->Size();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Fatal( "Unexpected frame type %d", type );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch( type ) {
|
||||||
|
case STREAM_JPEG :
|
||||||
|
fprintf( stdout, "Content-Type: image/jpeg\r\n" );
|
||||||
|
break;
|
||||||
|
case STREAM_RAW :
|
||||||
|
fprintf( stdout, "Content-Type: image/x-rgb\r\n" );
|
||||||
|
break;
|
||||||
|
case STREAM_ZIP :
|
||||||
|
fprintf( stdout, "Content-Type: image/x-rgbz\r\n" );
|
||||||
|
break;
|
||||||
|
default :
|
||||||
|
Fatal( "Unexpected frame type %d", type );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf( stdout, "\r\n\r\n" );
|
||||||
|
fflush( stdout );
|
||||||
|
}
|
||||||
|
last_frame_sent = TV_2_FLOAT( now );
|
||||||
|
return( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventStream::runStream() {
|
||||||
|
Event::Initialise();
|
||||||
|
|
||||||
|
openComms();
|
||||||
|
|
||||||
|
checkInitialised();
|
||||||
|
|
||||||
|
updateFrameRate( (double)event_data->frame_count/event_data->duration );
|
||||||
|
|
||||||
|
if ( type == STREAM_JPEG )
|
||||||
|
fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" );
|
||||||
|
|
||||||
|
if ( !event_data ) {
|
||||||
|
sendTextFrame( "No event data found" );
|
||||||
|
exit( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
while( !zm_terminate ) {
|
||||||
|
gettimeofday( &now, NULL );
|
||||||
|
|
||||||
|
unsigned int delta_us = 0;
|
||||||
|
send_frame = false;
|
||||||
|
|
||||||
|
// commands may set send_frame to true
|
||||||
|
while(checkCommandQueue());
|
||||||
|
|
||||||
|
if ( step != 0 )
|
||||||
|
curr_frame_id += step;
|
||||||
|
|
||||||
|
checkEventLoaded();
|
||||||
|
|
||||||
|
// Get current frame data
|
||||||
|
FrameData *frame_data = &event_data->frames[curr_frame_id-1];
|
||||||
|
|
||||||
|
//Info( "cst:%.2f", curr_stream_time );
|
||||||
|
//Info( "cfid:%d", curr_frame_id );
|
||||||
|
//Info( "fdt:%d", frame_data->timestamp );
|
||||||
|
if ( !paused ) {
|
||||||
|
bool in_event = true;
|
||||||
|
double time_to_event = 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 ) {
|
||||||
|
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 ) {
|
||||||
|
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
|
||||||
|
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 ) )
|
||||||
|
zm_terminate = true;
|
||||||
|
}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
usleep( STREAM_PAUSE_WAIT );
|
||||||
|
//curr_stream_time += (replay_rate>0?1:-1) * ((1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000));
|
||||||
|
curr_stream_time += (1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000);
|
||||||
|
//}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out if we should send this frame
|
||||||
|
|
||||||
|
// If we are streaming and this frame is due to be sent
|
||||||
|
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);
|
||||||
|
send_frame = true;
|
||||||
|
}
|
||||||
|
} else if ( step != 0 ) {
|
||||||
|
// We are paused and are just stepping forward or backward one frame
|
||||||
|
step = 0;
|
||||||
|
send_frame = true;
|
||||||
|
} else if ( !send_frame ) {
|
||||||
|
// We are paused, and doing nothing
|
||||||
|
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent;
|
||||||
|
if ( actual_delta_time > MAX_STREAM_DELAY ) {
|
||||||
|
// Send keepalive
|
||||||
|
Debug( 2, "Sending keepalive frame" );
|
||||||
|
send_frame = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( send_frame )
|
||||||
|
if ( !sendFrame( delta_us ) )
|
||||||
|
zm_terminate = true;
|
||||||
|
|
||||||
|
curr_stream_time = frame_data->timestamp;
|
||||||
|
|
||||||
|
if ( !paused ) {
|
||||||
|
curr_frame_id += replay_rate>0?1:-1;
|
||||||
|
if ( send_frame && type != STREAM_MPEG ) {
|
||||||
|
Debug( 3, "dUs: %d", delta_us );
|
||||||
|
if ( delta_us )
|
||||||
|
usleep( delta_us );
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
|
#endif // HAVE_LIBAVCODEC
|
||||||
|
|
||||||
|
closeComms();
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
//
|
||||||
|
// ZoneMinder Core Interfaces, $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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ZM_EVENTSTREAM_H
|
||||||
|
#define ZM_EVENTSTREAM_H
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "zm_image.h"
|
||||||
|
#include "zm_stream.h"
|
||||||
|
#include "zm_video.h"
|
||||||
|
|
||||||
|
class EventStream : public StreamBase {
|
||||||
|
public:
|
||||||
|
typedef enum { MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct FrameData {
|
||||||
|
//unsigned long id;
|
||||||
|
time_t timestamp;
|
||||||
|
time_t offset;
|
||||||
|
double delta;
|
||||||
|
bool in_db;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
char video_file[PATH_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static const int STREAM_PAUSE_WAIT = 250000; // Microseconds
|
||||||
|
|
||||||
|
static const StreamMode DEFAULT_MODE = MODE_SINGLE;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
StreamMode mode;
|
||||||
|
bool forceEventChange;
|
||||||
|
|
||||||
|
int curr_frame_id;
|
||||||
|
double curr_stream_time;
|
||||||
|
|
||||||
|
EventData *event_data;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool loadEventData( int event_id );
|
||||||
|
bool loadInitialEventData( int init_event_id, unsigned int init_frame_id );
|
||||||
|
bool loadInitialEventData( int monitor_id, time_t event_time );
|
||||||
|
|
||||||
|
void checkEventLoaded();
|
||||||
|
void processCommand( const CmdMsg *msg );
|
||||||
|
bool sendFrame( int delta_us );
|
||||||
|
|
||||||
|
public:
|
||||||
|
EventStream() {
|
||||||
|
mode = DEFAULT_MODE;
|
||||||
|
|
||||||
|
forceEventChange = false;
|
||||||
|
|
||||||
|
curr_frame_id = 0;
|
||||||
|
curr_stream_time = 0.0;
|
||||||
|
|
||||||
|
event_data = 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 ) {
|
||||||
|
loadInitialEventData( monitor_id, event_time );
|
||||||
|
loadMonitor( monitor_id );
|
||||||
|
}
|
||||||
|
void setStreamMode( StreamMode p_mode ) {
|
||||||
|
mode = p_mode;
|
||||||
|
}
|
||||||
|
void runStream();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ZM_EVENTSTREAM_H
|
|
@ -32,8 +32,8 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
/* Workaround for GNU/kFreeBSD */
|
/* Workaround for GNU/kFreeBSD and FreeBSD */
|
||||||
#if defined(__FreeBSD_kernel__)
|
#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
|
||||||
#ifndef ENODATA
|
#ifndef ENODATA
|
||||||
#define ENODATA ENOATTR
|
#define ENODATA ENOATTR
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -195,7 +195,7 @@ void Logger::initialise( const std::string &id, const Options &options ) {
|
||||||
level( tempLevel );
|
level( tempLevel );
|
||||||
|
|
||||||
mFlush = false;
|
mFlush = false;
|
||||||
if (envPtr = getenv( "LOG_FLUSH")) {
|
if ( (envPtr = getenv("LOG_FLUSH")) ) {
|
||||||
mFlush = atoi( envPtr );
|
mFlush = atoi( envPtr );
|
||||||
} else if ( config.log_debug ) {
|
} else if ( config.log_debug ) {
|
||||||
mFlush = true;
|
mFlush = true;
|
||||||
|
@ -341,6 +341,8 @@ Logger::Level Logger::databaseLevel( Logger::Level databaseLevel ) {
|
||||||
my_bool reconnect = 1;
|
my_bool reconnect = 1;
|
||||||
if ( mysql_options( &mDbConnection, MYSQL_OPT_RECONNECT, &reconnect ) )
|
if ( mysql_options( &mDbConnection, MYSQL_OPT_RECONNECT, &reconnect ) )
|
||||||
Fatal( "Can't set database auto reconnect option: %s", mysql_error( &mDbConnection ) );
|
Fatal( "Can't set database auto reconnect option: %s", mysql_error( &mDbConnection ) );
|
||||||
|
if ( !staticConfig.DB_SSL_CA_CERT.empty() )
|
||||||
|
mysql_ssl_set( &mDbConnection, staticConfig.DB_SSL_CLIENT_KEY.c_str(), staticConfig.DB_SSL_CLIENT_CERT.c_str(), staticConfig.DB_SSL_CA_CERT.c_str(), NULL, NULL );
|
||||||
std::string::size_type colonIndex = staticConfig.DB_HOST.find( ":" );
|
std::string::size_type colonIndex = staticConfig.DB_HOST.find( ":" );
|
||||||
if ( colonIndex == std::string::npos ) {
|
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 ) ) {
|
if ( !mysql_real_connect( &mDbConnection, staticConfig.DB_HOST.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, NULL, 0 ) ) {
|
||||||
|
|
|
@ -24,6 +24,10 @@ extern "C" {
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
#include <sys/time.h>
|
||||||
|
#endif // __FreeBSD__
|
||||||
|
|
||||||
class ZMPacket {
|
class ZMPacket {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
#ifdef HAVE_SENDFILE4_SUPPORT
|
#ifdef HAVE_SENDFILE4_SUPPORT
|
||||||
#include <sys/sendfile.h>
|
#include <sys/sendfile.h>
|
||||||
int zm_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) {
|
int zm_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) {
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = sendfile(out_fd, in_fd, offset, size);
|
err = sendfile(out_fd, in_fd, offset, size);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return -errno;
|
return -errno;
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
#elif HAVE_SENDFILE7_SUPPORT
|
#elif HAVE_SENDFILE7_SUPPORT
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/uio.h>
|
#include <sys/uio.h>
|
||||||
int zm_sendfile(int out_fd, int in_fd, off_t *offset, off_t size) {
|
int zm_sendfile(int out_fd, int in_fd, off_t *offset, off_t size) {
|
||||||
int err;
|
int err;
|
||||||
err = sendfile(in_fd, out_fd, *offset, size, NULL, &size, 0);
|
err = sendfile(in_fd, out_fd, *offset, size, NULL, &size, 0);
|
||||||
if (err && errno != EAGAIN)
|
if (err && errno != EAGAIN)
|
||||||
return -errno;
|
return -errno;
|
||||||
|
|
||||||
if (size) {
|
if (size) {
|
||||||
*offset += size;
|
*offset += size;
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#error "Your platform does not support sendfile. Sorry."
|
#error "Your platform does not support sendfile. Sorry."
|
||||||
|
|
|
@ -63,13 +63,13 @@ RETSIGTYPE zm_die_handler(int signal)
|
||||||
ucontext_t *uc = (ucontext_t *) context;
|
ucontext_t *uc = (ucontext_t *) context;
|
||||||
cr2 = info->si_addr;
|
cr2 = info->si_addr;
|
||||||
#if defined(__x86_64__)
|
#if defined(__x86_64__)
|
||||||
#ifdef __FreeBSD_kernel__
|
#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
|
||||||
ip = (void *)(uc->uc_mcontext.mc_rip);
|
ip = (void *)(uc->uc_mcontext.mc_rip);
|
||||||
#else
|
#else
|
||||||
ip = (void *)(uc->uc_mcontext.gregs[REG_RIP]);
|
ip = (void *)(uc->uc_mcontext.gregs[REG_RIP]);
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
#ifdef __FreeBSD_kernel__
|
#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
|
||||||
ip = (void *)(uc->uc_mcontext.mc_eip);
|
ip = (void *)(uc->uc_mcontext.mc_eip);
|
||||||
#else
|
#else
|
||||||
ip = (void *)(uc->uc_mcontext.gregs[REG_EIP]);
|
ip = (void *)(uc->uc_mcontext.gregs[REG_EIP]);
|
||||||
|
|
|
@ -30,8 +30,7 @@ class Monitor;
|
||||||
|
|
||||||
#define TV_2_FLOAT( tv ) ( double((tv).tv_sec) + (double((tv).tv_usec) / 1000000.0) )
|
#define TV_2_FLOAT( tv ) ( double((tv).tv_sec) + (double((tv).tv_usec) / 1000000.0) )
|
||||||
|
|
||||||
class StreamBase
|
class StreamBase {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
typedef enum { STREAM_JPEG, STREAM_RAW, STREAM_ZIP, STREAM_SINGLE, STREAM_MPEG } StreamType;
|
typedef enum { STREAM_JPEG, STREAM_RAW, STREAM_ZIP, STREAM_SINGLE, STREAM_MPEG } StreamType;
|
||||||
|
|
||||||
|
@ -110,8 +109,7 @@ protected:
|
||||||
virtual void processCommand( const CmdMsg *msg )=0;
|
virtual void processCommand( const CmdMsg *msg )=0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StreamBase()
|
StreamBase() {
|
||||||
{
|
|
||||||
monitor = 0;
|
monitor = 0;
|
||||||
|
|
||||||
type = DEFAULT_TYPE;
|
type = DEFAULT_TYPE;
|
||||||
|
@ -145,32 +143,27 @@ public:
|
||||||
}
|
}
|
||||||
virtual ~StreamBase();
|
virtual ~StreamBase();
|
||||||
|
|
||||||
void setStreamType( StreamType p_type )
|
void setStreamType( StreamType p_type ) {
|
||||||
{
|
|
||||||
type = p_type;
|
type = p_type;
|
||||||
}
|
}
|
||||||
void setStreamFormat( const char *p_format )
|
void setStreamFormat( const char *p_format ) {
|
||||||
{
|
|
||||||
format = p_format;
|
format = p_format;
|
||||||
}
|
}
|
||||||
void setStreamScale( int p_scale )
|
void setStreamScale( int p_scale ) {
|
||||||
{
|
|
||||||
scale = p_scale;
|
scale = p_scale;
|
||||||
|
if ( ! scale )
|
||||||
|
scale = DEFAULT_SCALE;
|
||||||
}
|
}
|
||||||
void setStreamReplayRate( int p_rate )
|
void setStreamReplayRate( int p_rate ) {
|
||||||
{
|
|
||||||
replay_rate = p_rate;
|
replay_rate = p_rate;
|
||||||
}
|
}
|
||||||
void setStreamMaxFPS( double p_maxfps )
|
void setStreamMaxFPS( double p_maxfps ) {
|
||||||
{
|
|
||||||
maxfps = p_maxfps;
|
maxfps = p_maxfps;
|
||||||
}
|
}
|
||||||
void setStreamBitrate( int p_bitrate )
|
void setStreamBitrate( int p_bitrate ) {
|
||||||
{
|
|
||||||
bitrate = p_bitrate;
|
bitrate = p_bitrate;
|
||||||
}
|
}
|
||||||
void setStreamQueue( int p_connkey )
|
void setStreamQueue( int p_connkey ) {
|
||||||
{
|
|
||||||
connkey = p_connkey;
|
connkey = p_connkey;
|
||||||
}
|
}
|
||||||
virtual void openComms();
|
virtual void openComms();
|
||||||
|
|
849
src/zm_video.cpp
849
src/zm_video.cpp
|
@ -1,522 +1,577 @@
|
||||||
|
// Copyright (C) 2001-2017 ZoneMinder LLC
|
||||||
// This program is free software; you can redistribute it and/or
|
// This program is free software; you can redistribute it and/or
|
||||||
// modify it under the terms of the GNU General Public License
|
// modify it under the terms of the GNU General Public License
|
||||||
// as published by the Free Software Foundation; either version 2
|
// as published by the Free Software Foundation; either version 2
|
||||||
// of the License, or (at your option) any later version.
|
// of the License, or (at your option) any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program; if not, write to the Free Software
|
// 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
//
|
//
|
||||||
#include "zm.h"
|
#include "zm.h"
|
||||||
#include "zm_video.h"
|
#include "zm_video.h"
|
||||||
#include "zm_image.h"
|
#include "zm_image.h"
|
||||||
#include "zm_utils.h"
|
#include "zm_utils.h"
|
||||||
#include "zm_rgb.h"
|
#include "zm_rgb.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
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) :
|
VideoWriter::VideoWriter(
|
||||||
container(p_container), codec(p_codec), path(p_path), width(p_width), height(p_height), colours(p_colours), subpixelorder(p_subpixelorder), frame_count(0) {
|
const char* p_container,
|
||||||
Debug(7,"Video object created");
|
const char* p_codec,
|
||||||
|
const char* p_path,
|
||||||
/* Parameter checking */
|
const unsigned int p_width,
|
||||||
if(path.empty()) {
|
const unsigned int p_height,
|
||||||
Error("Invalid file path");
|
const unsigned int p_colours,
|
||||||
}
|
const unsigned int p_subpixelorder) :
|
||||||
if(!width || !height) {
|
container(p_container),
|
||||||
Error("Invalid width or height");
|
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() {
|
VideoWriter::~VideoWriter() {
|
||||||
Debug(7,"Video object destroyed");
|
Debug(7, "Video object destroyed");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int VideoWriter::Reset(const char* new_path) {
|
int VideoWriter::Reset(const char* new_path) {
|
||||||
/* Common variables reset */
|
/* Common variables reset */
|
||||||
|
|
||||||
/* If there is a new path, use it */
|
/* If there is a new path, use it */
|
||||||
if(new_path != NULL) {
|
if ( new_path != NULL ) {
|
||||||
path = new_path;
|
path = new_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reset frame counter */
|
/* Reset frame counter */
|
||||||
frame_count = 0;
|
frame_count = 0;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if ZM_HAVE_VIDEOWRITER_X264MP4
|
#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<EncoderParameter_t>* p_user_params) : VideoWriter("mp4", "h264", p_path, p_width, p_height, p_colours, p_subpixelorder), bOpen(false), bGotH264AVCInfo(false), bFirstFrame(true) {
|
X264MP4Writer::X264MP4Writer(
|
||||||
|
const char* p_path,
|
||||||
/* Initialize ffmpeg if it hasn't been initialized yet */
|
const unsigned int p_width,
|
||||||
FFMPEGInit();
|
const unsigned int p_height,
|
||||||
|
const unsigned int p_colours,
|
||||||
|
const unsigned int p_subpixelorder,
|
||||||
|
const std::vector<EncoderParameter_t>* 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 */
|
/* Initialize swscale */
|
||||||
zm_pf = GetFFMPEGPixelFormat(colours,subpixelorder);
|
zm_pf = GetFFMPEGPixelFormat(colours, subpixelorder);
|
||||||
if(zm_pf == 0) {
|
if ( zm_pf == 0 ) {
|
||||||
Error("Unable to match ffmpeg pixelformat");
|
Error("Unable to match ffmpeg pixelformat");
|
||||||
}
|
}
|
||||||
codec_pf = AV_PIX_FMT_YUV420P;
|
codec_pf = AV_PIX_FMT_YUV420P;
|
||||||
|
|
||||||
swscaleobj.SetDefaults(zm_pf, codec_pf, width, height);
|
swscaleobj.SetDefaults(zm_pf, codec_pf, width, height);
|
||||||
|
|
||||||
/* Calculate the image sizes. We will need this for parameter checking */
|
/* Calculate the image sizes. We will need this for parameter checking */
|
||||||
zm_imgsize = colours * width * height;
|
zm_imgsize = colours * width * height;
|
||||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||||
codec_imgsize = av_image_get_buffer_size( codec_pf, width, height, 1 );
|
codec_imgsize = av_image_get_buffer_size(codec_pf, width, height, 1);
|
||||||
#else
|
#else
|
||||||
codec_imgsize = avpicture_get_size( codec_pf, width, height);
|
codec_imgsize = avpicture_get_size(codec_pf, width, height);
|
||||||
#endif
|
#endif
|
||||||
if(!codec_imgsize) {
|
if ( !codec_imgsize ) {
|
||||||
Error("Failed calculating codec pixel format image size");
|
Error("Failed calculating codec pixel format image size");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If supplied with user parameters to the encoder, copy them */
|
/* If supplied with user parameters to the encoder, copy them */
|
||||||
if(p_user_params != NULL) {
|
if ( p_user_params != NULL ) {
|
||||||
user_params = *p_user_params;
|
user_params = *p_user_params;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Setup x264 parameters */
|
/* Setup x264 parameters */
|
||||||
if(x264config() < 0) {
|
if ( x264config() < 0 ) {
|
||||||
Error("Failed setting x264 parameters");
|
Error("Failed setting x264 parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Allocate x264 input picture */
|
/* Allocate x264 input picture */
|
||||||
x264_picture_alloc(&x264picin, X264_CSP_I420, x264params.i_width, x264params.i_height);
|
x264_picture_alloc(
|
||||||
|
&x264picin,
|
||||||
|
X264_CSP_I420,
|
||||||
|
x264params.i_width,
|
||||||
|
x264params.i_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
X264MP4Writer::~X264MP4Writer() {
|
X264MP4Writer::~X264MP4Writer() {
|
||||||
|
/* Free x264 input picture */
|
||||||
|
x264_picture_clean(&x264picin);
|
||||||
|
|
||||||
/* Free x264 input picture */
|
if ( bOpen )
|
||||||
x264_picture_clean(&x264picin);
|
Close();
|
||||||
|
|
||||||
if(bOpen)
|
|
||||||
Close();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int X264MP4Writer::Open() {
|
int X264MP4Writer::Open() {
|
||||||
|
/* Open the encoder */
|
||||||
|
x264enc = x264_encoder_open(&x264params);
|
||||||
|
if ( x264enc == NULL ) {
|
||||||
|
Error("Failed opening x264 encoder");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Open the encoder */
|
// Debug(4,"x264 maximum delayed frames: %d",
|
||||||
x264enc = x264_encoder_open(&x264params);
|
// x264_encoder_maximum_delayed_frames(x264enc));
|
||||||
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;
|
||||||
x264_nal_t* nals;
|
if ( !x264_encoder_headers(x264enc, &nals, &i_nals) ) {
|
||||||
int i_nals;
|
Error("Failed getting encoder headers");
|
||||||
if(!x264_encoder_headers(x264enc,&nals,&i_nals)) {
|
return -2;
|
||||||
Error("Failed getting encoder headers");
|
}
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Search SPS NAL for AVC information */
|
|
||||||
for(int i=0;i<i_nals;i++) {
|
|
||||||
if(nals[i].i_type == NAL_SPS) {
|
|
||||||
x264_profleindication = nals[i].p_payload[5];
|
|
||||||
x264_profilecompat = nals[i].p_payload[6];
|
|
||||||
x264_levelindication = nals[i].p_payload[7];
|
|
||||||
bGotH264AVCInfo = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!bGotH264AVCInfo) {
|
|
||||||
Warning("Missing AVC information");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create the file */
|
|
||||||
mp4h = MP4Create((path + ".incomplete").c_str());
|
|
||||||
if(mp4h == MP4_INVALID_FILE_HANDLE) {
|
|
||||||
Error("Failed creating mp4 file: %s",path.c_str());
|
|
||||||
return -10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set the global timescale */
|
|
||||||
if(!MP4SetTimeScale(mp4h, 1000)) {
|
|
||||||
Error("Failed setting timescale");
|
|
||||||
return -11;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set the global video profile */
|
|
||||||
/* I am a bit confused about this one.
|
|
||||||
I couldn't find what the value should be
|
|
||||||
Some use 0x15 while others use 0x7f. */
|
|
||||||
MP4SetVideoProfileLevel(mp4h, 0x7f);
|
|
||||||
|
|
||||||
/* Add H264 video track */
|
|
||||||
mp4vtid = MP4AddH264VideoTrack(mp4h,1000,MP4_INVALID_DURATION,width,height,x264_profleindication,x264_profilecompat,x264_levelindication,3);
|
|
||||||
if(mp4vtid == MP4_INVALID_TRACK_ID) {
|
|
||||||
Error("Failed adding H264 video track");
|
|
||||||
return -12;
|
|
||||||
}
|
|
||||||
|
|
||||||
bOpen = true;
|
/* Search SPS NAL for AVC information */
|
||||||
|
for ( unsigned int i = 0; i < i_nals; i++ ) {
|
||||||
return 0;
|
if ( nals[i].i_type == NAL_SPS ) {
|
||||||
|
x264_profleindication = nals[i].p_payload[5];
|
||||||
|
x264_profilecompat = nals[i].p_payload[6];
|
||||||
|
x264_levelindication = nals[i].p_payload[7];
|
||||||
|
bGotH264AVCInfo = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( !bGotH264AVCInfo ) {
|
||||||
|
Warning("Missing AVC information");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the file */
|
||||||
|
mp4h = MP4Create((path + ".incomplete").c_str());
|
||||||
|
if ( mp4h == MP4_INVALID_FILE_HANDLE ) {
|
||||||
|
Error("Failed creating mp4 file: %s", path.c_str());
|
||||||
|
return -10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the global timescale */
|
||||||
|
if ( !MP4SetTimeScale(mp4h, 1000) ) {
|
||||||
|
Error("Failed setting timescale");
|
||||||
|
return -11;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the global video profile */
|
||||||
|
/* I am a bit confused about this one.
|
||||||
|
I couldn't find what the value should be
|
||||||
|
Some use 0x15 while others use 0x7f. */
|
||||||
|
MP4SetVideoProfileLevel(mp4h, 0x7f);
|
||||||
|
|
||||||
|
/* Add H264 video track */
|
||||||
|
mp4vtid = MP4AddH264VideoTrack(
|
||||||
|
mp4h,
|
||||||
|
1000,
|
||||||
|
MP4_INVALID_DURATION,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
x264_profleindication,
|
||||||
|
x264_profilecompat,
|
||||||
|
x264_levelindication,
|
||||||
|
3);
|
||||||
|
if ( mp4vtid == MP4_INVALID_TRACK_ID ) {
|
||||||
|
Error("Failed adding H264 video track");
|
||||||
|
return -12;
|
||||||
|
}
|
||||||
|
|
||||||
|
bOpen = true;
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int X264MP4Writer::Close() {
|
int X264MP4Writer::Close() {
|
||||||
|
/* Flush all pending frames */
|
||||||
|
for ( int i = (x264_encoder_delayed_frames(x264enc) + 1); i > 0; i-- ) {
|
||||||
|
x264encodeloop(true);
|
||||||
|
}
|
||||||
|
|
||||||
/* Flush all pending frames */
|
/* Close the encoder */
|
||||||
for(int i = (x264_encoder_delayed_frames(x264enc) + 1); i > 0; i-- ) {
|
x264_encoder_close(x264enc);
|
||||||
x264encodeloop(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Close the encoder */
|
/* Close MP4 handle */
|
||||||
x264_encoder_close(x264enc);
|
MP4Close(mp4h);
|
||||||
|
|
||||||
/* Close MP4 handle */
|
/* Required for proper HTTP streaming */
|
||||||
MP4Close(mp4h);
|
MP4Optimize((path + ".incomplete").c_str(), path.c_str());
|
||||||
|
|
||||||
/* Required for proper HTTP streaming */
|
|
||||||
MP4Optimize((path + ".incomplete").c_str(), path.c_str());
|
|
||||||
|
|
||||||
/* Delete the temporary file */
|
/* Delete the temporary file */
|
||||||
unlink((path + ".incomplete").c_str());
|
unlink((path + ".incomplete").c_str());
|
||||||
|
|
||||||
bOpen = false;
|
|
||||||
|
|
||||||
Debug(7, "Video closed. Total frames: %d", frame_count);
|
bOpen = false;
|
||||||
|
|
||||||
return 0;
|
Debug(7, "Video closed. Total frames: %d", frame_count);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int X264MP4Writer::Reset(const char* new_path) {
|
int X264MP4Writer::Reset(const char* new_path) {
|
||||||
|
/* Close the encoder and file */
|
||||||
/* Close the encoder and file */
|
if ( bOpen )
|
||||||
if(bOpen)
|
Close();
|
||||||
Close();
|
|
||||||
|
|
||||||
/* Reset common variables */
|
/* Reset common variables */
|
||||||
VideoWriter::Reset(new_path);
|
VideoWriter::Reset(new_path);
|
||||||
|
|
||||||
/* Reset local variables */
|
/* Reset local variables */
|
||||||
bFirstFrame = true;
|
bFirstFrame = true;
|
||||||
bGotH264AVCInfo = false;
|
bGotH264AVCInfo = false;
|
||||||
prevnals.clear();
|
prevnals.clear();
|
||||||
prevpayload.clear();
|
prevpayload.clear();
|
||||||
|
|
||||||
/* Reset x264 parameters */
|
|
||||||
x264config();
|
|
||||||
|
|
||||||
/* Open the encoder */
|
/* Reset x264 parameters */
|
||||||
Open();
|
x264config();
|
||||||
|
|
||||||
return 0;
|
/* Open the encoder */
|
||||||
|
Open();
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int X264MP4Writer::Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/* Parameter checking */
|
if ( data_size != zm_imgsize ) {
|
||||||
if(data == NULL) {
|
Error("The data buffer size (%d) != expected (%d)", data_size, zm_imgsize);
|
||||||
Error("NULL buffer");
|
return -2;
|
||||||
return -1;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(data_size != zm_imgsize) {
|
if ( !bOpen ) {
|
||||||
Error("The data buffer size does not match the expected size. Expected: %d Current: %d", zm_imgsize, data_size);
|
Warning("The encoder was not initialized, initializing now");
|
||||||
return -2;
|
Open();
|
||||||
}
|
}
|
||||||
|
|
||||||
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 */
|
/* Convert the image into the x264 input picture */
|
||||||
x264picin.i_pts = frame_time;
|
if ( swscaleobj.ConvertDefaults(data, data_size, x264picin.img.plane[0], codec_imgsize) < 0 ) {
|
||||||
|
Error("Image conversion failed");
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
/* Do the encoding */
|
/* Set PTS */
|
||||||
x264encodeloop();
|
x264picin.i_pts = frame_time;
|
||||||
|
|
||||||
/* Increment frame counter */
|
/* Do the encoding */
|
||||||
frame_count++;
|
x264encodeloop();
|
||||||
|
|
||||||
return 0;
|
/* Increment frame counter */
|
||||||
|
frame_count++;
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int X264MP4Writer::Encode(const Image* img, const unsigned int frame_time) {
|
int X264MP4Writer::Encode(const Image* img, const unsigned int frame_time) {
|
||||||
|
if ( img->Width() != width ) {
|
||||||
if(img->Width() != width) {
|
Error("Source image width differs. Source: %d Output: %d", img->Width(), width);
|
||||||
Error("Source image width differs. Source: %d Output: %d",img->Width(), width);
|
return -12;
|
||||||
return -12;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(img->Height() != height) {
|
if ( img->Height() != height ) {
|
||||||
Error("Source image height differs. Source: %d Output: %d",img->Height(), height);
|
Error("Source image height differs. Source: %d Output: %d", img->Height(), height);
|
||||||
return -13;
|
return -13;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Encode(img->Buffer(),img->Size(),frame_time);
|
return Encode(img->Buffer(), img->Size(), frame_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
int X264MP4Writer::x264config() {
|
int X264MP4Writer::x264config() {
|
||||||
/* Sets up the encoder configuration */
|
/* Sets up the encoder configuration */
|
||||||
|
|
||||||
int x264ret;
|
int x264ret;
|
||||||
|
|
||||||
/* Defaults */
|
/* Defaults */
|
||||||
const char* preset = "veryfast";
|
const char* preset = "veryfast";
|
||||||
const char* tune = "stillimage";
|
const char* tune = "stillimage";
|
||||||
const char* profile = "main";
|
const char* profile = "main";
|
||||||
|
|
||||||
/* Search the user parameters for preset, tune and profile */
|
/* Search the user parameters for preset, tune and profile */
|
||||||
for(unsigned int i=0; i < user_params.size(); i++) {
|
for ( unsigned int i = 0; i < user_params.size(); i++ ) {
|
||||||
if(strcmp(user_params[i].pname, "preset") == 0) {
|
if ( strcmp(user_params[i].pname, "preset") == 0 ) {
|
||||||
/* Got preset */
|
/* Got preset */
|
||||||
preset = user_params[i].pvalue;
|
preset = user_params[i].pvalue;
|
||||||
} else if(strcmp(user_params[i].pname, "tune") == 0) {
|
} else if ( strcmp(user_params[i].pname, "tune") == 0 ) {
|
||||||
/* Got tune */
|
/* Got tune */
|
||||||
tune = user_params[i].pvalue;
|
tune = user_params[i].pvalue;
|
||||||
} else if(strcmp(user_params[i].pname, "profile") == 0) {
|
} else if ( strcmp(user_params[i].pname, "profile") == 0 ) {
|
||||||
/* Got profile */
|
/* Got profile */
|
||||||
profile = user_params[i].pvalue;
|
profile = user_params[i].pvalue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set the defaults and preset and tune */
|
/* Set the defaults and preset and tune */
|
||||||
x264ret = x264_param_default_preset(&x264params, preset, tune);
|
x264ret = x264_param_default_preset(&x264params, preset, tune);
|
||||||
if(x264ret != 0) {
|
if ( x264ret != 0 ) {
|
||||||
Error("Failed setting x264 preset %s and tune %s : %d",preset,tune,x264ret);
|
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 */
|
/* Set the profile */
|
||||||
x264params.i_bframe = 16;
|
x264ret = x264_param_apply_profile(&x264params, profile);
|
||||||
x264params.i_bframe_adaptive = 1;
|
if ( x264ret != 0 ) {
|
||||||
|
Error("Failed setting x264 profile %s : %d", profile, x264ret);
|
||||||
/* Timebase */
|
}
|
||||||
x264params.i_timebase_num = 1;
|
|
||||||
x264params.i_timebase_den = 1000;
|
|
||||||
|
|
||||||
/* Enable variable frame rate */
|
/* Input format */
|
||||||
x264params.b_vfr_input = 1;
|
x264params.i_width = width;
|
||||||
|
x264params.i_height = height;
|
||||||
|
x264params.i_csp = X264_CSP_I420;
|
||||||
|
|
||||||
/* Disable annex-b (start codes) */
|
/* Quality control */
|
||||||
x264params.b_annexb = 0;
|
x264params.rc.i_rc_method = X264_RC_CRF;
|
||||||
|
x264params.rc.f_rf_constant = 23.0;
|
||||||
/* TODO: Setup error handler */
|
|
||||||
//x264params.i_log_level = X264_LOG_DEBUG;
|
|
||||||
|
|
||||||
/* Process user parameters (excluding preset, tune and profile) */
|
/* Enable b-frames */
|
||||||
for(unsigned int i=0; i < user_params.size(); i++) {
|
x264params.i_bframe = 16;
|
||||||
/* Skip preset, tune and profile */
|
x264params.i_bframe_adaptive = 1;
|
||||||
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 */
|
/* Timebase */
|
||||||
x264ret = x264_param_parse(&x264params, user_params[i].pname, user_params[i].pvalue);
|
x264params.i_timebase_num = 1;
|
||||||
|
x264params.i_timebase_den = 1000;
|
||||||
|
|
||||||
/* Error checking */
|
/* Enable variable frame rate */
|
||||||
if(x264ret != 0) {
|
x264params.b_vfr_input = 1;
|
||||||
if(x264ret == X264_PARAM_BAD_NAME) {
|
|
||||||
Error("Failed processing x264 user parameter %s=%s : Bad name", user_params[i].pname, user_params[i].pvalue);
|
/* Disable annex-b (start codes) */
|
||||||
} else if(x264ret == X264_PARAM_BAD_VALUE) {
|
x264params.b_annexb = 0;
|
||||||
Error("Failed processing x264 user parameter %s=%s : Bad value", user_params[i].pname, user_params[i].pvalue);
|
|
||||||
} else {
|
/* TODO: Setup error handler */
|
||||||
Error("Failed processing x264 user parameter %s=%s : Unknown error (%d)", user_params[i].pname, user_params[i].pvalue, x264ret);
|
// 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 */
|
||||||
return 0;
|
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) {
|
void X264MP4Writer::x264encodeloop(bool bFlush) {
|
||||||
|
x264_nal_t* nals;
|
||||||
|
int i_nals;
|
||||||
|
int frame_size;
|
||||||
|
|
||||||
x264_nal_t* nals;
|
if ( bFlush ) {
|
||||||
int i_nals;
|
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, NULL, &x264picout);
|
||||||
int frame_size;
|
} else {
|
||||||
|
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, &x264picin, &x264picout);
|
||||||
|
}
|
||||||
|
|
||||||
if(bFlush) {
|
if ( frame_size > 0 || bFlush ) {
|
||||||
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, NULL, &x264picout);
|
Debug(8, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",
|
||||||
} else {
|
frame_count, x264picout.i_pts, x264picout.i_dts, frame_size);
|
||||||
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, &x264picin, &x264picout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame_size > 0 || bFlush) {
|
/* Handle the previous frame */
|
||||||
Debug(8, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",frame_count, x264picout.i_pts, x264picout.i_dts, frame_size);
|
if ( !bFirstFrame ) {
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
/* Handle the previous frame */
|
/* Process the NALs for the previous frame */
|
||||||
if(!bFirstFrame) {
|
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);
|
||||||
|
|
||||||
buffer.clear();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Process the NALs for the previous frame */
|
/* Calculate frame duration and offset */
|
||||||
for(unsigned int i=0; i < prevnals.size(); i++) {
|
int duration = x264picout.i_dts - prevDTS;
|
||||||
Debug(9,"Processing NAL: Type %d Size %d",prevnals[i].i_type,prevnals[i].i_payload);
|
int offset = prevPTS - prevDTS;
|
||||||
|
|
||||||
switch(prevnals[i].i_type) {
|
/* Write the sample */
|
||||||
case NAL_PPS:
|
if ( !buffer.empty() ) {
|
||||||
/* PPS NAL */
|
if ( !MP4WriteSample(
|
||||||
MP4AddH264PictureParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4);
|
mp4h,
|
||||||
break;
|
mp4vtid,
|
||||||
case NAL_SPS:
|
buffer.extract(buffer.size()),
|
||||||
/* SPS NAL */
|
buffer.size(),
|
||||||
MP4AddH264SequenceParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4);
|
duration,
|
||||||
break;
|
offset,
|
||||||
default:
|
prevKeyframe) ) {
|
||||||
/* Anything else, hopefully frames, so copy it into the sample */
|
Error("Failed writing sample");
|
||||||
buffer.append(prevnals[i].p_payload, prevnals[i].i_payload);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Calculate frame duration and offset */
|
/* Cleanup */
|
||||||
int duration = x264picout.i_dts - prevDTS;
|
prevnals.clear();
|
||||||
int offset = prevPTS - prevDTS;
|
prevpayload.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/* Write the sample */
|
/* Got a frame. Copy this new frame into the previous frame */
|
||||||
if(!buffer.empty()) {
|
if ( frame_size > 0 ) {
|
||||||
if(!MP4WriteSample(mp4h, mp4vtid, buffer.extract(buffer.size()), buffer.size(), duration, offset, prevKeyframe)) {
|
/* Copy the NALs and the payloads */
|
||||||
Error("Failed writing sample");
|
for ( unsigned int i = 0; i < i_nals; i++ ) {
|
||||||
}
|
prevnals.push_back(nals[i]);
|
||||||
}
|
prevpayload.append(nals[i].p_payload, nals[i].i_payload);
|
||||||
|
}
|
||||||
|
|
||||||
/* Cleanup */
|
/* Update the payload pointers */
|
||||||
prevnals.clear();
|
/* This is done in a separate loop because the previous loop might reallocate memory when appending,
|
||||||
prevpayload.clear();
|
making the pointers invalid */
|
||||||
|
unsigned int payload_offset = 0;
|
||||||
|
for ( unsigned int i = 0; i < prevnals.size(); i++ ) {
|
||||||
|
prevnals[i].p_payload = prevpayload.head() + payload_offset;
|
||||||
|
payload_offset += nals[i].i_payload;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
/* We need this for the next frame */
|
||||||
|
prevPTS = x264picout.i_pts;
|
||||||
|
prevDTS = x264picout.i_dts;
|
||||||
|
prevKeyframe = x264picout.b_keyframe;
|
||||||
|
|
||||||
/* Got a frame. Copy this new frame into the previous frame */
|
bFirstFrame = false;
|
||||||
if(frame_size > 0) {
|
}
|
||||||
/* Copy the NALs and the payloads */
|
|
||||||
for(int i=0;i<i_nals;i++) {
|
|
||||||
|
|
||||||
prevnals.push_back(nals[i]);
|
|
||||||
prevpayload.append(nals[i].p_payload, nals[i].i_payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Update the payload pointers */
|
} else if ( frame_size == 0 ) {
|
||||||
/* This is done in a separate loop because the previous loop might reallocate memory when appending,
|
Debug(7, "x264 encode returned zero. Delayed frames: %d",
|
||||||
making the pointers invalid */
|
x264_encoder_delayed_frames(x264enc));
|
||||||
unsigned int payload_offset = 0;
|
} else {
|
||||||
for(unsigned int i=0;i<prevnals.size();i++) {
|
Error("x264 encode failed: %d", frame_size);
|
||||||
prevnals[i].p_payload = prevpayload.head() + payload_offset;
|
}
|
||||||
payload_offset += nals[i].i_payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We need this for the next frame */
|
|
||||||
prevPTS = x264picout.i_pts;
|
|
||||||
prevDTS = x264picout.i_dts;
|
|
||||||
prevKeyframe = x264picout.b_keyframe;
|
|
||||||
|
|
||||||
bFirstFrame = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if(frame_size == 0) {
|
|
||||||
Debug(7,"x264 encode returned zero. Delayed frames: %d",x264_encoder_delayed_frames(x264enc));
|
|
||||||
} else {
|
|
||||||
Error("x264 encode failed: %d",frame_size);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif // ZM_VIDEOWRITER_X264MP4
|
#endif // ZM_VIDEOWRITER_X264MP4
|
||||||
|
|
||||||
int ParseEncoderParameters(const char* str, std::vector<EncoderParameter_t>* vec) {
|
int ParseEncoderParameters(
|
||||||
if(vec == NULL) {
|
const char* str,
|
||||||
Error("NULL Encoder parameters vector pointer");
|
std::vector<EncoderParameter_t>* vec
|
||||||
return -1;
|
) {
|
||||||
}
|
if ( vec == NULL ) {
|
||||||
|
Error("NULL Encoder parameters vector pointer");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if(str == NULL) {
|
if ( str == NULL ) {
|
||||||
Error("NULL Encoder parameters string");
|
Error("NULL Encoder parameters string");
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec->clear();
|
vec->clear();
|
||||||
|
|
||||||
if(str[0] == 0) {
|
if ( str[0] == 0 ) {
|
||||||
/* Empty */
|
/* Empty */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string line;
|
std::string line;
|
||||||
std::stringstream ss(str);
|
std::stringstream ss(str);
|
||||||
size_t valueoffset;
|
size_t valueoffset;
|
||||||
size_t valuelen;
|
size_t valuelen;
|
||||||
unsigned int lineno = 0;
|
unsigned int lineno = 0;
|
||||||
EncoderParameter_t param;
|
EncoderParameter_t param;
|
||||||
|
|
||||||
while(std::getline(ss, line) ) {
|
while ( std::getline(ss, line) ) {
|
||||||
lineno++;
|
lineno++;
|
||||||
|
|
||||||
/* Remove CR if exists */
|
/* Remove CR if exists */
|
||||||
if(line.length() >= 1 && line[line.length()-1] == '\r') {
|
if ( line.length() >= 1 && line[line.length()-1] == '\r' ) {
|
||||||
line.erase(line.length()-1);
|
line.erase(line.length() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Skip comments and empty lines */
|
/* Skip comments and empty lines */
|
||||||
if(line.empty() || line[0] == '#') {
|
if ( line.empty() || line[0] == '#' ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
valueoffset = line.find('=');
|
valueoffset = line.find('=');
|
||||||
if(valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0) {
|
if ( valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0 ) {
|
||||||
Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno);
|
Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( valueoffset > (sizeof(param.pname) - 1 ) ) {
|
||||||
|
Warning("Failed parsing encoder parameters line %d: Name too long", lineno);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if(valueoffset > (sizeof(param.pname)-1) ) {
|
valuelen = line.length() - (valueoffset+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;
|
||||||
|
}
|
||||||
|
|
||||||
if( valuelen > (sizeof(param.pvalue)-1) ) {
|
/* Copy and NULL terminate */
|
||||||
Warning("Failed parsing encoder parameters line %d: Value too long", lineno);
|
line.copy(param.pname, valueoffset, 0);
|
||||||
continue;
|
line.copy(param.pvalue, valuelen, valueoffset+1);
|
||||||
}
|
param.pname[valueoffset] = 0;
|
||||||
|
param.pvalue[valuelen] = 0;
|
||||||
|
|
||||||
/* Copy and NULL terminate */
|
/* Push to the vector */
|
||||||
line.copy(param.pname, valueoffset, 0);
|
vec->push_back(param);
|
||||||
line.copy(param.pvalue, valuelen, valueoffset+1);
|
|
||||||
param.pname[valueoffset] = 0;
|
|
||||||
param.pvalue[valuelen] = 0;
|
|
||||||
|
|
||||||
/* Push to the vector */
|
Debug(7, "Parsed encoder parameter: %s = %s", param.pname, param.pvalue);
|
||||||
vec->push_back(param);
|
}
|
||||||
|
|
||||||
Debug(7, "Parsed encoder parameter: %s = %s", param.pname, param.pvalue);
|
Debug(7, "Parsed %d lines", lineno);
|
||||||
}
|
|
||||||
|
|
||||||
Debug(7, "Parsed %d lines", lineno);
|
return 0;
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -189,6 +189,8 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
|
||||||
audio_output_codec = NULL;
|
audio_output_codec = NULL;
|
||||||
audio_input_context = NULL;
|
audio_input_context = NULL;
|
||||||
audio_output_stream = NULL;
|
audio_output_stream = NULL;
|
||||||
|
input_frame = NULL;
|
||||||
|
output_frame = NULL;
|
||||||
#ifdef HAVE_LIBAVRESAMPLE
|
#ifdef HAVE_LIBAVRESAMPLE
|
||||||
resample_context = NULL;
|
resample_context = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
@ -391,6 +393,15 @@ Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts
|
||||||
|
|
||||||
/* free the stream */
|
/* free the stream */
|
||||||
avformat_free_context(oc);
|
avformat_free_context(oc);
|
||||||
|
|
||||||
|
if ( input_frame ) {
|
||||||
|
av_frame_free( &input_frame );
|
||||||
|
input_frame = NULL;
|
||||||
|
}
|
||||||
|
if ( output_frame ) {
|
||||||
|
av_frame_free( &output_frame );
|
||||||
|
output_frame = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoStore::setup_resampler() {
|
bool VideoStore::setup_resampler() {
|
||||||
|
@ -495,13 +506,13 @@ bool VideoStore::setup_resampler() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a new frame to store the audio samples. */
|
/** Create a new frame to store the audio samples. */
|
||||||
if (!(input_frame = zm_av_frame_alloc())) {
|
if ( !(input_frame = zm_av_frame_alloc()) ) {
|
||||||
Error("Could not allocate input frame");
|
Error("Could not allocate input frame");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a new frame to store the audio samples. */
|
/** Create a new frame to store the audio samples. */
|
||||||
if (!(output_frame = zm_av_frame_alloc())) {
|
if ( !(output_frame = zm_av_frame_alloc()) ) {
|
||||||
Error("Could not allocate output frame");
|
Error("Could not allocate output frame");
|
||||||
av_frame_free( &input_frame );
|
av_frame_free( &input_frame );
|
||||||
return false;
|
return false;
|
||||||
|
@ -618,7 +629,7 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) {
|
||||||
int duration;
|
int duration;
|
||||||
|
|
||||||
//Scale the PTS of the outgoing packet to be the correct time base
|
//Scale the PTS of the outgoing packet to be the correct time base
|
||||||
if (ipkt->pts != AV_NOPTS_VALUE) {
|
if ( ipkt->pts != AV_NOPTS_VALUE ) {
|
||||||
|
|
||||||
if ( ! video_last_pts ) {
|
if ( ! video_last_pts ) {
|
||||||
// This is the first packet.
|
// This is the first packet.
|
||||||
|
@ -687,18 +698,12 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) {
|
||||||
opkt.data = ipkt->data;
|
opkt.data = ipkt->data;
|
||||||
opkt.size = ipkt->size;
|
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
|
opkt.stream_index = video_output_stream->index;
|
||||||
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;
|
AVPacket safepkt;
|
||||||
memcpy(&safepkt, &opkt, sizeof(AVPacket));
|
memcpy( &safepkt, &opkt, sizeof(AVPacket) );
|
||||||
|
|
||||||
Debug(1, "writing video packet pts(%d) dts(%d) duration(%d)", opkt.pts, opkt.dts, opkt.duration );
|
Debug(1, "writing video packet pts(%d) dts(%d) duration(%d)", opkt.pts, opkt.dts, opkt.duration );
|
||||||
if ((opkt.data == NULL)||(opkt.size < 1)) {
|
if ((opkt.data == NULL)||(opkt.size < 1)) {
|
||||||
Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ );
|
Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ );
|
||||||
dumpPacket( ipkt);
|
dumpPacket( ipkt);
|
||||||
|
@ -714,10 +719,14 @@ Debug(1, "writing video packet pts(%d) dts(%d) duration(%d)", opkt.pts, opkt.dts
|
||||||
video_previous_dts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance
|
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;
|
video_previous_pts = opkt.pts;
|
||||||
ret = av_interleaved_write_frame(oc, &opkt);
|
ret = av_interleaved_write_frame(oc, &opkt);
|
||||||
if(ret<0){
|
if ( ret < 0 ) {
|
||||||
// There's nothing we can really do if the frame is rejected, just drop it and get on with the next
|
// 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));
|
Warning("%s:%d: Writing frame [av_interleaved_write_frame()] failed: %s(%d) ", __FILE__, __LINE__, av_make_error_string(ret).c_str(), (ret));
|
||||||
dumpPacket(&safepkt);
|
dumpPacket(&safepkt);
|
||||||
|
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
|
||||||
|
zm_dump_codecpar( video_input_stream->codecpar );
|
||||||
|
zm_dump_codecpar( video_output_stream->codecpar );
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -914,8 +923,7 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) {
|
||||||
|
|
||||||
// pkt.pos: byte position in stream, -1 if unknown
|
// pkt.pos: byte position in stream, -1 if unknown
|
||||||
opkt.pos = -1;
|
opkt.pos = -1;
|
||||||
opkt.stream_index = ipkt->stream_index;
|
opkt.stream_index = audio_output_stream->index;//ipkt->stream_index;
|
||||||
Debug(2, "Stream index is %d", opkt.stream_index );
|
|
||||||
|
|
||||||
AVPacket safepkt;
|
AVPacket safepkt;
|
||||||
memcpy(&safepkt, &opkt, sizeof(AVPacket));
|
memcpy(&safepkt, &opkt, sizeof(AVPacket));
|
||||||
|
|
151
src/zms.cpp
151
src/zms.cpp
|
@ -26,32 +26,26 @@
|
||||||
#include "zm_signal.h"
|
#include "zm_signal.h"
|
||||||
#include "zm_monitor.h"
|
#include "zm_monitor.h"
|
||||||
|
|
||||||
bool ValidateAccess( User *user, int mon_id )
|
bool ValidateAccess( User *user, int mon_id ) {
|
||||||
{
|
|
||||||
bool allowed = true;
|
bool allowed = true;
|
||||||
|
|
||||||
if ( mon_id > 0 )
|
if ( mon_id > 0 ) {
|
||||||
{
|
|
||||||
if ( user->getStream() < User::PERM_VIEW )
|
if ( user->getStream() < User::PERM_VIEW )
|
||||||
allowed = false;
|
allowed = false;
|
||||||
if ( !user->canAccess( mon_id ) )
|
if ( !user->canAccess( mon_id ) )
|
||||||
allowed = false;
|
allowed = false;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
if ( user->getEvents() < User::PERM_VIEW )
|
if ( user->getEvents() < User::PERM_VIEW )
|
||||||
allowed = false;
|
allowed = false;
|
||||||
}
|
}
|
||||||
if ( !allowed )
|
if ( !allowed ) {
|
||||||
{
|
|
||||||
Error( "Error, insufficient privileges for requested action" );
|
Error( "Error, insufficient privileges for requested action" );
|
||||||
exit( -1 );
|
exit( -1 );
|
||||||
}
|
}
|
||||||
return( allowed );
|
return( allowed );
|
||||||
}
|
}
|
||||||
|
|
||||||
int main( int argc, const char *argv[] )
|
int main( int argc, const char *argv[] ) {
|
||||||
{
|
|
||||||
self = argv[0];
|
self = argv[0];
|
||||||
|
|
||||||
srand( getpid() * time( 0 ) );
|
srand( getpid() * time( 0 ) );
|
||||||
|
@ -82,8 +76,7 @@ int main( int argc, const char *argv[] )
|
||||||
else //argv[0] will not always contain the full path, but rather just the script name
|
else //argv[0] will not always contain the full path, but rather just the script name
|
||||||
basename = argv[0];
|
basename = argv[0];
|
||||||
const char *nph_prefix = "nph-";
|
const char *nph_prefix = "nph-";
|
||||||
if ( basename && !strncmp( basename, nph_prefix, strlen(nph_prefix) ) )
|
if ( basename && !strncmp( basename, nph_prefix, strlen(nph_prefix) ) ) {
|
||||||
{
|
|
||||||
nph = true;
|
nph = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,8 +90,7 @@ int main( int argc, const char *argv[] )
|
||||||
zmSetDefaultDieHandler();
|
zmSetDefaultDieHandler();
|
||||||
|
|
||||||
const char *query = getenv( "QUERY_STRING" );
|
const char *query = getenv( "QUERY_STRING" );
|
||||||
if ( query )
|
if ( query ) {
|
||||||
{
|
|
||||||
Debug( 1, "Query: %s", query );
|
Debug( 1, "Query: %s", query );
|
||||||
|
|
||||||
char temp_query[1024];
|
char temp_query[1024];
|
||||||
|
@ -106,84 +98,68 @@ int main( int argc, const char *argv[] )
|
||||||
char *q_ptr = temp_query;
|
char *q_ptr = temp_query;
|
||||||
char *parms[16]; // Shouldn't be more than this
|
char *parms[16]; // Shouldn't be more than this
|
||||||
int parm_no = 0;
|
int parm_no = 0;
|
||||||
while( (parm_no < 16) && (parms[parm_no] = strtok( q_ptr, "&" )) )
|
while( (parm_no < 16) && (parms[parm_no] = strtok( q_ptr, "&" )) ) {
|
||||||
{
|
|
||||||
parm_no++;
|
parm_no++;
|
||||||
q_ptr = NULL;
|
q_ptr = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( int p = 0; p < parm_no; p++ )
|
for ( int p = 0; p < parm_no; p++ ) {
|
||||||
{
|
|
||||||
char *name = strtok( parms[p], "=" );
|
char *name = strtok( parms[p], "=" );
|
||||||
char *value = strtok( NULL, "=" );
|
char *value = strtok( NULL, "=" );
|
||||||
if ( !value )
|
if ( !value )
|
||||||
value = (char *)"";
|
value = (char *)"";
|
||||||
if ( !strcmp( name, "source" ) )
|
if ( !strcmp( name, "source" ) ) {
|
||||||
{
|
|
||||||
source = !strcmp( value, "event" )?ZMS_EVENT:ZMS_MONITOR;
|
source = !strcmp( value, "event" )?ZMS_EVENT:ZMS_MONITOR;
|
||||||
}
|
} else if ( !strcmp( name, "mode" ) ) {
|
||||||
else if ( !strcmp( name, "mode" ) )
|
|
||||||
{
|
|
||||||
mode = !strcmp( value, "jpeg" )?ZMS_JPEG:ZMS_MPEG;
|
mode = !strcmp( value, "jpeg" )?ZMS_JPEG:ZMS_MPEG;
|
||||||
mode = !strcmp( value, "raw" )?ZMS_RAW:mode;
|
mode = !strcmp( value, "raw" )?ZMS_RAW:mode;
|
||||||
mode = !strcmp( value, "zip" )?ZMS_ZIP:mode;
|
mode = !strcmp( value, "zip" )?ZMS_ZIP:mode;
|
||||||
mode = !strcmp( value, "single" )?ZMS_SINGLE:mode;
|
mode = !strcmp( value, "single" )?ZMS_SINGLE:mode;
|
||||||
}
|
} else if ( !strcmp( name, "format" ) ) {
|
||||||
else if ( !strcmp( name, "format" ) )
|
|
||||||
strncpy( format, value, sizeof(format) );
|
strncpy( format, value, sizeof(format) );
|
||||||
else if ( !strcmp( name, "monitor" ) )
|
} else if ( !strcmp( name, "monitor" ) ) {
|
||||||
monitor_id = atoi( value );
|
monitor_id = atoi( value );
|
||||||
else if ( !strcmp( name, "time" ) )
|
} else if ( !strcmp( name, "time" ) ) {
|
||||||
event_time = atoi( value );
|
event_time = atoi( value );
|
||||||
else if ( !strcmp( name, "event" ) )
|
} else if ( !strcmp( name, "event" ) ) {
|
||||||
event_id = strtoull( value, (char **)NULL, 10 );
|
event_id = strtoull( value, (char **)NULL, 10 );
|
||||||
else if ( !strcmp( name, "frame" ) )
|
} else if ( !strcmp( name, "frame" ) ) {
|
||||||
frame_id = strtoull( value, (char **)NULL, 10 );
|
frame_id = strtoull( value, (char **)NULL, 10 );
|
||||||
else if ( !strcmp( name, "scale" ) )
|
} else if ( !strcmp( name, "scale" ) ) {
|
||||||
scale = atoi( value );
|
scale = atoi( value );
|
||||||
else if ( !strcmp( name, "rate" ) )
|
} else if ( !strcmp( name, "rate" ) ) {
|
||||||
rate = atoi( value );
|
rate = atoi( value );
|
||||||
else if ( !strcmp( name, "maxfps" ) )
|
} else if ( !strcmp( name, "maxfps" ) ) {
|
||||||
maxfps = atof( value );
|
maxfps = atof( value );
|
||||||
else if ( !strcmp( name, "bitrate" ) )
|
} else if ( !strcmp( name, "bitrate" ) ) {
|
||||||
bitrate = atoi( value );
|
bitrate = atoi( value );
|
||||||
else if ( !strcmp( name, "ttl" ) )
|
} else if ( !strcmp( name, "ttl" ) ) {
|
||||||
ttl = atoi(value);
|
ttl = atoi(value);
|
||||||
else if ( !strcmp( name, "replay" ) )
|
} else if ( !strcmp( name, "replay" ) ) {
|
||||||
{
|
|
||||||
replay = !strcmp( value, "gapless" )?EventStream::MODE_ALL_GAPLESS:EventStream::MODE_SINGLE;
|
replay = !strcmp( value, "gapless" )?EventStream::MODE_ALL_GAPLESS:EventStream::MODE_SINGLE;
|
||||||
replay = !strcmp( value, "all" )?EventStream::MODE_ALL:replay;
|
replay = !strcmp( value, "all" )?EventStream::MODE_ALL:replay;
|
||||||
}
|
} else if ( !strcmp( name, "connkey" ) ) {
|
||||||
else if ( !strcmp( name, "connkey" ) )
|
|
||||||
connkey = atoi(value);
|
connkey = atoi(value);
|
||||||
else if ( !strcmp( name, "buffer" ) )
|
} else if ( !strcmp( name, "buffer" ) ) {
|
||||||
playback_buffer = atoi(value);
|
playback_buffer = atoi(value);
|
||||||
else if ( config.opt_use_auth )
|
} else if ( config.opt_use_auth ) {
|
||||||
{
|
if ( strcmp( config.auth_relay, "none" ) == 0 ) {
|
||||||
if ( strcmp( config.auth_relay, "none" ) == 0 )
|
if ( !strcmp( name, "user" ) ) {
|
||||||
{
|
|
||||||
if ( !strcmp( name, "user" ) )
|
|
||||||
{
|
|
||||||
username = value;
|
username = value;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
|
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
|
||||||
{
|
{
|
||||||
if ( !strcmp( name, "auth" ) )
|
if ( !strcmp( name, "auth" ) ) {
|
||||||
{
|
|
||||||
strncpy( auth, value, sizeof(auth) );
|
strncpy( auth, value, sizeof(auth) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
|
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
|
||||||
{
|
{
|
||||||
if ( !strcmp( name, "user" ) )
|
if ( !strcmp( name, "user" ) ) {
|
||||||
{
|
|
||||||
username = UriDecode( value );
|
username = UriDecode( value );
|
||||||
}
|
}
|
||||||
if ( !strcmp( name, "pass" ) )
|
if ( !strcmp( name, "pass" ) ) {
|
||||||
{
|
|
||||||
password = UriDecode( value );
|
password = UriDecode( value );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,36 +168,28 @@ int main( int argc, const char *argv[] )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( config.opt_use_auth )
|
if ( config.opt_use_auth ) {
|
||||||
{
|
|
||||||
User *user = 0;
|
User *user = 0;
|
||||||
|
|
||||||
if ( strcmp( config.auth_relay, "none" ) == 0 )
|
if ( strcmp( config.auth_relay, "none" ) == 0 ) {
|
||||||
{
|
if ( username.length() ) {
|
||||||
if ( username.length() )
|
|
||||||
{
|
|
||||||
user = zmLoadUser( username.c_str() );
|
user = zmLoadUser( username.c_str() );
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
|
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
|
||||||
{
|
{
|
||||||
if ( *auth )
|
if ( *auth ) {
|
||||||
{
|
|
||||||
user = zmLoadAuthUser( auth, config.auth_hash_ips );
|
user = zmLoadAuthUser( auth, config.auth_hash_ips );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
|
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
|
||||||
{
|
{
|
||||||
if ( username.length() && password.length() )
|
if ( username.length() && password.length() ) {
|
||||||
{
|
|
||||||
user = zmLoadUser( username.c_str(), password.c_str() );
|
user = zmLoadUser( username.c_str(), password.c_str() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( !user )
|
if ( !user ) {
|
||||||
{
|
|
||||||
Error( "Unable to authenticate user" );
|
Error( "Unable to authenticate user" );
|
||||||
logTerm();
|
logTerm();
|
||||||
zmDbClose();
|
zmDbClose();
|
||||||
|
@ -231,8 +199,7 @@ int main( int argc, const char *argv[] )
|
||||||
}
|
}
|
||||||
|
|
||||||
setbuf( stdout, 0 );
|
setbuf( stdout, 0 );
|
||||||
if ( nph )
|
if ( nph ) {
|
||||||
{
|
|
||||||
fprintf( stdout, "HTTP/1.0 200 OK\r\n" );
|
fprintf( stdout, "HTTP/1.0 200 OK\r\n" );
|
||||||
}
|
}
|
||||||
fprintf( stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION );
|
fprintf( stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION );
|
||||||
|
@ -252,8 +219,7 @@ int main( int argc, const char *argv[] )
|
||||||
//fprintf( stdout, "Content-Length: 0\r\n");
|
//fprintf( stdout, "Content-Length: 0\r\n");
|
||||||
//}
|
//}
|
||||||
|
|
||||||
if ( source == ZMS_MONITOR )
|
if ( source == ZMS_MONITOR ) {
|
||||||
{
|
|
||||||
MonitorStream stream;
|
MonitorStream stream;
|
||||||
stream.setStreamScale( scale );
|
stream.setStreamScale( scale );
|
||||||
stream.setStreamReplayRate( rate );
|
stream.setStreamReplayRate( rate );
|
||||||
|
@ -269,24 +235,15 @@ int main( int argc, const char *argv[] )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( mode == ZMS_JPEG )
|
if ( mode == ZMS_JPEG ) {
|
||||||
{
|
|
||||||
stream.setStreamType( MonitorStream::STREAM_JPEG );
|
stream.setStreamType( MonitorStream::STREAM_JPEG );
|
||||||
}
|
} else if ( mode == ZMS_RAW ) {
|
||||||
else if ( mode == ZMS_RAW )
|
|
||||||
{
|
|
||||||
stream.setStreamType( MonitorStream::STREAM_RAW );
|
stream.setStreamType( MonitorStream::STREAM_RAW );
|
||||||
}
|
} else if ( mode == ZMS_ZIP ) {
|
||||||
else if ( mode == ZMS_ZIP )
|
|
||||||
{
|
|
||||||
stream.setStreamType( MonitorStream::STREAM_ZIP );
|
stream.setStreamType( MonitorStream::STREAM_ZIP );
|
||||||
}
|
} else if ( mode == ZMS_SINGLE ) {
|
||||||
else if ( mode == ZMS_SINGLE )
|
|
||||||
{
|
|
||||||
stream.setStreamType( MonitorStream::STREAM_SINGLE );
|
stream.setStreamType( MonitorStream::STREAM_SINGLE );
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
#if HAVE_LIBAVCODEC
|
#if HAVE_LIBAVCODEC
|
||||||
stream.setStreamFormat( format );
|
stream.setStreamFormat( format );
|
||||||
stream.setStreamBitrate( bitrate );
|
stream.setStreamBitrate( bitrate );
|
||||||
|
@ -300,29 +257,21 @@ int main( int argc, const char *argv[] )
|
||||||
#endif // HAVE_LIBAVCODEC
|
#endif // HAVE_LIBAVCODEC
|
||||||
}
|
}
|
||||||
stream.runStream();
|
stream.runStream();
|
||||||
}
|
} else if ( source == ZMS_EVENT ) {
|
||||||
else if ( source == ZMS_EVENT )
|
|
||||||
{
|
|
||||||
EventStream stream;
|
EventStream stream;
|
||||||
stream.setStreamScale( scale );
|
stream.setStreamScale( scale );
|
||||||
stream.setStreamReplayRate( rate );
|
stream.setStreamReplayRate( rate );
|
||||||
stream.setStreamMaxFPS( maxfps );
|
stream.setStreamMaxFPS( maxfps );
|
||||||
stream.setStreamMode( replay );
|
stream.setStreamMode( replay );
|
||||||
stream.setStreamQueue( connkey );
|
stream.setStreamQueue( connkey );
|
||||||
if ( monitor_id && event_time )
|
if ( monitor_id && event_time ) {
|
||||||
{
|
|
||||||
stream.setStreamStart( monitor_id, event_time );
|
stream.setStreamStart( monitor_id, event_time );
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
stream.setStreamStart( event_id, frame_id );
|
stream.setStreamStart( event_id, frame_id );
|
||||||
}
|
}
|
||||||
if ( mode == ZMS_JPEG )
|
if ( mode == ZMS_JPEG ) {
|
||||||
{
|
|
||||||
stream.setStreamType( EventStream::STREAM_JPEG );
|
stream.setStreamType( EventStream::STREAM_JPEG );
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
#if HAVE_LIBAVCODEC
|
#if HAVE_LIBAVCODEC
|
||||||
stream.setStreamFormat( format );
|
stream.setStreamFormat( format );
|
||||||
stream.setStreamBitrate( bitrate );
|
stream.setStreamBitrate( bitrate );
|
||||||
|
|
|
@ -259,23 +259,19 @@ fi
|
||||||
|
|
||||||
if [ $TYPE == "binary" ]; then
|
if [ $TYPE == "binary" ]; then
|
||||||
if [ "$INTERACTIVE" != "no" ]; then
|
if [ "$INTERACTIVE" != "no" ]; then
|
||||||
read -p "Not doing dput since it's a binary release. Do you want to install it? (Y/N)"
|
read -p "Not doing dput since it's a binary release. Do you want to install it? (y/N)"
|
||||||
if [[ $REPLY == [yY] ]]; then
|
if [[ $REPLY == [yY] ]]; then
|
||||||
sudo dpkg -i $DIRECTORY*.deb
|
sudo dpkg -i $DIRECTORY*.deb
|
||||||
else
|
|
||||||
echo $REPLY;
|
|
||||||
fi;
|
fi;
|
||||||
if [ "$DISTRO" == "jessie" ]; then
|
read -p "Do you want to upload this binary to zmrepo? (y/N)"
|
||||||
read -p "Do you want to upload this binary to zmrepo? (y/N)"
|
if [[ $REPLY == [yY] ]]; then
|
||||||
if [[ $REPLY == [yY] ]]; then
|
if [ "$RELEASE" != "" ]; then
|
||||||
if [ "$RELEASE" != "" ]; then
|
scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/stable/mini-dinstall/incoming/"
|
||||||
scp "zoneminder_${VERSION}-${DISTRO}*" "zmrepo@zmrepo.connortechnology.com:debian/stable/mini-dinstall/incoming/"
|
else
|
||||||
|
if [ "$BRANCH" == "" ]; then
|
||||||
|
scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/master/mini-dinstall/incoming/"
|
||||||
else
|
else
|
||||||
if [ "$BRANCH" == "" ]; then
|
scp "$DIRECTORY-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/${BRANCH}/mini-dinstall/incoming/"
|
||||||
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;
|
||||||
fi;
|
fi;
|
||||||
|
@ -295,11 +291,10 @@ else
|
||||||
|
|
||||||
dput="Y";
|
dput="Y";
|
||||||
if [ "$INTERACTIVE" != "no" ]; then
|
if [ "$INTERACTIVE" != "no" ]; then
|
||||||
echo "Ready to dput $SC to $PPA ? Y/N...";
|
read -p "Ready to dput $SC to $PPA ? Y/N...";
|
||||||
read dput
|
if [[ "$REPLY" == [yY] ]]; then
|
||||||
fi
|
dput $PPA $SC
|
||||||
if [ "$dput" == [Yy] ]; then
|
fi;
|
||||||
dput $PPA $SC
|
|
||||||
fi;
|
fi;
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ if [ "${TRAVIS_EVENT_TYPE}" == "cron" ]; then
|
||||||
|
|
||||||
# Don't keep packages older than 5 days
|
# Don't keep packages older than 5 days
|
||||||
find ./zmrepo/$targetfolder/ -maxdepth 1 -type f -mtime +5 -delete
|
find ./zmrepo/$targetfolder/ -maxdepth 1 -type f -mtime +5 -delete
|
||||||
rsync -vzh --ignore-errors build/* zmrepo/$targetfolder/
|
rsync -vzlh --ignore-errors build/* zmrepo/$targetfolder/
|
||||||
fusermount -zu zmrepo
|
fusermount -zu zmrepo
|
||||||
else
|
else
|
||||||
echo
|
echo
|
||||||
|
|
|
@ -95,6 +95,7 @@ switch ( $_REQUEST['task'] )
|
||||||
foreach ( dbFetchAll( $sql, NULL, $values ) as $log ) {
|
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['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() : '';
|
||||||
|
$log['Message'] = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $log['Message'] );
|
||||||
$logs[] = $log;
|
$logs[] = $log;
|
||||||
}
|
}
|
||||||
$options = array();
|
$options = array();
|
||||||
|
|
|
@ -101,49 +101,66 @@ CakeLog::config('debug', array(
|
||||||
'engine' => 'File',
|
'engine' => 'File',
|
||||||
'types' => array('notice', 'info', 'debug'),
|
'types' => array('notice', 'info', 'debug'),
|
||||||
'file' => 'cake_debug',
|
'file' => 'cake_debug',
|
||||||
|
'path' => '@ZM_LOGDIR@/'
|
||||||
));
|
));
|
||||||
CakeLog::config('error', array(
|
CakeLog::config('error', array(
|
||||||
'engine' => 'File',
|
'engine' => 'File',
|
||||||
'types' => array('warning', 'error', 'critical', 'alert', 'emergency'),
|
'types' => array('warning', 'error', 'critical', 'alert', 'emergency'),
|
||||||
'file' => 'cake_error',
|
'file' => 'cake_error',
|
||||||
|
'path' => '@ZM_LOGDIR@/'
|
||||||
));
|
));
|
||||||
CakeLog::config('custom_path', array(
|
CakeLog::config('custom_path', array(
|
||||||
'engine' => 'File',
|
'engine' => 'File',
|
||||||
'path' => '@ZM_LOGDIR@'
|
'path' => '@ZM_LOGDIR@/'
|
||||||
));
|
));
|
||||||
|
|
||||||
Configure::write('ZM_CONFIG', '@ZM_CONFIG@');
|
Configure::write('ZM_CONFIG', '@ZM_CONFIG@');
|
||||||
|
Configure::write('ZM_CONFIG_SUBDIR', '@ZM_CONFIG_SUBDIR@');
|
||||||
Configure::write('ZM_VERSION', '@VERSION@');
|
Configure::write('ZM_VERSION', '@VERSION@');
|
||||||
Configure::write('ZM_API_VERSION', '@API_VERSION@');
|
Configure::write('ZM_API_VERSION', '@API_VERSION@');
|
||||||
|
|
||||||
loadConfigFile();
|
# Process name, value pairs from the main config file first
|
||||||
|
$configvals = process_configfile(Configure::read('ZM_CONFIG'));
|
||||||
|
|
||||||
function loadConfigFile() {
|
# Search for user created config files. If one or more are found then
|
||||||
$configFile = Configure::read('ZM_CONFIG');
|
# update our config value array with those values
|
||||||
$localConfigFile = basename($configFile);
|
$configSubFolder = Configure::read('ZM_CONFIG_SUBDIR');
|
||||||
if ( file_exists( $localConfigFile ) && filesize( $localConfigFile ) > 0 )
|
if ( is_dir($configSubFolder) ) {
|
||||||
{
|
if ( is_readable($configSubFolder) ) {
|
||||||
if ( php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']) )
|
foreach ( glob("$configSubFolder/*.conf") as $filename ) {
|
||||||
print( "Warning, overriding installed $localConfigFile file with local copy\n" );
|
$configvals = array_replace($configvals, process_configfile($filename) );
|
||||||
else
|
}
|
||||||
error_log( "Warning, overriding installed $localConfigFile file with local copy" );
|
} else {
|
||||||
$configFile = $localConfigFile;
|
error_log( "WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on $configSubFolder." );
|
||||||
}
|
}
|
||||||
|
|
||||||
$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 )) {
|
|
||||||
Configure::write( $matches[1], $matches[2] );
|
|
||||||
define( $matches[1], $matches[2] );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fclose( $cfg );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 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 );
|
||||||
|
Configure::write( $key, $value );
|
||||||
|
}
|
||||||
|
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,9 @@ class DATABASE_CONFIG {
|
||||||
'login' => ZM_DB_USER,
|
'login' => ZM_DB_USER,
|
||||||
'password' => ZM_DB_PASS,
|
'password' => ZM_DB_PASS,
|
||||||
'database' => ZM_DB_NAME,
|
'database' => ZM_DB_NAME,
|
||||||
|
'ssl_ca' => ZM_DB_SSL_CA_CERT,
|
||||||
|
'ssl_key' => ZM_DB_SSL_CLIENT_KEY,
|
||||||
|
'ssl_cert' => ZM_DB_SSL_CLIENT_CERT,
|
||||||
'prefix' => '',
|
'prefix' => '',
|
||||||
'encoding' => 'utf8',
|
'encoding' => 'utf8',
|
||||||
);
|
);
|
||||||
|
|
|
@ -176,8 +176,8 @@ if ( !empty($action) ) {
|
||||||
} else {
|
} else {
|
||||||
Error("No new Id despite new name");
|
Error("No new Id despite new name");
|
||||||
}
|
}
|
||||||
|
$refreshParent = '/index.php?view=filter&Id='.$_REQUEST['Id'];
|
||||||
}
|
}
|
||||||
$refreshParent = '/index.php?view=filter&Id='.$_REQUEST['Id'];
|
|
||||||
}
|
}
|
||||||
} // end if canedit events
|
} // end if canedit events
|
||||||
} // end if action == filter
|
} // end if action == filter
|
||||||
|
|
|
@ -42,7 +42,12 @@ function dbConnect() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$dbConn = new PDO( ZM_DB_TYPE . $socket . ';dbname='.ZM_DB_NAME, ZM_DB_USER, ZM_DB_PASS );
|
$dbOptions = array(
|
||||||
|
PDO::MYSQL_ATTR_SSL_CA => ZM_DB_SSL_CA_CERT,
|
||||||
|
PDO::MYSQL_ATTR_SSL_KEY => ZM_DB_SSL_CLIENT_KEY,
|
||||||
|
PDO::MYSQL_ATTR_SSL_CERT => ZM_DB_SSL_CLIENT_CERT,
|
||||||
|
);
|
||||||
|
$dbConn = new PDO( ZM_DB_TYPE . $socket . ';dbname='.ZM_DB_NAME, ZM_DB_USER, ZM_DB_PASS, $dbOptions );
|
||||||
$dbConn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
|
$dbConn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
|
||||||
$dbConn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
$dbConn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
} catch(PDOException $ex ) {
|
} catch(PDOException $ex ) {
|
||||||
|
|
|
@ -2138,8 +2138,7 @@ function getSkinFile( $file ) {
|
||||||
return( $skinFile );
|
return( $skinFile );
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSkinIncludes( $file, $includeBase=false, $asOverride=false )
|
function getSkinIncludes( $file, $includeBase=false, $asOverride=false ) {
|
||||||
{
|
|
||||||
global $skinBase;
|
global $skinBase;
|
||||||
$skinFile = false;
|
$skinFile = false;
|
||||||
foreach ( $skinBase as $skin ) {
|
foreach ( $skinBase as $skin ) {
|
||||||
|
|
152
web/index.php
152
web/index.php
|
@ -21,29 +21,26 @@
|
||||||
error_reporting( E_ALL );
|
error_reporting( E_ALL );
|
||||||
|
|
||||||
$debug = false;
|
$debug = false;
|
||||||
if ( $debug )
|
if ( $debug ) {
|
||||||
{
|
// Use these for debugging, though not both at once!
|
||||||
// Use these for debugging, though not both at once!
|
phpinfo( INFO_VARIABLES );
|
||||||
phpinfo( INFO_VARIABLES );
|
//error_reporting( E_ALL );
|
||||||
//error_reporting( E_ALL );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use new style autoglobals where possible
|
// Use new style autoglobals where possible
|
||||||
if ( version_compare( phpversion(), "4.1.0", "<") )
|
if ( version_compare( phpversion(), '4.1.0', '<') ) {
|
||||||
{
|
$_SESSION = &$HTTP_SESSION_VARS;
|
||||||
$_SESSION = &$HTTP_SESSION_VARS;
|
$_SERVER = &$HTTP_SERVER_VARS;
|
||||||
$_SERVER = &$HTTP_SERVER_VARS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Useful debugging lines for mobile devices
|
// Useful debugging lines for mobile devices
|
||||||
if ( false )
|
if ( false ) {
|
||||||
{
|
ob_start();
|
||||||
ob_start();
|
phpinfo( INFO_VARIABLES );
|
||||||
phpinfo( INFO_VARIABLES );
|
$fp = fopen( '/tmp/env.html', 'w' );
|
||||||
$fp = fopen( "/tmp/env.html", "w" );
|
fwrite( $fp, ob_get_contents() );
|
||||||
fwrite( $fp, ob_get_contents() );
|
fclose( $fp );
|
||||||
fclose( $fp );
|
ob_end_clean();
|
||||||
ob_end_clean();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once( 'includes/config.php' );
|
require_once( 'includes/config.php' );
|
||||||
|
@ -53,26 +50,23 @@ require_once( 'includes/Storage.php' );
|
||||||
require_once( 'includes/Event.php' );
|
require_once( 'includes/Event.php' );
|
||||||
require_once( 'includes/Monitor.php' );
|
require_once( 'includes/Monitor.php' );
|
||||||
|
|
||||||
if ( isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == 'on' )
|
if ( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ) {
|
||||||
{
|
$protocol = 'https';
|
||||||
$protocol = 'https';
|
} else {
|
||||||
|
$protocol = 'http';
|
||||||
}
|
}
|
||||||
else
|
define( 'ZM_BASE_PROTOCOL', $protocol );
|
||||||
{
|
|
||||||
$protocol = 'http';
|
|
||||||
}
|
|
||||||
define( "ZM_BASE_PROTOCOL", $protocol );
|
|
||||||
|
|
||||||
// Absolute URL's are unnecessary and break compatibility with reverse proxies
|
// Absolute URL's are unnecessary and break compatibility with reverse proxies
|
||||||
// define( "ZM_BASE_URL", $protocol.'://'.$_SERVER['HTTP_HOST'] );
|
// define( "ZM_BASE_URL", $protocol.'://'.$_SERVER['HTTP_HOST'] );
|
||||||
|
|
||||||
// Use relative URL's instead
|
// Use relative URL's instead
|
||||||
define( "ZM_BASE_URL", "" );
|
define( 'ZM_BASE_URL', '' );
|
||||||
|
|
||||||
// Check time zone is set
|
// Check time zone is set
|
||||||
if (!ini_get('date.timezone') || !date_default_timezone_set(ini_get('date.timezone'))) {
|
if (!ini_get('date.timezone') || !date_default_timezone_set(ini_get('date.timezone'))) {
|
||||||
date_default_timezone_set('UTC');
|
date_default_timezone_set('UTC');
|
||||||
Fatal( "ZoneMinder is not installed properly: php's date.timezone is not set to a valid timezone" );
|
Fatal( "ZoneMinder is not installed properly: php's date.timezone is not set to a valid timezone" );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isset($_GET['skin']) )
|
if ( isset($_GET['skin']) )
|
||||||
|
@ -82,7 +76,7 @@ elseif ( isset($_COOKIE['zmSkin']) )
|
||||||
elseif ( defined('ZM_SKIN_DEFAULT') )
|
elseif ( defined('ZM_SKIN_DEFAULT') )
|
||||||
$skin = ZM_SKIN_DEFAULT;
|
$skin = ZM_SKIN_DEFAULT;
|
||||||
else
|
else
|
||||||
$skin = "classic";
|
$skin = 'classic';
|
||||||
|
|
||||||
$skins = array_map( 'basename', glob('skins/*',GLOB_ONLYDIR) );
|
$skins = array_map( 'basename', glob('skins/*',GLOB_ONLYDIR) );
|
||||||
if ( ! in_array( $skin, $skins ) ) {
|
if ( ! in_array( $skin, $skins ) ) {
|
||||||
|
@ -97,7 +91,7 @@ elseif ( isset($_COOKIE['zmCSS']) )
|
||||||
elseif (defined('ZM_CSS_DEFAULT'))
|
elseif (defined('ZM_CSS_DEFAULT'))
|
||||||
$css = ZM_CSS_DEFAULT;
|
$css = ZM_CSS_DEFAULT;
|
||||||
else
|
else
|
||||||
$css = "classic";
|
$css = 'classic';
|
||||||
|
|
||||||
$css_skins = array_map( 'basename', glob('skins/'.$skin.'/css/*',GLOB_ONLYDIR) );
|
$css_skins = array_map( 'basename', glob('skins/'.$skin.'/css/*',GLOB_ONLYDIR) );
|
||||||
if ( ! in_array( $css, $css_skins ) ) {
|
if ( ! in_array( $css, $css_skins ) ) {
|
||||||
|
@ -105,9 +99,9 @@ if ( ! in_array( $css, $css_skins ) ) {
|
||||||
$css = $css_skins[0];
|
$css = $css_skins[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
define( "ZM_BASE_PATH", dirname( $_SERVER['REQUEST_URI'] ) );
|
define( 'ZM_BASE_PATH', dirname( $_SERVER['REQUEST_URI'] ) );
|
||||||
define( "ZM_SKIN_NAME", $skin );
|
define( 'ZM_SKIN_NAME', $skin );
|
||||||
define( "ZM_SKIN_PATH", "skins/$skin" );
|
define( 'ZM_SKIN_PATH', "skins/$skin" );
|
||||||
|
|
||||||
$skinBase = array(); // To allow for inheritance of skins
|
$skinBase = array(); // To allow for inheritance of skins
|
||||||
if ( !file_exists( ZM_SKIN_PATH ) )
|
if ( !file_exists( ZM_SKIN_PATH ) )
|
||||||
|
@ -117,26 +111,25 @@ $skinBase[] = $skin;
|
||||||
$currentCookieParams = session_get_cookie_params();
|
$currentCookieParams = session_get_cookie_params();
|
||||||
Logger::Debug('Setting cookie parameters to lifetime('.$currentCookieParams['lifetime'].') path('.$currentCookieParams['path'].') domain ('.$currentCookieParams['domain'].') secure('.$currentCookieParams['secure'].') httpOnly(1)');
|
Logger::Debug('Setting cookie parameters to lifetime('.$currentCookieParams['lifetime'].') path('.$currentCookieParams['path'].') domain ('.$currentCookieParams['domain'].') secure('.$currentCookieParams['secure'].') httpOnly(1)');
|
||||||
session_set_cookie_params(
|
session_set_cookie_params(
|
||||||
$currentCookieParams["lifetime"],
|
$currentCookieParams['lifetime'],
|
||||||
$currentCookieParams["path"],
|
$currentCookieParams['path'],
|
||||||
$currentCookieParams["domain"],
|
$currentCookieParams['domain'],
|
||||||
$currentCookieParams["secure"],
|
$currentCookieParams['secure'],
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
ini_set( "session.name", "ZMSESSID" );
|
ini_set( 'session.name', 'ZMSESSID' );
|
||||||
|
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
if ( !isset($_SESSION['skin']) || isset($_REQUEST['skin']) || !isset($_COOKIE['zmSkin']) || $_COOKIE['zmSkin'] != $skin )
|
if ( !isset($_SESSION['skin']) || isset($_REQUEST['skin']) || !isset($_COOKIE['zmSkin']) || $_COOKIE['zmSkin'] != $skin ) {
|
||||||
{
|
|
||||||
$_SESSION['skin'] = $skin;
|
$_SESSION['skin'] = $skin;
|
||||||
setcookie( "zmSkin", $skin, time()+3600*24*30*12*10 );
|
setcookie( 'zmSkin', $skin, time()+3600*24*30*12*10 );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !isset($_SESSION['css']) || isset($_REQUEST['css']) || !isset($_COOKIE['zmCSS']) || $_COOKIE['zmCSS'] != $css ) {
|
if ( !isset($_SESSION['css']) || isset($_REQUEST['css']) || !isset($_COOKIE['zmCSS']) || $_COOKIE['zmCSS'] != $css ) {
|
||||||
$_SESSION['css'] = $css;
|
$_SESSION['css'] = $css;
|
||||||
setcookie( "zmCSS", $css, time()+3600*24*30*12*10 );
|
setcookie( 'zmCSS', $css, time()+3600*24*30*12*10 );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ZM_OPT_USE_AUTH )
|
if ( ZM_OPT_USE_AUTH )
|
||||||
|
@ -149,14 +142,12 @@ else
|
||||||
|
|
||||||
require_once( 'includes/lang.php' );
|
require_once( 'includes/lang.php' );
|
||||||
require_once( 'includes/functions.php' );
|
require_once( 'includes/functions.php' );
|
||||||
require_once( 'includes/csrf/csrf-magic.php' );
|
|
||||||
|
|
||||||
# Add Cross domain access headers
|
# Add Cross domain access headers
|
||||||
CORSHeaders();
|
CORSHeaders();
|
||||||
|
|
||||||
// Check for valid content dirs
|
// Check for valid content dirs
|
||||||
if ( !is_writable(ZM_DIR_EVENTS) || !is_writable(ZM_DIR_IMAGES) )
|
if ( !is_writable(ZM_DIR_EVENTS) || !is_writable(ZM_DIR_IMAGES) ) {
|
||||||
{
|
|
||||||
Error( "Cannot write to content dirs('".ZM_DIR_EVENTS."','".ZM_DIR_IMAGES."'). Check that these exist and are owned by the web account user");
|
Error( "Cannot write to content dirs('".ZM_DIR_EVENTS."','".ZM_DIR_IMAGES."'). Check that these exist and are owned by the web account user");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,58 +168,51 @@ isset($view) || $view = NULL;
|
||||||
isset($request) || $request = NULL;
|
isset($request) || $request = NULL;
|
||||||
isset($action) || $action = NULL;
|
isset($action) || $action = NULL;
|
||||||
|
|
||||||
if ( ZM_ENABLE_CSRF_MAGIC && $action != 'login' ) {
|
if ( ZM_ENABLE_CSRF_MAGIC && $action != 'login' && $view != 'view_video' ) {
|
||||||
Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\"");
|
require_once( 'includes/csrf/csrf-magic.php' );
|
||||||
csrf_check();
|
Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\"");
|
||||||
|
csrf_check();
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once( 'includes/actions.php' );
|
require_once( 'includes/actions.php' );
|
||||||
|
|
||||||
# If I put this here, it protects all views and popups, but it has to go after actions.php because actions.php does the actual logging in.
|
# If I put this here, it protects all views and popups, but it has to go after actions.php because actions.php does the actual logging in.
|
||||||
if ( ZM_OPT_USE_AUTH && ! isset($user) && $view != 'login' ) {
|
if ( ZM_OPT_USE_AUTH && ! isset($user) && $view != 'login' ) {
|
||||||
$view = 'login';
|
$view = 'login';
|
||||||
}
|
}
|
||||||
|
|
||||||
# Only one request can open the session file at a time, so let's close the session here to improve concurrency.
|
# Only one request can open the session file at a time, so let's close the session here to improve concurrency.
|
||||||
# Any file/page that uses the session must re-open it.
|
# Any file/page that uses the session must re-open it.
|
||||||
session_write_close();
|
session_write_close();
|
||||||
|
|
||||||
if ( isset( $_REQUEST['request'] ) )
|
if ( isset( $_REQUEST['request'] ) ) {
|
||||||
{
|
foreach ( getSkinIncludes( 'ajax/'.$request.'.php', true, true ) as $includeFile ) {
|
||||||
foreach ( getSkinIncludes( 'ajax/'.$request.'.php', true, true ) as $includeFile )
|
if ( !file_exists( $includeFile ) )
|
||||||
{
|
Fatal( "Request '$request' does not exist" );
|
||||||
if ( !file_exists( $includeFile ) )
|
require_once $includeFile;
|
||||||
Fatal( "Request '$request' does not exist" );
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if ( $includeFiles = getSkinIncludes( 'views/'.$view.'.php', true, true ) ) {
|
||||||
|
foreach ( $includeFiles as $includeFile ) {
|
||||||
|
if ( !file_exists( $includeFile ) )
|
||||||
|
Fatal( "View '$view' does not exist" );
|
||||||
|
require_once $includeFile;
|
||||||
|
}
|
||||||
|
// If the view overrides $view to 'error', and the user is not logged in, then the
|
||||||
|
// issue is probably resolvable by logging in, so provide the opportunity to do so.
|
||||||
|
// The login view should handle redirecting to the correct location afterward.
|
||||||
|
if ( $view == 'error' && !isset($user) ) {
|
||||||
|
$view = 'login';
|
||||||
|
foreach ( getSkinIncludes( 'views/login.php', true, true ) as $includeFile )
|
||||||
require_once $includeFile;
|
require_once $includeFile;
|
||||||
}
|
}
|
||||||
return;
|
}
|
||||||
}
|
// If the view is missing or the view still returned error with the user logged in,
|
||||||
else
|
// then it is not recoverable.
|
||||||
{
|
if ( !$includeFiles || $view == 'error' ) {
|
||||||
if ( $includeFiles = getSkinIncludes( 'views/'.$view.'.php', true, true ) )
|
foreach ( getSkinIncludes( 'views/error.php', true, true ) as $includeFile )
|
||||||
{
|
require_once $includeFile;
|
||||||
foreach ( $includeFiles as $includeFile )
|
}
|
||||||
{
|
|
||||||
if ( !file_exists( $includeFile ) )
|
|
||||||
Fatal( "View '$view' does not exist" );
|
|
||||||
require_once $includeFile;
|
|
||||||
}
|
|
||||||
// If the view overrides $view to 'error', and the user is not logged in, then the
|
|
||||||
// issue is probably resolvable by logging in, so provide the opportunity to do so.
|
|
||||||
// The login view should handle redirecting to the correct location afterward.
|
|
||||||
if ( $view == 'error' && !isset($user) )
|
|
||||||
{
|
|
||||||
$view = 'login';
|
|
||||||
foreach ( getSkinIncludes( 'views/login.php', true, true ) as $includeFile )
|
|
||||||
require_once $includeFile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the view is missing or the view still returned error with the user logged in,
|
|
||||||
// then it is not recoverable.
|
|
||||||
if ( !$includeFiles || $view == 'error' )
|
|
||||||
{
|
|
||||||
foreach ( getSkinIncludes( 'views/error.php', true, true ) as $includeFile )
|
|
||||||
require_once $includeFile;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -79,8 +79,8 @@
|
||||||
border: 1px solid #006699;
|
border: 1px solid #006699;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
width: 96px;
|
width: 100px;
|
||||||
height: 96px;
|
height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .arrowBtn {
|
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .arrowBtn {
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
#monitors .imageFeed img {
|
#monitors .imageFeed img {
|
||||||
border: 2px solid #ffffff;
|
border: 2px solid #ffffff;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#monitors .imageFeed img.idle {
|
#monitors .imageFeed img.idle {
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
.ptzControls input.ptzTextBtn {
|
.ptzControls input.ptzTextBtn {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
width: 40px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ptzControls .controlsPanel {
|
.ptzControls .controlsPanel {
|
||||||
|
@ -79,8 +78,8 @@
|
||||||
border: 1px solid #006699;
|
border: 1px solid #006699;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
width: 96px;
|
width: 100px;
|
||||||
height: 96px;
|
height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .arrowBtn {
|
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .arrowBtn {
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
#monitors .imageFeed img {
|
#monitors .imageFeed img {
|
||||||
border: 2px solid #999999;
|
border: 2px solid #999999;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#monitors .imageFeed img.idle {
|
#monitors .imageFeed img.idle {
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
.ptzControls input.ptzTextBtn {
|
.ptzControls input.ptzTextBtn {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
width: 40px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ptzControls .controlsPanel {
|
.ptzControls .controlsPanel {
|
||||||
|
@ -79,8 +78,8 @@
|
||||||
border: 1px solid #006699;
|
border: 1px solid #006699;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
width: 96px;
|
width: 100px;
|
||||||
height: 96px;
|
height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .arrowBtn {
|
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .arrowBtn {
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
#monitors .imageFeed img {
|
#monitors .imageFeed img {
|
||||||
border: 2px solid #ffffff;
|
border: 2px solid #ffffff;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#monitors .imageFeed img.idle {
|
#monitors .imageFeed img.idle {
|
||||||
|
|
|
@ -25,264 +25,227 @@
|
||||||
var popupOptions = "resizable,scrollbars,status=no";
|
var popupOptions = "resizable,scrollbars,status=no";
|
||||||
|
|
||||||
function checkSize() {
|
function checkSize() {
|
||||||
if (window.outerHeight) {
|
if (window.outerHeight) {
|
||||||
var w = window.outerWidth;
|
var w = window.outerWidth;
|
||||||
var prevW = w;
|
var prevW = w;
|
||||||
var h = window.outerHeight;
|
var h = window.outerHeight;
|
||||||
var prevH = h;
|
var prevH = h;
|
||||||
if (h > screen.availHeight)
|
if (h > screen.availHeight)
|
||||||
h = screen.availHeight;
|
h = screen.availHeight;
|
||||||
if (w > screen.availWidth)
|
if (w > screen.availWidth)
|
||||||
w = screen.availWidth;
|
w = screen.availWidth;
|
||||||
if (w != prevW || h != prevH)
|
if (w != prevW || h != prevH)
|
||||||
window.resizeTo(w, h);
|
window.resizeTo(w, h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
function newWindow( url, name, width, height )
|
function newWindow( url, name, width, height ) {
|
||||||
{
|
var windowId = window.open( url, name, popupOptions+",width="+width+",height="+height );
|
||||||
var windowId = window.open( url, name, popupOptions+",width="+width+",height="+height );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPopupSize( tag, width, height )
|
function getPopupSize( tag, width, height ) {
|
||||||
{
|
var popupSize = Object.clone( popupSizes[tag] );
|
||||||
var popupSize = Object.clone( popupSizes[tag] );
|
if ( !popupSize ) {
|
||||||
if ( !popupSize )
|
Error( "Can't find window size for tag '"+tag+"'" );
|
||||||
{
|
return( { 'width': 0, 'height': 0 } );
|
||||||
Error( "Can't find window size for tag '"+tag+"'" );
|
}
|
||||||
return( { 'width': 0, 'height': 0 } );
|
if ( popupSize.width && popupSize.height ) {
|
||||||
}
|
if ( width || height )
|
||||||
if ( popupSize.width && popupSize.height )
|
Warning( "Ignoring passed dimensions "+width+"x"+height+" when getting popup size for tag '"+tag+"'" );
|
||||||
{
|
|
||||||
if ( width || height )
|
|
||||||
Warning( "Ignoring passed dimensions "+width+"x"+height+" when getting popup size for tag '"+tag+"'" );
|
|
||||||
return( popupSize );
|
|
||||||
}
|
|
||||||
if ( popupSize.addWidth )
|
|
||||||
{
|
|
||||||
popupSize.width = popupSize.addWidth;
|
|
||||||
if ( !width )
|
|
||||||
Error( "Got addWidth but no passed width when getting popup size for tag '"+tag+"'" );
|
|
||||||
else
|
|
||||||
popupSize.width += parseInt(width);
|
|
||||||
}
|
|
||||||
else if ( width )
|
|
||||||
{
|
|
||||||
popupSize.width = width;
|
|
||||||
Error( "Got passed width but no addWidth when getting popup size for tag '"+tag+"'" );
|
|
||||||
}
|
|
||||||
if ( popupSize.minWidth && popupSize.width < popupSize.minWidth )
|
|
||||||
{
|
|
||||||
Warning( "Adjusting to minimum width when getting popup size for tag '"+tag+"'" );
|
|
||||||
popupSize.width = popupSize.minWidth;
|
|
||||||
}
|
|
||||||
if ( popupSize.addHeight )
|
|
||||||
{
|
|
||||||
popupSize.height = popupSize.addHeight;
|
|
||||||
if ( !height )
|
|
||||||
Error( "Got addHeight but no passed height when getting popup size for tag '"+tag+"'" );
|
|
||||||
else
|
|
||||||
popupSize.height += parseInt(height);
|
|
||||||
}
|
|
||||||
else if ( height )
|
|
||||||
{
|
|
||||||
popupSize.height = height;
|
|
||||||
Error( "Got passed height but no addHeight when getting popup size for tag '"+tag+"'" );
|
|
||||||
}
|
|
||||||
if ( popupSize.minHeight && popupSize.height < popupSize.minHeight )
|
|
||||||
{
|
|
||||||
Warning( "Adjusting to minimum height when getting popup size for tag '"+tag+"'" );
|
|
||||||
popupSize.height = popupSize.minHeight;
|
|
||||||
}
|
|
||||||
Debug( popupSize );
|
|
||||||
return( popupSize );
|
return( popupSize );
|
||||||
|
}
|
||||||
|
if ( popupSize.addWidth ) {
|
||||||
|
popupSize.width = popupSize.addWidth;
|
||||||
|
if ( !width )
|
||||||
|
Error( "Got addWidth but no passed width when getting popup size for tag '"+tag+"'" );
|
||||||
|
else
|
||||||
|
popupSize.width += parseInt(width);
|
||||||
|
} else if ( width ) {
|
||||||
|
popupSize.width = width;
|
||||||
|
Error( "Got passed width but no addWidth when getting popup size for tag '"+tag+"'" );
|
||||||
|
}
|
||||||
|
if ( popupSize.minWidth && popupSize.width < popupSize.minWidth ) {
|
||||||
|
Warning( "Adjusting to minimum width when getting popup size for tag '"+tag+"'" );
|
||||||
|
popupSize.width = popupSize.minWidth;
|
||||||
|
}
|
||||||
|
if ( popupSize.addHeight ) {
|
||||||
|
popupSize.height = popupSize.addHeight;
|
||||||
|
if ( !height )
|
||||||
|
Error( "Got addHeight but no passed height when getting popup size for tag '"+tag+"'" );
|
||||||
|
else
|
||||||
|
popupSize.height += parseInt(height);
|
||||||
|
} else if ( height ) {
|
||||||
|
popupSize.height = height;
|
||||||
|
Error( "Got passed height but no addHeight when getting popup size for tag '"+tag+"'" );
|
||||||
|
}
|
||||||
|
if ( popupSize.minHeight && ( popupSize.height < popupSize.minHeight ) ) {
|
||||||
|
Warning( "Adjusting to minimum height ("+popupSize.minHeight+") when getting popup size for tag '"+tag+"' because calculated height is " + popupSize.height );
|
||||||
|
popupSize.height = popupSize.minHeight;
|
||||||
|
}
|
||||||
|
Debug( popupSize );
|
||||||
|
return( popupSize );
|
||||||
}
|
}
|
||||||
|
|
||||||
function zmWindow()
|
function zmWindow() {
|
||||||
{
|
var zmWin = window.open( 'http://www.zoneminder.com', 'ZoneMinder' );
|
||||||
var zmWin = window.open( 'http://www.zoneminder.com', 'ZoneMinder' );
|
if ( ! zmWin ) {
|
||||||
if ( ! zmWin ) {
|
// if popup blocking is enabled, the popup won't be defined.
|
||||||
// if popup blocking is enabled, the popup won't be defined.
|
console.log("Please disable popup blocking.");
|
||||||
console.log("Please disable popup blocking.");
|
} else {
|
||||||
} else {
|
zmWin.focus();
|
||||||
zmWin.focus();
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPopup( url, name, tag, width, height )
|
function createPopup( url, name, tag, width, height ) {
|
||||||
{
|
var popupSize = getPopupSize( tag, width, height );
|
||||||
var popupSize = getPopupSize( tag, width, height );
|
var popupDimensions = "";
|
||||||
var popupDimensions = "";
|
if ( popupSize.width > 0 )
|
||||||
if ( popupSize.width > 0 )
|
popupDimensions += ",width="+popupSize.width;
|
||||||
popupDimensions += ",width="+popupSize.width;
|
if ( popupSize.height > 0 )
|
||||||
if ( popupSize.height > 0 )
|
popupDimensions += ",height="+popupSize.height;
|
||||||
popupDimensions += ",height="+popupSize.height;
|
var popup = window.open( url, name, popupOptions+popupDimensions );
|
||||||
var popup = window.open( url, name, popupOptions+popupDimensions );
|
if ( ! popup ) {
|
||||||
if ( ! popup ) {
|
// if popup blocking is enabled, the popup won't be defined.
|
||||||
// if popup blocking is enabled, the popup won't be defined.
|
console.log("Please disable popup blocking.");
|
||||||
console.log("Please disable popup blocking.");
|
} else {
|
||||||
} else {
|
popup.focus();
|
||||||
popup.focus();
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createEventPopup( eventId, eventFilter, width, height )
|
function createEventPopup( eventId, eventFilter, width, height ) {
|
||||||
{
|
var url = '?view=event&eid='+eventId;
|
||||||
var url = '?view=event&eid='+eventId;
|
if ( eventFilter )
|
||||||
if ( eventFilter )
|
url += eventFilter;
|
||||||
url += eventFilter;
|
var name = 'zmEvent';
|
||||||
var name = 'zmEvent';
|
var popupSize = getPopupSize( 'event', width, height );
|
||||||
var popupSize = getPopupSize( 'event', width, height );
|
var popup = window.open( url, name, popupOptions+",width="+popupSize.width+",height="+popupSize.height );
|
||||||
var popup = window.open( url, name, popupOptions+",width="+popupSize.width+",height="+popupSize.height );
|
if ( ! popup ) {
|
||||||
if ( ! popup ) {
|
// if popup blocking is enabled, the popup won't be defined.
|
||||||
// if popup blocking is enabled, the popup won't be defined.
|
console.log("Please disable popup blocking.");
|
||||||
console.log("Please disable popup blocking.");
|
} else {
|
||||||
} else {
|
popup.focus();
|
||||||
popup.focus();
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFramesPopup( eventId, width, height )
|
function createFramesPopup( eventId, width, height ) {
|
||||||
{
|
var url = '?view=frames&eid='+eventId;
|
||||||
var url = '?view=frames&eid='+eventId;
|
var name = 'zmFrames';
|
||||||
var name = 'zmFrames';
|
var popupSize = getPopupSize( 'frames', width, height );
|
||||||
var popupSize = getPopupSize( 'frames', width, height );
|
var popup = window.open( url, name, popupOptions+",width="+popupSize.width+",height="+popupSize.height );
|
||||||
var popup = window.open( url, name, popupOptions+",width="+popupSize.width+",height="+popupSize.height );
|
if ( ! popup ) {
|
||||||
if ( ! popup ) {
|
// if popup blocking is enabled, the popup won't be defined.
|
||||||
// if popup blocking is enabled, the popup won't be defined.
|
console.log("Please disable popup blocking.");
|
||||||
console.log("Please disable popup blocking.");
|
} else {
|
||||||
} else {
|
popup.focus();
|
||||||
popup.focus();
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFramePopup( eventId, frameId, width, height )
|
function createFramePopup( eventId, frameId, width, height ) {
|
||||||
{
|
var url = '?view=frame&eid='+eventId+'&fid='+frameId;
|
||||||
var url = '?view=frame&eid='+eventId+'&fid='+frameId;
|
var name = 'zmFrame';
|
||||||
var name = 'zmFrame';
|
var popupSize = getPopupSize( 'frame', width, height );
|
||||||
var popupSize = getPopupSize( 'frame', width, height );
|
var popup = window.open( url, name, popupOptions+",width="+popupSize.width+",height="+popupSize.height );
|
||||||
var popup = window.open( url, name, popupOptions+",width="+popupSize.width+",height="+popupSize.height );
|
if ( ! popup ) {
|
||||||
if ( ! popup ) {
|
// if popup blocking is enabled, the popup won't be defined.
|
||||||
// if popup blocking is enabled, the popup won't be defined.
|
console.log("Please disable popup blocking.");
|
||||||
console.log("Please disable popup blocking.");
|
} else {
|
||||||
} else {
|
popup.focus();
|
||||||
popup.focus();
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function windowToFront()
|
function windowToFront() {
|
||||||
{
|
top.window.focus();
|
||||||
top.window.focus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeWindow()
|
function closeWindow() {
|
||||||
{
|
top.window.close();
|
||||||
top.window.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshWindow()
|
function refreshWindow() {
|
||||||
{
|
window.location.reload( true );
|
||||||
window.location.reload( true );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshParentWindow()
|
function refreshParentWindow() {
|
||||||
{
|
if ( window.opener )
|
||||||
if ( window.opener )
|
window.opener.location.reload( true );
|
||||||
window.opener.location.reload( true );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Shows a message if there is an error in the streamObj or the stream doesn't exist. Returns true if error, false otherwise.
|
//Shows a message if there is an error in the streamObj or the stream doesn't exist. Returns true if error, false otherwise.
|
||||||
function checkStreamForErrors( funcName, streamObj )
|
function checkStreamForErrors( funcName, streamObj ) {
|
||||||
{
|
if ( !streamObj ) {
|
||||||
if ( !streamObj )
|
Error( funcName+": stream object was null" );
|
||||||
{
|
return true;
|
||||||
Error( funcName+": stream object was null" );
|
}
|
||||||
return true;
|
if ( streamObj.result == "Error" ) {
|
||||||
}
|
Error( funcName+" stream error: "+streamObj.message );
|
||||||
if ( streamObj.result == "Error" )
|
return true;
|
||||||
{
|
}
|
||||||
Error( funcName+" stream error: "+streamObj.message );
|
return false;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function secsToTime( seconds )
|
function secsToTime( seconds ) {
|
||||||
{
|
var timeString = "--";
|
||||||
var timeString = "--";
|
if ( seconds < 60 ) {
|
||||||
if ( seconds < 60 )
|
timeString = seconds.toString();
|
||||||
timeString = seconds.toString();
|
} else if ( seconds < 60*60 ) {
|
||||||
else if ( seconds < 60*60 )
|
var timeMins = parseInt(seconds/60);
|
||||||
{
|
var timeSecs = seconds%60;
|
||||||
var timeMins = parseInt(seconds/60);
|
if ( timeSecs < 10 )
|
||||||
var timeSecs = seconds%60;
|
timeSecs = '0'+timeSecs.toString().substr( 0, 4 );
|
||||||
if ( timeSecs < 10 )
|
|
||||||
timeSecs = '0'+timeSecs.toString().substr( 0, 4 );
|
|
||||||
else
|
|
||||||
timeSecs = timeSecs.toString().substr( 0, 5 );
|
|
||||||
timeString = timeMins+":"+timeSecs;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
timeSecs = timeSecs.toString().substr( 0, 5 );
|
||||||
var timeHours = parseInt(seconds/3600);
|
timeString = timeMins+":"+timeSecs;
|
||||||
var timeMins = (seconds%3600)/60;
|
} else {
|
||||||
var timeSecs = seconds%60;
|
var timeHours = parseInt(seconds/3600);
|
||||||
if ( timeMins < 10 )
|
var timeMins = (seconds%3600)/60;
|
||||||
timeMins = '0'+timeMins.toString().substr( 0, 4 );
|
var timeSecs = seconds%60;
|
||||||
else
|
if ( timeMins < 10 )
|
||||||
timeMins = timeMins.toString().substr( 0, 5 );
|
timeMins = '0'+timeMins.toString().substr( 0, 4 );
|
||||||
if ( timeSecs < 10 )
|
else
|
||||||
timeSecs = '0'+timeSecs.toString().substr( 0, 4 );
|
timeMins = timeMins.toString().substr( 0, 5 );
|
||||||
else
|
if ( timeSecs < 10 )
|
||||||
timeSecs = timeSecs.toString().substr( 0, 5 );
|
timeSecs = '0'+timeSecs.toString().substr( 0, 4 );
|
||||||
timeString = timeHours+":"+timeMins+":"+timeSecs;
|
else
|
||||||
}
|
timeSecs = timeSecs.toString().substr( 0, 5 );
|
||||||
return( timeString );
|
timeString = timeHours+":"+timeMins+":"+timeSecs;
|
||||||
|
}
|
||||||
|
return( timeString );
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitTab( tab )
|
function submitTab( tab ) {
|
||||||
{
|
var form = $('contentForm');
|
||||||
var form = $('contentForm');
|
form.action.value = "";
|
||||||
form.action.value = "";
|
form.tab.value = tab;
|
||||||
form.tab.value = tab;
|
form.submit();
|
||||||
form.submit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureDeleteButton( element )
|
function configureDeleteButton( element ) {
|
||||||
{
|
var form = element.form;
|
||||||
var form = element.form;
|
var checked = element.checked;
|
||||||
var checked = element.checked;
|
if ( !checked ) {
|
||||||
if ( !checked )
|
for ( var i = 0; i < form.elements.length; i++ ) {
|
||||||
{
|
if ( form.elements[i].name == element.name ) {
|
||||||
for ( var i = 0; i < form.elements.length; i++ )
|
if ( form.elements[i].checked ) {
|
||||||
{
|
checked = true;
|
||||||
if ( form.elements[i].name == element.name )
|
break;
|
||||||
{
|
|
||||||
if ( form.elements[i].checked )
|
|
||||||
{
|
|
||||||
checked = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
form.deleteBtn.disabled = !checked;
|
}
|
||||||
|
form.deleteBtn.disabled = !checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmDelete( message )
|
function confirmDelete( message ) {
|
||||||
{
|
return( confirm( message?message:'Are you sure you wish to delete?' ) );
|
||||||
return( confirm( message?message:'Are you sure you wish to delete?' ) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( refreshParent )
|
if ( refreshParent ) {
|
||||||
{
|
refreshParentWindow();
|
||||||
refreshParentWindow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( focusWindow )
|
if ( focusWindow ) {
|
||||||
{
|
windowToFront();
|
||||||
windowToFront();
|
|
||||||
}
|
}
|
||||||
window.addEvent( 'domready', checkSize);
|
window.addEvent( 'domready', checkSize);
|
||||||
|
|
||||||
|
@ -320,4 +283,3 @@ function addVideoTimingTrack(video, LabelFormat, monitorName, duration, startTim
|
||||||
track.src = 'data:plain/text;charset=utf-8,'+encodeURIComponent(webvttdata);
|
track.src = 'data:plain/text;charset=utf-8,'+encodeURIComponent(webvttdata);
|
||||||
video.appendChild(track);
|
video.appendChild(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -155,7 +155,11 @@ xhtmlHeaders( __FILE__, translate('Console') );
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<h3 id="systemTime"><?php echo preg_match( '/%/', DATE_FMT_CONSOLE_LONG )?strftime( DATE_FMT_CONSOLE_LONG ):date( DATE_FMT_CONSOLE_LONG ) ?></h3>
|
<h3 id="systemTime"><?php echo preg_match( '/%/', DATE_FMT_CONSOLE_LONG )?strftime( DATE_FMT_CONSOLE_LONG ):date( DATE_FMT_CONSOLE_LONG ) ?></h3>
|
||||||
<h3 id="systemStats"><?php echo translate('Load') ?>: <?php echo getLoad() ?> - <?php echo translate('Disk') ?>: <?php echo getDiskPercent() ?>% - <?php echo ZM_PATH_MAP ?>: <?php echo getDiskPercent(ZM_PATH_MAP) ?>%</h3>
|
<h3 id="systemStats"><?php echo translate('Load') ?>: <?php echo getLoad() ?> - <?php echo translate('Disk') ?>: <?php echo getDiskPercent() ?>% - <?php echo ZM_PATH_MAP ?>: <?php echo getDiskPercent(ZM_PATH_MAP) ?>%</h3>
|
||||||
<h2 id="title"><a href="http://www.zoneminder.com" target="ZoneMinder">ZoneMinder</a> <?php echo translate('Console') ?> - <?php echo makePopupLink( '?view=state', 'zmState', 'state', $status, canEdit( 'System' ) ) ?> - <?php echo $run_state ?> <?php echo makePopupLink( '?view=version', 'zmVersion', 'version', '<span class="'.$versionClass.'">v'.ZM_VERSION.'</span>', canEdit( 'System' ) ) ?></h2>
|
<h2 id="title">
|
||||||
|
<a href="http://www.zoneminder.com" target="ZoneMinder">ZoneMinder</a> <?php echo translate('Console') ?> -
|
||||||
|
<?php echo makePopupLink( '?view=state', 'zmState', 'state', $status, canEdit( 'System' ) ) ?> -
|
||||||
|
<?php echo $run_state ?> <?php echo makePopupLink( '?view=version', 'zmVersion', 'version', '<span class="'.$versionClass.'">v'.ZM_VERSION.'</span>', canEdit( 'System' ) ) ?>
|
||||||
|
</h2>
|
||||||
<div class="clear"></div>
|
<div class="clear"></div>
|
||||||
<?php if ( ZM_WEB_CONSOLE_BANNER ) { ?><h3 id="development"><?php echo ZM_WEB_CONSOLE_BANNER ?></h3><?php } ?>
|
<?php if ( ZM_WEB_CONSOLE_BANNER ) { ?><h3 id="development"><?php echo ZM_WEB_CONSOLE_BANNER ?></h3><?php } ?>
|
||||||
<div id="monitorSummary"><?php echo makePopupLink( '?view=groups', 'zmGroups', 'groups', sprintf( $CLANG['MonitorCount'], count($displayMonitors), zmVlang( $VLANG['Monitor'], count($displayMonitors) ) ).($group?' ('.$group['Name'].')':''), canView( 'Groups' ) ); ?></div>
|
<div id="monitorSummary"><?php echo makePopupLink( '?view=groups', 'zmGroups', 'groups', sprintf( $CLANG['MonitorCount'], count($displayMonitors), zmVlang( $VLANG['Monitor'], count($displayMonitors) ) ).($group?' ('.$group['Name'].')':''), canView( 'Groups' ) ); ?></div>
|
||||||
|
@ -248,27 +252,26 @@ foreach( $displayMonitors as $monitor ) {
|
||||||
$Server = new Server( $monitor['ServerId'] );
|
$Server = new Server( $monitor['ServerId'] );
|
||||||
echo $Server->Name();
|
echo $Server->Name();
|
||||||
?></td>
|
?></td>
|
||||||
<?php } ?>
|
|
||||||
<?php if ( $monitor['Type'] == "Local" ) { ?>
|
|
||||||
<td class="colSource"><?php echo makePopupLink( '?view=monitor&mid='.$monitor['Id'], 'zmMonitor'.$monitor['Id'], 'monitor', '<span class="'.$dclass.'">'.$monitor['Device'].' ('.$monitor['Channel'].')</span>', canEdit( 'Monitors' ) ) ?></td>
|
|
||||||
<?php } elseif ( $monitor['Type'] == "Remote" ) { ?>
|
|
||||||
<td class="colSource"><?php echo makePopupLink( '?view=monitor&mid='.$monitor['Id'], 'zmMonitor'.$monitor['Id'], 'monitor', '<span class="'.$dclass.'">'.preg_replace( '/^.*@/', '', $monitor['Host'] ).'</span>', canEdit( 'Monitors' ) ) ?></td>
|
|
||||||
<?php } elseif ( $monitor['Type'] == "File" ) { ?>
|
|
||||||
<td class="colSource"><?php echo makePopupLink( '?view=monitor&mid='.$monitor['Id'], 'zmMonitor'.$monitor['Id'], 'monitor', '<span class="'.$dclass.'">'.preg_replace( '/^.*\//', '', $monitor['Path'] ).'</span>', canEdit( 'Monitors' ) ) ?></td>
|
|
||||||
<?php } elseif ( $monitor['Type'] == "Ffmpeg" || $monitor['Type'] == "Libvlc" ) {
|
|
||||||
$domain = parse_url( $monitor['Path'], PHP_URL_HOST );
|
|
||||||
$shortpath = $domain ? $domain : preg_replace( '/^.*\//', '', $monitor['Path'] );
|
|
||||||
if ( $shortpath == '' ) {
|
|
||||||
$shortpath = 'Monitor ' . $monitor['Id'];
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<td class="colSource"><?php echo makePopupLink( '?view=monitor&mid='.$monitor['Id'], 'zmMonitor'.$monitor['Id'], 'monitor', '<span class="'.$dclass.'">'.$shortpath.'</span>', canEdit( 'Monitors' ) ) ?></td>
|
|
||||||
<?php } elseif ( $monitor['Type'] == "cURL" ) { ?>
|
|
||||||
<td class="colSource"><?php echo makePopupLink( '?view=monitor&mid='.$monitor['Id'], 'zmMonitor'.$monitor['Id'], 'monitor', '<span class="'.$dclass.'">'.preg_replace( '/^.*\//', '', $monitor['Path'] ).'</span>', canEdit( 'Monitors' ) ) ?></td>
|
|
||||||
<?php } else { ?>
|
|
||||||
<td class="colSource"> </td>
|
|
||||||
<?php } ?>
|
|
||||||
<?php
|
<?php
|
||||||
|
}
|
||||||
|
$source = '';
|
||||||
|
if ( $monitor['Type'] == 'Local' ) {
|
||||||
|
$source = $monitor['Device'].' ('.$monitor['Channel'].')';
|
||||||
|
} elseif ( $monitor['Type'] == 'Remote' ) {
|
||||||
|
$source = preg_replace( '/^.*@/', '', $monitor['Host'] );
|
||||||
|
} elseif ( $monitor['Type'] == 'File' || $monitor['Type'] == 'cURL' ) {
|
||||||
|
$source = preg_replace( '/^.*\//', '', $monitor['Path'] );
|
||||||
|
} elseif ( $monitor['Type'] == 'Ffmpeg' || $monitor['Type'] == 'Libvlc' ) {
|
||||||
|
$domain = parse_url( $monitor['Path'], PHP_URL_HOST );
|
||||||
|
$source = $domain ? $domain : preg_replace( '/^.*\//', '', $monitor['Path'] );
|
||||||
|
} elseif ( $monitor['Type'] == 'cURL' ) {
|
||||||
|
|
||||||
|
}
|
||||||
|
if ( $source == '' ) {
|
||||||
|
$source = 'Monitor ' . $monitor['Id'];
|
||||||
|
}
|
||||||
|
echo '<td class="colSource">'. makePopupLink( '?view=monitor&mid='.$monitor['Id'], 'zmMonitor'.$monitor['Id'], 'monitor', '<span class="'.$dclass.'">'.$source.'</span>', canEdit( 'Monitors' ) ).'</td>';
|
||||||
|
|
||||||
for ( $i = 0; $i < count($eventCounts); $i++ ) {
|
for ( $i = 0; $i < count($eventCounts); $i++ ) {
|
||||||
?>
|
?>
|
||||||
<td class="colEvents"><?php echo makePopupLink( '?view='.$eventsView.'&page=1'.$monitor['eventCounts'][$i]['filter']['query'], $eventsWindow, $eventsView, $monitor['EventCount'.$i], canView( 'Events' ) ) ?></td>
|
<td class="colEvents"><?php echo makePopupLink( '?view='.$eventsView.'&page=1'.$monitor['eventCounts'][$i]['filter']['query'], $eventsWindow, $eventsView, $monitor['EventCount'.$i], canView( 'Events' ) ) ?></td>
|
||||||
|
|
|
@ -152,7 +152,7 @@ if ( $Event->DefaultVideo() ) {
|
||||||
?>
|
?>
|
||||||
<div id="videoFeed">
|
<div id="videoFeed">
|
||||||
<video id="videoobj" class="video-js vjs-default-skin" width="<?php echo reScale( $Event->Width(), $scale ) ?>" height="<?php echo reScale( $Event->Height(), $scale ) ?>" data-setup='{ "controls": true, "playbackRates": [0.5, 1, 1.5, 2, 4, 8, 16, 32, 64, 128, 256], "autoplay": true, "preload": "auto", "plugins": { "zoomrotate": { "zoom": "<?php echo $Zoom ?>"}}}'>
|
<video id="videoobj" class="video-js vjs-default-skin" width="<?php echo reScale( $Event->Width(), $scale ) ?>" height="<?php echo reScale( $Event->Height(), $scale ) ?>" data-setup='{ "controls": true, "playbackRates": [0.5, 1, 1.5, 2, 4, 8, 16, 32, 64, 128, 256], "autoplay": true, "preload": "auto", "plugins": { "zoomrotate": { "zoom": "<?php echo $Zoom ?>"}}}'>
|
||||||
<source src="<?php echo $Event->getStreamSrc( array( "mode=mpeg&format=h264" ) ); ?>" type="video/mp4">
|
<source src="<?php echo $Event->getStreamSrc( array( 'mode'=>'mpeg','format'=>'h264' ) ); ?>" type="video/mp4">
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -214,7 +214,7 @@ foreach ( $events as $event ) {
|
||||||
<td class="colThumbnail">
|
<td class="colThumbnail">
|
||||||
<?php
|
<?php
|
||||||
$imgSrc = '?view=image&eid='.$event->Id().'&fid='.$thumbData['FrameId'].'&width='.$thumbData['Width'].'&height='.$thumbData['Height'];
|
$imgSrc = '?view=image&eid='.$event->Id().'&fid='.$thumbData['FrameId'].'&width='.$thumbData['Width'].'&height='.$thumbData['Height'];
|
||||||
$streamSrc = getStreamSrc( array( "source=event", "mode=jpeg", "event=".$event->Id(), "scale=".$scale, "maxfps=".ZM_WEB_VIDEO_MAXFPS, "replay=single") );
|
$streamSrc = $event->getStreamSrc( array( 'mode'=>'jpeg', 'scale'=>$scale, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>'single') );
|
||||||
|
|
||||||
$imgHtml = '<img id="thumbnail'.$event->id().'" src="'.$imgSrc.'" alt="'. validHtmlStr('Event '.$event->Id()) .'" style="width:'. validInt($thumbData['Width']) .'px;height:'. validInt( $thumbData['Height'] ).'px;" onmouseover="this.src=\''.$streamSrc.'\';" onmouseout="this.src=\''.$imgSrc.'\';"/>';
|
$imgHtml = '<img id="thumbnail'.$event->id().'" src="'.$imgSrc.'" alt="'. validHtmlStr('Event '.$event->Id()) .'" style="width:'. validInt($thumbData['Width']) .'px;height:'. validInt( $thumbData['Height'] ).'px;" onmouseover="this.src=\''.$streamSrc.'\';" onmouseout="this.src=\''.$imgSrc.'\';"/>';
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program; if not, write to the Free Software
|
// 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 ( !canView( 'Events' ) )
|
if ( !canView( 'Events' ) )
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
var logParms = "view=request&request=log&task=query";
|
var logParms = "view=request&request=log&task=query";
|
||||||
var logReq = new Request.JSON( { url: thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: logResponse } );
|
var logReq = new Request.JSON( { url: thisUrl, method: 'post', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: logResponse } );
|
||||||
var logTimer = undefined;
|
var logTimer = undefined;
|
||||||
var logTable = undefined;
|
var logTable = undefined;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,720 @@
|
||||||
|
function evaluateLoadTimes() {
|
||||||
|
// Only consider it a completed event if we load ALL monitors, then zero all and start again
|
||||||
|
var start=0;
|
||||||
|
var end=0;
|
||||||
|
if ( liveMode != 1 && currentSpeed == 0 ) return; // don't evaluate when we are not moving as we can do nothing really fast.
|
||||||
|
for ( var i = 0; i < monitorIndex.length; i++ ) {
|
||||||
|
if ( monitorName[i] > "" ) {
|
||||||
|
if ( monitorLoadEndTimems[i] ==0 ) return; // if we have a monitor with no time yet just wait
|
||||||
|
if ( start == 0 || start > monitorLoadStartTimems[i] ) start = monitorLoadStartTimems[i];
|
||||||
|
if ( end == 0 || end < monitorLoadEndTimems[i] ) end = monitorLoadEndTimems[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( start == 0 || end == 0 ) return; // we really should not get here
|
||||||
|
for ( var i=0; i < numMonitors; i++ ) {
|
||||||
|
var monId = monitorPtr[i];
|
||||||
|
monitorLoadStartTimems[monId] = 0;
|
||||||
|
monitorLoadEndTimems[monId] = 0;
|
||||||
|
}
|
||||||
|
freeTimeLastIntervals[imageLoadTimesEvaluated++] = 1 - ((end - start)/currentDisplayInterval);
|
||||||
|
if( imageLoadTimesEvaluated < imageLoadTimesNeeded ) return;
|
||||||
|
var avgFrac=0;
|
||||||
|
for ( var i=0; i < imageLoadTimesEvaluated; i++ )
|
||||||
|
avgFrac += freeTimeLastIntervals[i];
|
||||||
|
avgFrac = avgFrac / imageLoadTimesEvaluated;
|
||||||
|
// The larger this is(positive) the faster we can go
|
||||||
|
if (avgFrac >= 0.9) currentDisplayInterval = (currentDisplayInterval * 0.50).toFixed(1); // we can go much faster
|
||||||
|
else if (avgFrac >= 0.8) currentDisplayInterval = (currentDisplayInterval * 0.55).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.7) currentDisplayInterval = (currentDisplayInterval * 0.60).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.6) currentDisplayInterval = (currentDisplayInterval * 0.65).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.5) currentDisplayInterval = (currentDisplayInterval * 0.70).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.4) currentDisplayInterval = (currentDisplayInterval * 0.80).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.35) currentDisplayInterval = (currentDisplayInterval * 0.90).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.3) currentDisplayInterval = (currentDisplayInterval * 1.00).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.25) currentDisplayInterval = (currentDisplayInterval * 1.20).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.2) currentDisplayInterval = (currentDisplayInterval * 1.50).toFixed(1);
|
||||||
|
else if (avgFrac >= 0.1) currentDisplayInterval = (currentDisplayInterval * 2.00).toFixed(1);
|
||||||
|
else currentDisplayInterval = (currentDisplayInterval * 2.50).toFixed(1);
|
||||||
|
currentDisplayInterval=Math.min(Math.max(currentDisplayInterval, 30),10000); // limit this from about 30fps to .1 fps
|
||||||
|
imageLoadTimesEvaluated=0;
|
||||||
|
setSpeed(speedIndex);
|
||||||
|
$('fps').innerHTML="Display refresh rate is " + (1000 / currentDisplayInterval).toFixed(1) + " per second, avgFrac=" + avgFrac.toFixed(3) + ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetImageSource( monId, val ) {
|
||||||
|
if ( liveMode == 1 ) {
|
||||||
|
return monitorImageObject[monId].src.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) ));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for ( var i=0, eIdlength = eId.length; i < eIdlength; i++ ) {
|
||||||
|
// Search for a match
|
||||||
|
if ( eMonId[i] == monId && val >= eStartSecs[i] && val <= eEndSecs[i] ) {
|
||||||
|
var frame = parseInt((val - eStartSecs[i])/(eEndSecs[i]-eStartSecs[i])*eventFrames[i])+1;
|
||||||
|
return "index.php?view=image&eid=" + eId[i] + '&fid='+frame + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height;
|
||||||
|
}
|
||||||
|
} // end for
|
||||||
|
return "no data";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// callback when loading an image. Will load itself to the canvas, or draw no data
|
||||||
|
function imagedone( obj, monId, success ) {
|
||||||
|
if ( success ) {
|
||||||
|
var canvasCtx = monitorCanvasCtx[monId];
|
||||||
|
var canvasObj = monitorCanvasObj[monId];
|
||||||
|
|
||||||
|
canvasCtx.drawImage( monitorImageObject[monId], 0, 0, canvasObj.width, canvasObj.height );
|
||||||
|
var iconSize=(Math.max(canvasObj.width, canvasObj.height) * 0.10);
|
||||||
|
canvasCtx.font = "600 " + iconSize.toString() + "px Arial";
|
||||||
|
canvasCtx.fillStyle = "white";
|
||||||
|
canvasCtx.globalCompositeOperation = "difference";
|
||||||
|
canvasCtx.fillText( "+", iconSize*0.2, iconSize*1.2 );
|
||||||
|
canvasCtx.fillText( "-", canvasObj.width - iconSize*1.2, iconSize*1.2 );
|
||||||
|
canvasCtx.globalCompositeOperation = "source-over";
|
||||||
|
monitorLoadEndTimems[monId] = new Date().getTime(); // elapsed time to load
|
||||||
|
evaluateLoadTimes();
|
||||||
|
}
|
||||||
|
monitorLoading[monId] = false;
|
||||||
|
if ( ! success ) {
|
||||||
|
// if we had a failrue queue up the no-data image
|
||||||
|
//loadImage2Monitor(monId,"no data"); // leave the staged URL if there is one, just ignore it here.
|
||||||
|
loadNoData( monId );
|
||||||
|
} else {
|
||||||
|
if ( monitorLoadingStageURL[monId] == "" ) {
|
||||||
|
console.log("Not showing image for " + monId );
|
||||||
|
// This means that there wasn't a loading image placeholder.
|
||||||
|
// So we weren't actually loading an image... which seems weird.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//loadImage2Monitor(monId,monitorLoadingStageURL[monId] );
|
||||||
|
//monitorLoadingStageURL[monId]="";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadNoData( monId ) {
|
||||||
|
if ( monId ) {
|
||||||
|
var canvasCtx = monitorCanvasCtx[monId];
|
||||||
|
var canvasObj = monitorCanvasObj[monId];
|
||||||
|
canvasCtx.fillStyle="white";
|
||||||
|
canvasCtx.fillRect(0, 0, canvasObj.width, canvasObj.height);
|
||||||
|
var textSize=canvasObj.width * 0.15;
|
||||||
|
var text="No Data";
|
||||||
|
canvasCtx.font = "600 " + textSize.toString() + "px Arial";
|
||||||
|
canvasCtx.fillStyle="black";
|
||||||
|
var textWidth = canvasCtx.measureText(text).width;
|
||||||
|
canvasCtx.fillText(text,canvasObj.width/2 - textWidth/2,canvasObj.height/2);
|
||||||
|
} else {
|
||||||
|
console.log("No monId in loadNoData");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either draws the
|
||||||
|
function loadImage2Monitor( monId, url ) {
|
||||||
|
if ( monitorLoading[monId] && monitorImageObject[monId].src != url ) {
|
||||||
|
// never queue the same image twice (if it's loading it has to be defined, right?
|
||||||
|
monitorLoadingStageURL[monId] = url; // we don't care if we are overriting, it means it didn't change fast enough
|
||||||
|
} else {
|
||||||
|
if ( monitorImageObject[monId].src == url ) return; // do nothing if it's the same
|
||||||
|
if ( url == 'no data' ) {
|
||||||
|
loadNoData( monId );
|
||||||
|
} else {
|
||||||
|
monitorLoading[monId] = true;
|
||||||
|
monitorLoadStartTimems[monId] = new Date().getTime();
|
||||||
|
monitorImageObject[monId].src = url; // starts a load but doesn't refresh yet, wait until ready
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function timerFire() {
|
||||||
|
// See if we need to reschedule
|
||||||
|
if(currentDisplayInterval != timerInterval || currentSpeed == 0) {
|
||||||
|
// zero just turn off interrupts
|
||||||
|
clearInterval(timerObj);
|
||||||
|
timerInterval=currentDisplayInterval;
|
||||||
|
if(currentSpeed>0 || liveMode!=0) timerObj=setInterval(timerFire,timerInterval); // don't fire out of live mode if speed is zero
|
||||||
|
}
|
||||||
|
|
||||||
|
if (liveMode) outputUpdate(currentTimeSecs); // In live mode we basically do nothing but redisplay
|
||||||
|
else if (currentTimeSecs + playSecsperInterval >= maxTimeSecs) // beyond the end just stop
|
||||||
|
{
|
||||||
|
setSpeed(0);
|
||||||
|
outputUpdate(currentTimeSecs);
|
||||||
|
}
|
||||||
|
else outputUpdate(currentTimeSecs + playSecsperInterval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawSliderOnGraph(val) {
|
||||||
|
var sliderWidth=10;
|
||||||
|
var sliderLineWidth=1;
|
||||||
|
var sliderHeight=cHeight;
|
||||||
|
|
||||||
|
if(liveMode==1) {
|
||||||
|
val=Math.floor( Date.now() / 1000);
|
||||||
|
}
|
||||||
|
// Set some sizes
|
||||||
|
|
||||||
|
var labelpx = Math.max( 6, Math.min( 20, parseInt(cHeight * timeLabelsFractOfRow / (numMonitors+1)) ) );
|
||||||
|
var labbottom=parseInt(cHeight * 0.2 / (numMonitors+1)).toString() + "px"; // This is positioning same as row labels below, but from bottom so 1-position
|
||||||
|
var labfont=labelpx + "px Georgia"; // set this like below row labels
|
||||||
|
|
||||||
|
if(numMonitors>0) {
|
||||||
|
// if we have no data to display don't do the slider itself
|
||||||
|
var sliderX=parseInt( (val - minTimeSecs) / rangeTimeSecs * cWidth - sliderWidth/2); // position left side of slider
|
||||||
|
if(sliderX < 0) sliderX=0;
|
||||||
|
if(sliderX+sliderWidth > cWidth) sliderX=cWidth-sliderWidth-1;
|
||||||
|
|
||||||
|
// If we have data already saved first restore it from LAST time
|
||||||
|
|
||||||
|
if(typeof underSlider !== 'undefined')
|
||||||
|
{
|
||||||
|
ctx.putImageData(underSlider,underSliderX, 0, 0, 0, sliderWidth, sliderHeight);
|
||||||
|
underSlider=undefined;
|
||||||
|
}
|
||||||
|
if(liveMode==0) // we get rid of the slider if we switch to live (since it may not be in the "right" place)
|
||||||
|
{
|
||||||
|
// Now save where we are putting it THIS time
|
||||||
|
underSlider=ctx.getImageData(sliderX, 0, sliderWidth, sliderHeight);
|
||||||
|
// And add in the slider'
|
||||||
|
ctx.lineWidth=sliderLineWidth;
|
||||||
|
ctx.strokeStyle='black';
|
||||||
|
// looks like strokes are on the outside (or could be) so shrink it by the line width so we replace all the pixels
|
||||||
|
ctx.strokeRect(sliderX+sliderLineWidth,sliderLineWidth,sliderWidth - 2*sliderLineWidth, sliderHeight - 2*sliderLineWidth);
|
||||||
|
underSliderX=sliderX;
|
||||||
|
}
|
||||||
|
var o = $('scruboutput');
|
||||||
|
if(liveMode==1)
|
||||||
|
{
|
||||||
|
o.innerHTML="Live Feed @ " + (1000 / currentDisplayInterval).toFixed(1) + " fps";
|
||||||
|
o.style.color="red";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
o.innerHTML=secs2dbstr(val);
|
||||||
|
o.style.color="blue";
|
||||||
|
}
|
||||||
|
o.style.position="absolute";
|
||||||
|
o.style.bottom=labbottom;
|
||||||
|
o.style.font=labfont;
|
||||||
|
// try to get length and then when we get too close to the right switch to the left
|
||||||
|
var len = o.offsetWidth;
|
||||||
|
var x;
|
||||||
|
if(sliderX > cWidth/2)
|
||||||
|
x=sliderX - len - 10;
|
||||||
|
else
|
||||||
|
x=sliderX + 10;
|
||||||
|
o.style.left=x.toString() + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
// This displays (or not) the left/right limits depending on how close the slider is.
|
||||||
|
// Because these change widths if the slider is too close, use the slider width as an estimate for the left/right label length (i.e. don't recalculate len from above)
|
||||||
|
// If this starts to collide increase some of the extra space
|
||||||
|
|
||||||
|
var o = $('scrubleft');
|
||||||
|
o.innerHTML=secs2dbstr(minTimeSecs);
|
||||||
|
o.style.position="absolute";
|
||||||
|
o.style.bottom=labbottom;
|
||||||
|
o.style.font=labfont;
|
||||||
|
o.style.left="5px";
|
||||||
|
if(numMonitors==0) // we need a len calculation if we skipped the slider
|
||||||
|
len = o.offsetWidth;
|
||||||
|
// If the slider will overlay part of this suppress (this is the left side)
|
||||||
|
if(len + 10 > sliderX || cWidth < len * 4 ) // that last check is for very narrow browsers
|
||||||
|
o.style.display="none";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
o.style.display="inline";
|
||||||
|
o.style.display="inline-flex"; // safari won't take this but will just ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
var o = $('scrubright');
|
||||||
|
o.innerHTML=secs2dbstr(maxTimeSecs);
|
||||||
|
o.style.position="absolute";
|
||||||
|
o.style.bottom=labbottom;
|
||||||
|
o.style.font=labfont;
|
||||||
|
// If the slider will overlay part of this suppress (this is the right side)
|
||||||
|
o.style.left=(cWidth - len - 15).toString() + "px";
|
||||||
|
if(sliderX > cWidth - len - 20 || cWidth < len * 4 )
|
||||||
|
o.style.display="none";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
o.style.display="inline";
|
||||||
|
o.style.display="inline-flex";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawGraph()
|
||||||
|
{
|
||||||
|
var divWidth=$('timelinediv').clientWidth
|
||||||
|
canvas.width = cWidth = divWidth; // Let it float and determine width (it should be sized a bit smaller percentage of window)
|
||||||
|
canvas.height=cHeight = parseInt(window.innerHeight * 0.10);
|
||||||
|
if(eId.length==0)
|
||||||
|
{
|
||||||
|
ctx.font="40px Georgia";
|
||||||
|
ctx.fillStyle="Black";
|
||||||
|
ctx.globalAlpha=1;
|
||||||
|
var t="No data found in range - choose differently";
|
||||||
|
var l=ctx.measureText(t).width;
|
||||||
|
ctx.fillText(t,(cWidth - l)/2, cHeight-10);
|
||||||
|
underSlider=undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var rowHeight=parseInt(cHeight / (numMonitors + 1) ); // Leave room for a scale of some sort
|
||||||
|
|
||||||
|
// first fill in the bars for the events (not alarms)
|
||||||
|
|
||||||
|
for(var i=0; i<eId.length; i++) // Display all we loaded
|
||||||
|
{
|
||||||
|
var x1=parseInt( (eStartSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
|
||||||
|
var x2=parseInt( (eEndSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round high end up to be sure consecutive ones connect
|
||||||
|
ctx.fillStyle=monitorColour[eMonId[i]];
|
||||||
|
ctx.globalAlpha = 0.2; // light color for background
|
||||||
|
ctx.clearRect(x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight); // Erase any overlap so it doesn't look artificially darker
|
||||||
|
ctx.fillRect (x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight);
|
||||||
|
}
|
||||||
|
for(var i=0; (i<fScore.length) && (maxScore>0); i++) // Now put in scored frames (if any)
|
||||||
|
{
|
||||||
|
var x1=parseInt( (fTimeFromSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
|
||||||
|
var x2=parseInt( (fTimeToSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round up
|
||||||
|
if(x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide
|
||||||
|
ctx.fillStyle=monitorColour[fMonId[i]];
|
||||||
|
ctx.globalAlpha = 0.4 + 0.6 * (1 - fScore[i]/maxScore); // Background is scaled but even lowest is twice as dark as the background
|
||||||
|
ctx.fillRect(x1,monitorIndex[fMonId[i]]*rowHeight,x2-x1,rowHeight);
|
||||||
|
}
|
||||||
|
for(var i=0; i<numMonitors; i++) // Note that this may be a sparse array
|
||||||
|
{
|
||||||
|
ctx.font= parseInt(rowHeight * timeLabelsFractOfRow).toString() + "px Georgia";
|
||||||
|
ctx.fillStyle="Black";
|
||||||
|
ctx.globalAlpha=1;
|
||||||
|
ctx.fillText(monitorName[monitorPtr[i]], 0, (i + 1 - (1 - timeLabelsFractOfRow)/2 ) * rowHeight ); // This should roughly center font in row
|
||||||
|
}
|
||||||
|
underSlider=undefined; // flag we don't have a slider cached
|
||||||
|
drawSliderOnGraph(currentTimeSecs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function redrawScreen()
|
||||||
|
{
|
||||||
|
if(fitMode==0) // if we fit, then monitors were absolutely positioned already (or will be) otherwise release them to float
|
||||||
|
{
|
||||||
|
for(var i=0; i<numMonitors; i++)
|
||||||
|
monitorCanvasObj[monitorPtr[i]].style.position="";
|
||||||
|
$('monitors').setStyle('height',"auto");
|
||||||
|
}
|
||||||
|
if(liveMode==1) // if we are not in live view switch to history -- this has to come before fit in case we re-establish the timeline
|
||||||
|
{
|
||||||
|
$('SpeedDiv').style.display="none";
|
||||||
|
$('timelinediv').style.display="none";
|
||||||
|
$('live').innerHTML="History";
|
||||||
|
$('zoomin').style.display="none";
|
||||||
|
$('zoomout').style.display="none";
|
||||||
|
$('panleft').style.display="none";
|
||||||
|
$('panright').style.display="none";
|
||||||
|
|
||||||
|
}
|
||||||
|
else // switch out of liveview mode
|
||||||
|
{
|
||||||
|
$('SpeedDiv').style.display="inline";
|
||||||
|
$('SpeedDiv').style.display="inline-flex";
|
||||||
|
$('timelinediv').style.display=null;
|
||||||
|
$('live').innerHTML="Live";
|
||||||
|
$('zoomin').style.display="inline";
|
||||||
|
$('zoomin').style.display="inline-flex";
|
||||||
|
$('zoomout').style.display="inline";
|
||||||
|
$('zoomout').style.display="inline-flex";
|
||||||
|
$('panleft').style.display="inline";
|
||||||
|
$('panleft').style.display="inline-flex";
|
||||||
|
$('panright').style.display="inline";
|
||||||
|
$('panright').style.display="inline-flex";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fitMode==1)
|
||||||
|
{
|
||||||
|
$('ScaleDiv').style.display="none";
|
||||||
|
$('fit').innerHTML="Scale";
|
||||||
|
var vh=window.innerHeight;
|
||||||
|
var vw=window.innerWidth;
|
||||||
|
var pos=$('monitors').getPosition();
|
||||||
|
var mh=(vh - pos.y - $('fps').getSize().y);
|
||||||
|
$('monitors').setStyle('height',mh.toString() + "px"); // leave a small gap at bottom
|
||||||
|
if(maxfit2($('monitors').getSize().x,$('monitors').getSize().y) == 0) /// if we fail to fix we back out of fit mode -- ??? This may need some better handling
|
||||||
|
fitMode=1-fitMode;
|
||||||
|
}
|
||||||
|
else // switch out of fit mode
|
||||||
|
{
|
||||||
|
$('ScaleDiv').style.display="inline";
|
||||||
|
$('ScaleDiv').style.display="inline-flex";
|
||||||
|
$('fit').innerHTML="Fit";
|
||||||
|
setScale(currentScale);
|
||||||
|
}
|
||||||
|
drawGraph();
|
||||||
|
outputUpdate(currentTimeSecs);
|
||||||
|
timerFire(); // force a fire in case it's not timing
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function outputUpdate(val)
|
||||||
|
{
|
||||||
|
drawSliderOnGraph(val);
|
||||||
|
for(var i=0; i<numMonitors; i++)
|
||||||
|
{
|
||||||
|
loadImage2Monitor(monitorPtr[i],SetImageSource(monitorPtr[i],val));
|
||||||
|
}
|
||||||
|
var currentTimeMS = new Date(val*1000);
|
||||||
|
currentTimeSecs=val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Found this here: http://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element
|
||||||
|
function relMouseCoords(event){
|
||||||
|
var totalOffsetX = 0;
|
||||||
|
var totalOffsetY = 0;
|
||||||
|
var canvasX = 0;
|
||||||
|
var canvasY = 0;
|
||||||
|
var currentElement = this;
|
||||||
|
|
||||||
|
do{
|
||||||
|
totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
|
||||||
|
totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
|
||||||
|
}
|
||||||
|
while(currentElement = currentElement.offsetParent)
|
||||||
|
|
||||||
|
canvasX = event.pageX - totalOffsetX;
|
||||||
|
canvasY = event.pageY - totalOffsetY;
|
||||||
|
|
||||||
|
return {x:canvasX, y:canvasY}
|
||||||
|
}
|
||||||
|
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
|
||||||
|
|
||||||
|
// These are the functions for mouse movement in the timeline. Note that touch is treated as a mouse move with mouse down
|
||||||
|
|
||||||
|
var mouseisdown=false;
|
||||||
|
function mdown(event) {mouseisdown=true; mmove(event);}
|
||||||
|
function mup(event) {mouseisdown=false;}
|
||||||
|
function mout(event) {mouseisdown=false;} // if we go outside treat it as release
|
||||||
|
function tmove(event) {mouseisdown=true; mmove(event);}
|
||||||
|
|
||||||
|
function mmove(event) {
|
||||||
|
if(mouseisdown) {
|
||||||
|
// only do anything if the mouse is depressed while on the sheet
|
||||||
|
var sec = minTimeSecs + rangeTimeSecs / event.target.width * event.target.relMouseCoords(event).x;
|
||||||
|
outputUpdate(sec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function secs2dbstr (s)
|
||||||
|
{
|
||||||
|
var st = (new Date(s * 1000)).format("%Y-%m-%d %H:%M:%S");
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFit(value)
|
||||||
|
{
|
||||||
|
fitMode=value;
|
||||||
|
redrawScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showScale(newscale) // updates slider only
|
||||||
|
{
|
||||||
|
$('scaleslideroutput').innerHTML = parseFloat(newscale).toFixed(2).toString() + " x";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setScale(newscale) // makes actual change
|
||||||
|
{
|
||||||
|
showScale(newscale);
|
||||||
|
for(var i=0; i<numMonitors; i++)
|
||||||
|
{
|
||||||
|
monitorCanvasObj[monitorPtr[i]].width=monitorWidth[monitorPtr[i]]*monitorNormalizeScale[monitorPtr[i]]*monitorZoomScale[monitorPtr[i]]*newscale;
|
||||||
|
monitorCanvasObj[monitorPtr[i]].height=monitorHeight[monitorPtr[i]]*monitorNormalizeScale[monitorPtr[i]]*monitorZoomScale[monitorPtr[i]]*newscale;
|
||||||
|
}
|
||||||
|
currentScale=newscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSpeed(val) // updates slider only
|
||||||
|
{
|
||||||
|
$('speedslideroutput').innerHTML = parseFloat(speeds[val]).toFixed(2).toString() + " x";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSpeed(val) // Note parameter is the index not the speed
|
||||||
|
{
|
||||||
|
var t;
|
||||||
|
if(liveMode==1) return; // we shouldn't actually get here but just in case
|
||||||
|
currentSpeed=parseFloat(speeds[val]);
|
||||||
|
speedIndex=val;
|
||||||
|
playSecsperInterval = currentSpeed * currentDisplayInterval / 1000;
|
||||||
|
showSpeed(val);
|
||||||
|
if( timerInterval != currentDisplayInterval || currentSpeed == 0 ) timerFire(); // if the timer isn't firing we need to trigger it to update
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLive(value)
|
||||||
|
{
|
||||||
|
liveMode=value;
|
||||||
|
redrawScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
|
||||||
|
// The section below are to reload this program with new parameters
|
||||||
|
|
||||||
|
function clicknav(minSecs,maxSecs,arch,live) {// we use the current time if we can
|
||||||
|
var now = new Date() / 1000;
|
||||||
|
var minStr="";
|
||||||
|
var maxStr="";
|
||||||
|
var currentStr="";
|
||||||
|
if ( minSecs > 0 ) {
|
||||||
|
if(maxSecs > now)
|
||||||
|
maxSecs = parseInt(now);
|
||||||
|
maxStr="&maxTime=" + secs2dbstr(maxSecs);
|
||||||
|
}
|
||||||
|
if ( maxSecs > 0 )
|
||||||
|
minStr="&minTime=" + secs2dbstr(minSecs);
|
||||||
|
if ( maxSecs == 0 && minSecs == 0 ) {
|
||||||
|
minStr="&minTime=01/01/1950 12:00:00";
|
||||||
|
maxStr="&maxTime=12/31/2035 12:00:00";
|
||||||
|
}
|
||||||
|
var intervalStr="&displayinterval=" + currentDisplayInterval.toString();
|
||||||
|
if ( minSecs && maxSecs ) {
|
||||||
|
if ( currentTimeSecs > minSecs && currentTimeSecs < maxSecs ) // make sure time is in the new range
|
||||||
|
currentStr="¤t=" + secs2dbstr(currentTimeSecs);
|
||||||
|
}
|
||||||
|
|
||||||
|
var liveStr="&live=0";
|
||||||
|
if ( live == 1 )
|
||||||
|
liveStr="&live=1";
|
||||||
|
|
||||||
|
var fitStr="&fit=0";
|
||||||
|
if ( fitMode == 1 )
|
||||||
|
fitStr="&fit=1";
|
||||||
|
|
||||||
|
var zoomStr="";
|
||||||
|
for ( var i=0; i < numMonitors; i++ )
|
||||||
|
if ( monitorZoomScale[monitorPtr[i]] < 0.99 || monitorZoomScale[monitorPtr[i]] > 1.01 ) // allow for some up/down changes and just treat as 1 of almost 1
|
||||||
|
zoomStr += "&z" + monitorPtr[i].toString() + "=" + monitorZoomScale[monitorPtr[i]].toFixed(2);
|
||||||
|
|
||||||
|
var uri = "?view=" + currentView + fitStr + groupStr + minStr + maxStr + currentStr + intervalStr + liveStr + zoomStr + "&scale=" + document.getElementById("scaleslider").value + "&speed=" + speeds[$j("#speedslider").value];
|
||||||
|
window.location = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lastHour() {
|
||||||
|
var now = new Date() / 1000;
|
||||||
|
clicknav(now - 3600 + 1, now,1,0);
|
||||||
|
}
|
||||||
|
function lastEight() {
|
||||||
|
var now = new Date() / 1000;
|
||||||
|
clicknav(now - 3600*8 + 1, now,1,0);
|
||||||
|
}
|
||||||
|
function zoomin() {
|
||||||
|
rangeTimeSecs = parseInt(rangeTimeSecs / 2);
|
||||||
|
minTimeSecs = parseInt(currentTimeSecs - rangeTimeSecs/2); // this is the slider current time, we center on that
|
||||||
|
maxTimeSecs = parseInt(currentTimeSecs + rangeTimeSecs/2);
|
||||||
|
clicknav(minTimeSecs,maxTimeSecs,1,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomout() {
|
||||||
|
rangeTimeSecs = parseInt(rangeTimeSecs * 2);
|
||||||
|
minTimeSecs = parseInt(currentTimeSecs - rangeTimeSecs/2); // this is the slider current time, we center on that
|
||||||
|
maxTimeSecs = parseInt(currentTimeSecs + rangeTimeSecs/2);
|
||||||
|
clicknav(minTimeSecs,maxTimeSecs,1,0);
|
||||||
|
}
|
||||||
|
function panleft() {
|
||||||
|
minTimeSecs = parseInt(minTimeSecs - rangeTimeSecs/2);
|
||||||
|
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
|
||||||
|
clicknav(minTimeSecs,maxTimeSecs,1,0);
|
||||||
|
}
|
||||||
|
function panright() {
|
||||||
|
minTimeSecs = parseInt(minTimeSecs + rangeTimeSecs/2);
|
||||||
|
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
|
||||||
|
clicknav(minTimeSecs,maxTimeSecs,1,0);
|
||||||
|
}
|
||||||
|
function allof() {
|
||||||
|
clicknav(0,0,1,0);
|
||||||
|
}
|
||||||
|
function allnon() {
|
||||||
|
clicknav(0,0,0,0);
|
||||||
|
}
|
||||||
|
/// >>>>>>>>>>>>>>>>> handles packing different size/aspect monitors on screen <<<<<<<<<<<<<<<<<<<<<<<<
|
||||||
|
|
||||||
|
function compSize(a, b) { // sort array by some size parameter - height seems to work best. A semi-greedy algorithm
|
||||||
|
var a_value = monitorHeight[a] * monitorWidth[a] * monitorNormalizeScale[a] * monitorZoomScale[a] * monitorNormalizeScale[a] * monitorZoomScale[a];
|
||||||
|
var b_value = monitorHeight[b] * monitorWidth[b] * monitorNormalizeScale[b] * monitorZoomScale[b] * monitorNormalizeScale[b] * monitorZoomScale[b];
|
||||||
|
|
||||||
|
if ( a_value > b_value ) return -1;
|
||||||
|
else if ( a_value == b_value ) return 0;
|
||||||
|
else return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function maxfit2(divW, divH) {
|
||||||
|
var bestFitX=[]; // how we arranged the so-far best match
|
||||||
|
var bestFitX2=[];
|
||||||
|
var bestFitY=[];
|
||||||
|
var bestFitY2=[];
|
||||||
|
var bestFitScale;
|
||||||
|
|
||||||
|
var minScale=0.05;
|
||||||
|
var maxScale=5.00;
|
||||||
|
var bestFitArea=0;
|
||||||
|
|
||||||
|
var borders=-1;
|
||||||
|
|
||||||
|
monitorPtr.sort(compSize);
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
if( maxScale - minScale < 0.01 ) break;
|
||||||
|
var thisScale = (maxScale + minScale) / 2;
|
||||||
|
var allFit=1;
|
||||||
|
var thisArea=0;
|
||||||
|
var thisX=[]; // top left
|
||||||
|
var thisY=[];
|
||||||
|
var thisX2=[]; // bottom right
|
||||||
|
var thisY2=[];
|
||||||
|
|
||||||
|
for ( var m = 0; m < numMonitors; m++ ) {
|
||||||
|
// this loop places each monitor (if it can)
|
||||||
|
var monId = monitorPtr[m];
|
||||||
|
|
||||||
|
function doesItFit(x,y,w,h,d) { // does block (w,h) fit at position (x,y) relative to edge and other nodes already done (0..d)
|
||||||
|
if(x+w>=divW) return 0;
|
||||||
|
if(y+h>=divH) return 0;
|
||||||
|
for(var i=0; i<=d; i++)
|
||||||
|
if( !( thisX[i]>x+w-1 || thisX2[i] < x || thisY[i] > y+h-1 || thisY2[i] < y ) ) return 0;
|
||||||
|
return 1; // it's OK
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( borders <= 0 )
|
||||||
|
borders=$("Monitor"+monId).getStyle("border").toInt() * 2; // assume fixed size border, and added to both sides and top/bottom
|
||||||
|
// try fitting over first, then down. Each new one must land at either upper right or lower left corner of last (try in that order)
|
||||||
|
// Pick the one with the smallest Y, then smallest X if Y equal
|
||||||
|
var fitX = 999999999;
|
||||||
|
var fitY = 999999999;
|
||||||
|
for ( adjacent = 0; adjacent < m; adjacent ++ ) {
|
||||||
|
// try top right of adjacent
|
||||||
|
if ( doesItFit(thisX2[adjacent]+1, thisY[adjacent], monitorWidth[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, monitorHeight[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, m-1) == 1 ) {
|
||||||
|
if ( thisY[adjacent]<fitY || ( thisY[adjacent] == fitY && thisX2[adjacent]+1 < fitX ) ) {
|
||||||
|
fitX = thisX2[adjacent] + 1;
|
||||||
|
fitY = thisY[adjacent];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// try bottom left
|
||||||
|
if ( doesItFit(thisX[adjacent], thisY2[adjacent]+1, monitorWidth[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, monitorHeight[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, m-1) == 1 ) {
|
||||||
|
if ( thisY2[adjacent]+1 < fitY || ( thisY2[adjacent]+1 == fitY && thisX[adjacent] < fitX ) ) {
|
||||||
|
fitX = thisX[adjacent];
|
||||||
|
fitY = thisY2[adjacent] + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( m == 0 ) { // note for the very first one there were no adjacents so the above loop didn't run
|
||||||
|
if ( doesItFit(0,0,monitorWidth[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, monitorHeight[monId] * thisScale * monitorNormalizeScale[monId] * monitorZoomScale[monId] + borders, -1) == 1 ) {
|
||||||
|
fitX = 0;
|
||||||
|
fitY = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( fitX == 999999999 ) {
|
||||||
|
allFit = 0;
|
||||||
|
break; // break out of monitor loop flagging we didn't fit
|
||||||
|
}
|
||||||
|
thisX[m] =fitX;
|
||||||
|
thisX2[m]=fitX + monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders;
|
||||||
|
thisY[m] =fitY;
|
||||||
|
thisY2[m]=fitY + monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders;
|
||||||
|
thisArea += (thisX2[m] - thisX[m])*(thisY2[m] - thisY[m]);
|
||||||
|
}
|
||||||
|
if ( allFit == 1 ) {
|
||||||
|
minScale=thisScale;
|
||||||
|
if(bestFitArea<thisArea) {
|
||||||
|
bestFitArea=thisArea;
|
||||||
|
bestFitX=thisX;
|
||||||
|
bestFitY=thisY;
|
||||||
|
bestFitX2=thisX2;
|
||||||
|
bestFitY2=thisY2;
|
||||||
|
bestFitScale=thisScale;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// didn't fit
|
||||||
|
maxScale=thisScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( bestFitArea > 0 ) { // only rearrange if we could fit -- otherwise just do nothing, let them start coming out, whatever
|
||||||
|
for ( m = 0; m < numMonitors; m++ ) {
|
||||||
|
c = $("Monitor" + monitorPtr[m]);
|
||||||
|
c.style.position="absolute";
|
||||||
|
c.style.left=bestFitX[m].toString() + "px";
|
||||||
|
c.style.top=bestFitY[m].toString() + "px";
|
||||||
|
c.width = bestFitX2[m] - bestFitX[m] + 1 - borders;
|
||||||
|
c.height= bestFitY2[m] - bestFitY[m] + 1 - borders;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// >>>>>>>>>>>>>>>> Handles individual monitor clicks and navigation to the standard event/watch display
|
||||||
|
|
||||||
|
function showOneMonitor(monId) {
|
||||||
|
// link out to the normal view of one event's data
|
||||||
|
// We know the monitor, need to determine the event based on current time
|
||||||
|
var url;
|
||||||
|
if ( liveMode != 0 )
|
||||||
|
url="?view=watch&mid=" + monId.toString();
|
||||||
|
else
|
||||||
|
for ( var i=0, len=eId.length; i<len; i++ ) {
|
||||||
|
if ( eMonId[i] == monId && currentTimeSecs >= eStartSecs[i] && currentTimeSecs <= eEndSecs[i] )
|
||||||
|
url="?view=event&eid=" + eId[i] + '&fid=' + parseInt(Math.max(1, Math.min(eventFrames[i], eventFrames[i] * (currentTimeSecs - eStartSecs[i]) / (eEndSecs[i] - eStartSecs[i] + 1) ) ));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
createPopup(url, 'zmEvent', 'event', monitorWidth[eMonId[i]], monitorHeight[eMonId[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoom(monId,scale) {
|
||||||
|
var lastZoomMonPriorScale = monitorZoomScale[monId];
|
||||||
|
monitorZoomScale[monId] *= scale;
|
||||||
|
if ( redrawScreen() == 0 ) {// failure here is probably because we zoomed too far
|
||||||
|
monitorZoomScale[monId] = lastZoomMonPriorScale;
|
||||||
|
alert("You can't zoom that far -- rolling back");
|
||||||
|
redrawScreen(); // put things back and hope it works
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickMonitor(event,monId) {
|
||||||
|
var monitor_element = $("Monitor"+monId.toString());
|
||||||
|
var pos_x = event.offsetX ? (event.offsetX) : event.pageX - monitor_element.offsetLeft;
|
||||||
|
var pos_y = event.offsetY ? (event.offsetY) : event.pageY - monitor_element.offsetTop;
|
||||||
|
if ( pos_x < monitor_element.width/4 && pos_y < monitor_element.height/4 )
|
||||||
|
zoom(monId,1.15);
|
||||||
|
else if ( pos_x > monitor_element.width * 3/4 && pos_y < monitor_element.height/4 )
|
||||||
|
zoom(monId,1/1.15);
|
||||||
|
else
|
||||||
|
showOneMonitor(monId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// >>>>>>>>> Initialization that runs on window load by being at the bottom
|
||||||
|
|
||||||
|
function initPage() {
|
||||||
|
canvas = $("timeline");
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
for ( var i = 0, len = monitorPtr.length; i < len; i += 1 ) {
|
||||||
|
var monId = monitorPtr[i];
|
||||||
|
if ( ! monId ) continue;
|
||||||
|
monitorCanvasObj[monId] = $('Monitor'+monId );
|
||||||
|
if ( ! monitorCanvasObj[monId] ) {
|
||||||
|
alert("Couldn't find DOM element for Monitor"+monId + "monitorPtr.length="+len);
|
||||||
|
} else {
|
||||||
|
monitorCanvasCtx[monId] = monitorCanvasObj[monId].getContext('2d');
|
||||||
|
var imageObject = monitorImageObject[monId] = new Image();
|
||||||
|
imageObject.monId = monId;
|
||||||
|
imageObject.onload = function() {imagedone(this, this.monId, true )};
|
||||||
|
imageObject.onerror = function() {imagedone(this, this.monId, false )};
|
||||||
|
loadImage2Monitor( monId, monitorImageURL[monId] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drawGraph();
|
||||||
|
setSpeed(speedIndex);
|
||||||
|
setFit(fitMode); // will redraw
|
||||||
|
setLive(liveMode); // will redraw
|
||||||
|
}
|
||||||
|
window.addEventListener("resize",redrawScreen);
|
||||||
|
// Kick everything off
|
||||||
|
window.addEvent( 'domready', initPage );
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
var currentScale=<?php echo $defaultScale?>;
|
||||||
|
var liveMode=<?php echo $initialModeIsLive?>;
|
||||||
|
console.log("Live mode?"+liveMode);
|
||||||
|
var fitMode=<?php echo $fitMode?>;
|
||||||
|
var currentSpeed=<?php echo $speeds[$speedIndex]?>; // slider scale, which is only for replay and relative to real time
|
||||||
|
var speedIndex=<?php echo $speedIndex?>;
|
||||||
|
var currentDisplayInterval=<?php echo $initialDisplayInterval?>; // will be set based on performance, this is the display interval in milliseconds for history, and fps for live, and dynamically determined (in ms)
|
||||||
|
var playSecsperInterval=1; // How many seconds of recorded image we play per refresh determined by speed (replay rate) and display interval; (default=1 if coming from live)
|
||||||
|
var timerInterval; // milliseconds between interrupts
|
||||||
|
var timerObj; // object to hold timer interval;
|
||||||
|
var freeTimeLastIntervals=[]; // Percentage of current interval used in loading most recent image
|
||||||
|
var imageLoadTimesEvaluated=0; // running count
|
||||||
|
var imageLoadTimesNeeded=15; // and how many we need
|
||||||
|
var timeLabelsFractOfRow = 0.9;
|
||||||
|
var eMonId = [];
|
||||||
|
var eId = [];
|
||||||
|
var eStartSecs = [];
|
||||||
|
var eEndSecs = [];
|
||||||
|
var eventFrames = []; // this is going to presume all frames equal durationlength
|
||||||
|
var groupStr=<?php if($group=="") echo '""'; else echo "\"&group=$group\""; ?>;
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Because we might not have time as the criteria, figure out the min/max time when we run the query
|
||||||
|
|
||||||
|
$minTimeSecs = strtotime('2036-01-01 01:01:01');
|
||||||
|
$maxTimeSecs = strtotime('1950-01-01 01:01:01');
|
||||||
|
|
||||||
|
// This builds the list of events that are eligible from this range
|
||||||
|
|
||||||
|
$index=0;
|
||||||
|
$anyAlarms=false;
|
||||||
|
|
||||||
|
$result = dbQuery( $eventsSql );
|
||||||
|
if ( ! $result ) {
|
||||||
|
Fatal( "SQL-ERR");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while( $event = $result->fetch( PDO::FETCH_ASSOC ) ) {
|
||||||
|
|
||||||
|
if ( $minTimeSecs > $event['StartTimeSecs'] ) $minTimeSecs = $event['StartTimeSecs'];
|
||||||
|
if ( $maxTimeSecs < $event['CalcEndTimeSecs'] ) $maxTimeSecs = $event['CalcEndTimeSecs'];
|
||||||
|
echo "
|
||||||
|
eMonId[$index]=" . $event['MonitorId'] . ";
|
||||||
|
eId[$index]=" . $event['Id'] . ";
|
||||||
|
eStartSecs[$index]=" . $event['StartTimeSecs'] . ";
|
||||||
|
eEndSecs[$index]=" . $event['CalcEndTimeSecs'] . ";
|
||||||
|
eventFrames[$index]=" . $event['Frames'] . ";
|
||||||
|
|
||||||
|
";
|
||||||
|
|
||||||
|
$index = $index + 1;
|
||||||
|
if ( $event['MaxScore'] > 0 )
|
||||||
|
$anyAlarms = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there is no data set the min/max to the passed in values
|
||||||
|
if ( $index == 0 ) {
|
||||||
|
if ( isset($minTime) && isset($maxTime) ) {
|
||||||
|
$minTimeSecs = strtotime($minTime);
|
||||||
|
$maxTimeSecs = strtotime($maxTime);
|
||||||
|
} else {
|
||||||
|
// this is the case of no passed in times AND no data -- just set something arbitrary
|
||||||
|
$minTimeSecs = strtotime('1950-06-01 01:01:01'); // random time so there's something to display
|
||||||
|
$maxTimeSecs = time() + 86400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only reset the calling time if there was no calling time
|
||||||
|
if ( !isset($minTime) || !isset($maxTime) ) {
|
||||||
|
$maxTime = strftime($maxTimeSecs);
|
||||||
|
$minTime = strftime($minTimeSecs);
|
||||||
|
} else {
|
||||||
|
$minTimeSecs = strtotime($minTime);
|
||||||
|
$maxTimeSecs = strtotime($maxTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we had any alarms in those events, this builds the list of all alarm frames, but consolidated down to (nearly) contiguous segments
|
||||||
|
// comparison in else governs how aggressively it consolidates
|
||||||
|
|
||||||
|
echo "var fMonId = [];\n";
|
||||||
|
echo "var fTimeFromSecs = [];\n";
|
||||||
|
echo "var fTimeToSecs = [];\n";
|
||||||
|
echo "var fScore = [];\n";
|
||||||
|
$maxScore=0;
|
||||||
|
$index=0;
|
||||||
|
$mId=-1;
|
||||||
|
$fromSecs=-1;
|
||||||
|
$toSecs=-1;
|
||||||
|
$maxScore=-1;
|
||||||
|
|
||||||
|
if ( $anyAlarms && $result = dbQuery( $frameSql ) ) {
|
||||||
|
|
||||||
|
while( $frame = $result->fetch( PDO::FETCH_ASSOC ) ) {
|
||||||
|
if ( $mId < 0 ) {
|
||||||
|
$mId = $frame['MonitorId'];
|
||||||
|
$fromSecs = $frame['TimeStampSecs'];
|
||||||
|
$toSecs = $frame['TimeStampSecs'];
|
||||||
|
$maxScore = $frame['Score'];
|
||||||
|
} else if ( $mId != $frame['MonitorId'] || $frame['TimeStampSecs'] - $toSecs > 10 ) {
|
||||||
|
// dump this one start a new
|
||||||
|
$index++;
|
||||||
|
echo "
|
||||||
|
fMonId[$index]= $mId;
|
||||||
|
fTimeFromSecs[$index]= $fromSecs;
|
||||||
|
fTimeToSecs[$index]= $toSecs;
|
||||||
|
fScore[$index]= $maxScore;
|
||||||
|
";
|
||||||
|
$mId = $frame['MonitorId'];
|
||||||
|
$fromSecs = $frame['TimeStampSecs'];
|
||||||
|
$toSecs = $frame['TimeStampSecs'];
|
||||||
|
$maxScore = $frame['Score'];
|
||||||
|
} else {
|
||||||
|
// just add this one on
|
||||||
|
$toSecs = $frame['TimeStampSecs'];
|
||||||
|
if ( $maxScore < $frame['Score'] ) $maxScore = $frame['Score'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( $mId > 0 ) {
|
||||||
|
echo "
|
||||||
|
fMonId[$index]= $mId;
|
||||||
|
fTimeFromSecs[$index]= $fromSecs;
|
||||||
|
fTimeToSecs[$index]= $toSecs;
|
||||||
|
fScore[$index]= $maxScore;
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "var maxScore=$maxScore;\n"; // used to skip frame load if we find no alarms.
|
||||||
|
echo "var monitorName = [];\n";
|
||||||
|
echo "var monitorLoading = [];\n";
|
||||||
|
echo "var monitorImageObject = [];\n";
|
||||||
|
echo "var monitorImageURL = [];\n";
|
||||||
|
echo "var monitorLoadingStageURL = [];\n";
|
||||||
|
echo "var monitorLoadStartTimems = [];\n";
|
||||||
|
echo "var monitorLoadEndTimems = [];\n";
|
||||||
|
echo "var monitorColour = [];\n";
|
||||||
|
echo "var monitorWidth = [];\n";
|
||||||
|
echo "var monitorHeight = [];\n";
|
||||||
|
echo "var monitorIndex = [];\n";
|
||||||
|
echo "var monitorNormalizeScale = [];\n";
|
||||||
|
echo "var monitorZoomScale = [];\n";
|
||||||
|
echo "var monitorCanvasObj = [];\n"; // stash location of these here so we don't have to search
|
||||||
|
echo "var monitorCanvasCtx = [];\n";
|
||||||
|
echo "var monitorPtr = []; // monitorName[monitorPtr[0]] is first monitor\n";
|
||||||
|
|
||||||
|
|
||||||
|
$numMonitors=0; // this array is indexed by the monitor ID for faster access later, so it may be sparse
|
||||||
|
$avgArea=floatval(0); // Calculations the normalizing scale
|
||||||
|
|
||||||
|
foreach ( $monitors as $m ) {
|
||||||
|
$avgArea = $avgArea + floatval($m->Width() * $m->Height());
|
||||||
|
$numMonitors++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $numMonitors > 0 ) $avgArea = $avgArea / $numMonitors;
|
||||||
|
|
||||||
|
$numMonitors = 0;
|
||||||
|
foreach ( $monitors as $m ) {
|
||||||
|
echo " monitorLoading[" . $m->Id() . "]=false;\n";
|
||||||
|
echo " monitorImageURL[" . $m->Id() . "]='".$m->getStreamSrc( array('mode'=>'single','scale'=>$defaultScale*100), '&' )."';\n";
|
||||||
|
echo " monitorLoadingStageURL[" . $m->Id() . "] = '';\n";
|
||||||
|
echo " monitorColour[" . $m->Id() . "]=\"" . $m->WebColour() . "\";\n";
|
||||||
|
echo " monitorWidth[" . $m->Id() . "]=" . $m->Width() . ";\n";
|
||||||
|
echo " monitorHeight[" . $m->Id() . "]=" . $m->Height() . ";\n";
|
||||||
|
echo " monitorIndex[" . $m->Id() . "]=" . $numMonitors . ";\n";
|
||||||
|
echo " monitorName[" . $m->Id() . "]=\"" . $m->Name() . "\";\n";
|
||||||
|
echo " monitorLoadStartTimems[" . $m->Id() . "]=0;\n";
|
||||||
|
echo " monitorLoadEndTimems[" . $m->Id() . "]=0;\n";
|
||||||
|
echo " monitorNormalizeScale[" . $m->Id() . "]=" . sqrt($avgArea / ($m->Width() * $m->Height() )) . ";\n";
|
||||||
|
$zoomScale=1.0;
|
||||||
|
if(isset($_REQUEST[ 'z' . $m->Id() ]) )
|
||||||
|
$zoomScale = floatval( validHtmlStr($_REQUEST[ 'z' . $m->Id() ]) );
|
||||||
|
echo " monitorZoomScale[" . $m->Id() . "]=" . $zoomScale . ";\n";
|
||||||
|
echo " monitorPtr[" . $numMonitors . "]=" . $m->Id() . ";\n";
|
||||||
|
$numMonitors += 1;
|
||||||
|
}
|
||||||
|
echo "var numMonitors = $numMonitors;\n";
|
||||||
|
echo "var minTimeSecs=" . $minTimeSecs . ";\n";
|
||||||
|
echo "var maxTimeSecs=" . $maxTimeSecs . ";\n";
|
||||||
|
echo "var rangeTimeSecs=" . ( $maxTimeSecs - $minTimeSecs + 1) . ";\n";
|
||||||
|
if(isset($defaultCurrentTime))
|
||||||
|
echo "var currentTimeSecs=" . strtotime($defaultCurrentTime) . ";\n";
|
||||||
|
else
|
||||||
|
echo "var currentTimeSecs=" . ($minTimeSecs + $maxTimeSecs)/2 . ";\n";
|
||||||
|
|
||||||
|
echo 'var speeds=[';
|
||||||
|
for ($i=0; $i<count($speeds); $i++)
|
||||||
|
echo (($i>0)?', ':'') . $speeds[$i];
|
||||||
|
echo "];\n";
|
||||||
|
?>
|
||||||
|
|
||||||
|
var scrubAsObject=$('scrub');
|
||||||
|
var cWidth; // save canvas width
|
||||||
|
var cHeight; // save canvas height
|
||||||
|
var canvas; // global canvas definition so we don't have to keep looking it up
|
||||||
|
var ctx;
|
||||||
|
var underSlider; // use this to hold what is hidden by the slider
|
||||||
|
var underSliderX; // Where the above was taken from (left side, Y is zero)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -40,6 +40,16 @@ $maxHeight = 0;
|
||||||
$showControl = false;
|
$showControl = false;
|
||||||
$index = 0;
|
$index = 0;
|
||||||
$monitors = array();
|
$monitors = array();
|
||||||
|
|
||||||
|
$scale = 100;
|
||||||
|
if ( isset( $_REQUEST['scale'] ) )
|
||||||
|
$scale = validInt($_REQUEST['scale']);
|
||||||
|
else if ( isset( $_COOKIE['zmMontageScale'] ) )
|
||||||
|
$scale = $_COOKIE['zmMontageScale'];
|
||||||
|
|
||||||
|
if ( ! $scale )
|
||||||
|
$scale = 100;
|
||||||
|
|
||||||
foreach( dbFetchAll( $sql ) as $row )
|
foreach( dbFetchAll( $sql ) as $row )
|
||||||
{
|
{
|
||||||
if ( !visibleMonitor( $row['Id'] ) )
|
if ( !visibleMonitor( $row['Id'] ) )
|
||||||
|
@ -47,14 +57,17 @@ foreach( dbFetchAll( $sql ) as $row )
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$monitor_scale = 100;
|
||||||
if ( isset( $_REQUEST['scale'] ) )
|
if ( isset( $_REQUEST['scale'] ) )
|
||||||
$scale = validInt($_REQUEST['scale']);
|
$monitor_scale = validInt($_REQUEST['scale']);
|
||||||
else if ( isset( $_COOKIE['zmMontageScale'] ) )
|
else if ( isset( $_COOKIE['zmMontageScale'] ) )
|
||||||
$scale = $_COOKIE['zmMontageScale'];
|
$monitor_scale = $_COOKIE['zmMontageScale'];
|
||||||
else
|
else
|
||||||
$scale = reScale( SCALE_BASE, $row['DefaultScale'], ZM_WEB_DEFAULT_SCALE );
|
$monitor_scale = reScale( SCALE_BASE, $row['DefaultScale'], ZM_WEB_DEFAULT_SCALE );
|
||||||
|
if ( ! $monitor_scale )
|
||||||
|
$monitor_scale = 100;
|
||||||
|
|
||||||
$scaleWidth = reScale( $row['Width'], $scale );
|
$scaleWidth = reScale( $row['Width'], $monitor_scale );
|
||||||
$scaleHeight = reScale( $row['Height'], $scale );
|
$scaleHeight = reScale( $row['Height'], $scale );
|
||||||
if ( $maxWidth < $scaleWidth )
|
if ( $maxWidth < $scaleWidth )
|
||||||
$maxWidth = $scaleWidth;
|
$maxWidth = $scaleWidth;
|
||||||
|
@ -117,14 +130,11 @@ foreach ( $monitors as $monitor )
|
||||||
<div id="monitor<?php echo $monitor->index() ?>" class="monitor idle">
|
<div id="monitor<?php echo $monitor->index() ?>" class="monitor idle">
|
||||||
<div id="imageFeed<?php echo $monitor->index() ?>" class="imageFeed" onclick="createPopup( '?view=watch&mid=<?php echo $monitor->Id() ?>', 'zmWatch<?php echo $monitor->Id() ?>', 'watch', <?php echo $monitor->scaleWidth() ?>, <?php echo $monitor->scaleHeight() ?> );">
|
<div id="imageFeed<?php echo $monitor->index() ?>" class="imageFeed" onclick="createPopup( '?view=watch&mid=<?php echo $monitor->Id() ?>', 'zmWatch<?php echo $monitor->Id() ?>', 'watch', <?php echo $monitor->scaleWidth() ?>, <?php echo $monitor->scaleHeight() ?> );">
|
||||||
<?php
|
<?php
|
||||||
if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT )
|
if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT ) {
|
||||||
{
|
$streamSrc = $monitor->getStreamSrc( array( 'mode'=>'mpeg', 'scale'=>$scale, 'bitrate'=>ZM_WEB_VIDEO_BITRATE, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'format'=>ZM_MPEG_LIVE_FORMAT ) );
|
||||||
$streamSrc = $monitor->getStreamSrc( array( "mode=mpeg", "scale=".$scale, "bitrate=".ZM_WEB_VIDEO_BITRATE, "maxfps=".ZM_WEB_VIDEO_MAXFPS, "format=".ZM_MPEG_LIVE_FORMAT ) );
|
|
||||||
outputVideoStream( "liveStream".$monitor->Id(), $streamSrc, reScale( $monitor->Width(), $scale ), reScale( $monitor->Height(), $scale ), ZM_MPEG_LIVE_FORMAT );
|
outputVideoStream( "liveStream".$monitor->Id(), $streamSrc, reScale( $monitor->Width(), $scale ), reScale( $monitor->Height(), $scale ), ZM_MPEG_LIVE_FORMAT );
|
||||||
}
|
} else {
|
||||||
else
|
$streamSrc = $monitor->getStreamSrc( array( 'mode'=>'jpeg', 'scale'=>$scale, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS ) );
|
||||||
{
|
|
||||||
$streamSrc = $monitor->getStreamSrc( array( "mode=jpeg", "scale=".$scale, "maxfps=".ZM_WEB_VIDEO_MAXFPS ) );
|
|
||||||
if ( canStreamNative() )
|
if ( canStreamNative() )
|
||||||
{
|
{
|
||||||
outputImageStream( "liveStream".$monitor->Id(), $streamSrc, reScale( $monitor->Width(), $scale ), reScale( $monitor->Height(), $scale ), validHtmlStr($monitor->Name()) );
|
outputImageStream( "liveStream".$monitor->Id(), $streamSrc, reScale( $monitor->Width(), $scale ), reScale( $monitor->Height(), $scale ), validHtmlStr($monitor->Name()) );
|
||||||
|
|
|
@ -95,20 +95,20 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
if ( !canView( 'Events' ) ) {
|
if ( !canView( 'Events' ) ) {
|
||||||
$view = 'error';
|
$view = 'error';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once( 'includes/Monitor.php' );
|
require_once( 'includes/Monitor.php' );
|
||||||
|
|
||||||
# FIXME THere is no way to select group at this time.
|
# FIXME THere is no way to select group at this time.
|
||||||
if ( !empty($_REQUEST['group']) ) {
|
if ( !empty($_REQUEST['group']) ) {
|
||||||
$group = $_REQUEST['group'];
|
$group = $_REQUEST['group'];
|
||||||
$row = dbFetchOne( 'SELECT * FROM Groups WHERE Id = ?', NULL, array($_REQUEST['group']) );
|
$row = dbFetchOne( 'SELECT * FROM Groups WHERE Id = ?', NULL, array($_REQUEST['group']) );
|
||||||
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None' AND find_in_set( Id, '".$row['MonitorIds']."' ) ";
|
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None' AND find_in_set( Id, '".$row['MonitorIds']."' ) ";
|
||||||
} else {
|
} else {
|
||||||
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None'";
|
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None'";
|
||||||
$group = '';
|
$group = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that this finds incomplete events as well, and any frame records written, but still cannot "see" to the end frame
|
// Note that this finds incomplete events as well, and any frame records written, but still cannot "see" to the end frame
|
||||||
|
@ -116,7 +116,7 @@ if ( !empty($_REQUEST['group']) ) {
|
||||||
// Note we round up just a bit on the end time as otherwise you get gaps, like 59.78 to 00 in the next second, which can give blank frames when moved through slowly.
|
// Note we round up just a bit on the end time as otherwise you get gaps, like 59.78 to 00 in the next second, which can give blank frames when moved through slowly.
|
||||||
|
|
||||||
$eventsSql = '
|
$eventsSql = '
|
||||||
SELECT E.Id,E.Name,E.StorageId,UNIX_TIMESTAMP(E.StartTime) AS StartTimeSecs,
|
SELECT E.Id,E.Name,UNIX_TIMESTAMP(E.StartTime) AS StartTimeSecs,
|
||||||
CASE WHEN E.EndTime IS NULL THEN (SELECT UNIX_TIMESTAMP(DATE_ADD(E.StartTime, Interval max(Delta)+0.5 Second)) FROM Frames F WHERE F.EventId=E.Id)
|
CASE WHEN E.EndTime IS NULL THEN (SELECT UNIX_TIMESTAMP(DATE_ADD(E.StartTime, Interval max(Delta)+0.5 Second)) FROM Frames F WHERE F.EventId=E.Id)
|
||||||
ELSE UNIX_TIMESTAMP(E.EndTime)
|
ELSE UNIX_TIMESTAMP(E.EndTime)
|
||||||
END AS CalcEndTimeSecs, E.Length,
|
END AS CalcEndTimeSecs, E.Length,
|
||||||
|
@ -222,7 +222,7 @@ $monitors = array();
|
||||||
$monitorsSql .= ' ORDER BY Sequence ASC';
|
$monitorsSql .= ' ORDER BY Sequence ASC';
|
||||||
$index=0;
|
$index=0;
|
||||||
foreach( dbFetchAll( $monitorsSql ) as $row ) {
|
foreach( dbFetchAll( $monitorsSql ) as $row ) {
|
||||||
$monitors[$index] = $row;
|
$monitors[$index] = new Monitor( $row );
|
||||||
$index = $index + 1;
|
$index = $index + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,921 +270,15 @@ input[type=range]::-ms-tooltip {
|
||||||
<span id="scrubright"></span>
|
<span id="scrubright"></span>
|
||||||
<span id="scruboutput"></span>
|
<span id="scruboutput"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="monitors">
|
||||||
<?php
|
<?php
|
||||||
// Monitor images - these had to be loaded after the monitors used were determined (after loading events)
|
// Monitor images - these had to be loaded after the monitors used were determined (after loading events)
|
||||||
|
|
||||||
echo '<div id="monitors">';
|
|
||||||
foreach ($monitors as $m) {
|
foreach ($monitors as $m) {
|
||||||
echo '<canvas width="' . $m['Width'] * $defaultScale . 'px" height="' . $m['Height'] * $defaultScale . 'px" id="Monitor' . $m['Id'] . '" style="border:3px solid ' . $m['WebColour'] . '" onclick="clickMonitor(event,' . $m['Id'] . ')">No Canvas Support!!</canvas>';
|
echo '<canvas width="' . $m->Width() * $defaultScale . 'px" height="' . $m->Height() * $defaultScale . 'px" id="Monitor' . $m->Id() . '" style="border:3px solid ' . $m->WebColour() . '" onclick="clickMonitor(event,' . $m->Id() . ')">No Canvas Support!!</canvas>';
|
||||||
}
|
}
|
||||||
echo "</div>\n";
|
|
||||||
echo "<p id=\"fps\">evaluating fps</p>\n";
|
|
||||||
echo "<script type=\"text/javascript\">\n";
|
|
||||||
?>
|
?>
|
||||||
|
</div>
|
||||||
var currentScale=<?php echo $defaultScale?>;
|
<p id="fps">evaluating fps</p>
|
||||||
var liveMode=<?php echo $initialModeIsLive?>;
|
|
||||||
var fitMode=<?php echo $fitMode?>;
|
|
||||||
var currentSpeed=<?php echo $speeds[$speedIndex]?>; // slider scale, which is only for replay and relative to real time
|
|
||||||
var speedIndex=<?php echo $speedIndex?>;
|
|
||||||
var currentDisplayInterval=<?php echo $initialDisplayInterval?>; // will be set based on performance, this is the display interval in milliseconds for history, and fps for live, and dynamically determined (in ms)
|
|
||||||
var playSecsperInterval=1; // How many seconds of recorded image we play per refresh determined by speed (replay rate) and display interval; (default=1 if coming from live)
|
|
||||||
var timerInterval; // milliseconds between interrupts
|
|
||||||
var timerObj; // object to hold timer interval;
|
|
||||||
var freeTimeLastIntervals=[]; // Percentage of current interval used in loading most recent image
|
|
||||||
var imageLoadTimesEvaluated=0; // running count
|
|
||||||
var imageLoadTimesNeeded=15; // and how many we need
|
|
||||||
var timeLabelsFractOfRow = 0.9;
|
|
||||||
var eMonId = [];
|
|
||||||
var eId = [];
|
|
||||||
var eStartSecs = [];
|
|
||||||
var eEndSecs = [];
|
|
||||||
var ePath = [];
|
|
||||||
var eventFrames = []; // this is going to presume all frames equal durationlength
|
|
||||||
|
|
||||||
<?php
|
|
||||||
|
|
||||||
// Because we might not have time as the criteria, figure out the min/max time when we run the query
|
|
||||||
|
|
||||||
$minTimeSecs = strtotime("2036-01-01 01:01:01");
|
|
||||||
$maxTimeSecs = strtotime("1950-01-01 01:01:01");
|
|
||||||
|
|
||||||
// This builds the list of events that are eligible from this range
|
|
||||||
|
|
||||||
$index=0;
|
|
||||||
$anyAlarms=false;
|
|
||||||
|
|
||||||
foreach( dbFetchAll( $eventsSql ) as $event ) {
|
|
||||||
if( $minTimeSecs > $event['StartTimeSecs']) $minTimeSecs = $event['StartTimeSecs'];
|
|
||||||
if( $maxTimeSecs < $event['CalcEndTimeSecs']) $maxTimeSecs = $event['CalcEndTimeSecs'];
|
|
||||||
echo "eMonId[$index]=" . $event['MonitorId'] . ";
|
|
||||||
eId[$index]=" . $event['Id'] . ";
|
|
||||||
eStartSecs[$index]=" . $event['StartTimeSecs'] . ";
|
|
||||||
eEndSecs[$index]=" . $event['CalcEndTimeSecs'] . ";
|
|
||||||
eventFrames[$index]=" . $event['Frames'] . "; ";
|
|
||||||
|
|
||||||
// NO GOOD, need to use view=image
|
|
||||||
if ( ZM_USE_DEEP_STORAGE )
|
|
||||||
echo "ePath[$index] = \"events/" . $event['MonitorId'] . "/" . strftime("%y/%m/%d/%H/%M/%S", $event['StartTimeSecs']) . "/\";" ;
|
|
||||||
else
|
|
||||||
echo "ePath[$index] = \"events/" . $event['MonitorId'] . "/" . $event['Id'] . "/\";" ;
|
|
||||||
$index = $index + 1;
|
|
||||||
if($event['MaxScore']>0)
|
|
||||||
$anyAlarms = true;
|
|
||||||
echo "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there is no data set the min/max to the passed in values
|
|
||||||
if($index == 0) {
|
|
||||||
if(isset($minTime) && isset($maxTime)) {
|
|
||||||
$minTimeSecs = strtotime($minTime);
|
|
||||||
$maxTimeSecs = strtotime($maxTime);
|
|
||||||
} else {
|
|
||||||
// this is the case of no passed in times AND no data -- just set something arbitrary
|
|
||||||
$minTimeSecs=strtotime('1950-06-01 01:01:01'); // random time so there's something to display
|
|
||||||
$maxTimeSecs=strtotime('2020-06-02 02:02:02');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only reset the calling time if there was no calling time
|
|
||||||
if(!isset($minTime) || !isset($maxTime)) {
|
|
||||||
$maxTime = strftime($maxTimeSecs);
|
|
||||||
$minTime = strftime($minTimeSecs);
|
|
||||||
} else {
|
|
||||||
$minTimeSecs = strtotime($minTime);
|
|
||||||
$maxTimeSecs = strtotime($maxTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we had any alarms in those events, this builds the list of all alarm frames, but consolidated down to (nearly) contiguous segments
|
|
||||||
// comparison in else governs how aggressively it consolidates
|
|
||||||
|
|
||||||
echo "var fMonId = [];\n";
|
|
||||||
echo "var fTimeFromSecs = [];\n";
|
|
||||||
echo "var fTimeToSecs = [];\n";
|
|
||||||
echo "var fScore = [];\n";
|
|
||||||
$maxScore=0;
|
|
||||||
$index=0;
|
|
||||||
$mId=-1;
|
|
||||||
$fromSecs=-1;
|
|
||||||
$toSecs=-1;
|
|
||||||
$maxScore=-1;
|
|
||||||
|
|
||||||
if($anyAlarms) {
|
|
||||||
foreach( dbFetchAll ($frameSql) as $frame ) {
|
|
||||||
if($mId<0) {
|
|
||||||
$mId=$frame['MonitorId'];
|
|
||||||
$fromSecs=$frame['TimeStampSecs'];
|
|
||||||
$toSecs=$frame['TimeStampSecs'];
|
|
||||||
$maxScore=$frame['Score'];
|
|
||||||
} else if ($mId != $frame['MonitorId'] || $frame['TimeStampSecs'] - $toSecs > 10) {
|
|
||||||
// dump this one start a new
|
|
||||||
$index++;
|
|
||||||
echo " fMonId[$index]=" . $mId . ";";
|
|
||||||
echo " fTimeFromSecs[$index]=" . $fromSecs . ";";
|
|
||||||
echo " fTimeToSecs[$index]=" . $toSecs . ";";
|
|
||||||
echo " fScore[$index]=" . $maxScore . ";\n";
|
|
||||||
$mId=$frame['MonitorId'];
|
|
||||||
$fromSecs=$frame['TimeStampSecs'];
|
|
||||||
$toSecs=$frame['TimeStampSecs'];
|
|
||||||
$maxScore=$frame['Score'];
|
|
||||||
} else {
|
|
||||||
// just add this one on
|
|
||||||
$toSecs=$frame['TimeStampSecs'];
|
|
||||||
if($maxScore < $frame['Score']) $maxScore=$frame['Score'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if($mId>0) {
|
|
||||||
echo " fMonId[$index]=" . $mId . ";";
|
|
||||||
echo " fTimeFromSecs[$index]=" . $fromSecs . ";";
|
|
||||||
echo " fTimeToSecs[$index]=" . $toSecs . ";";
|
|
||||||
echo " fScore[$index]=" . $maxScore . ";\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "var maxScore=$maxScore;\n"; // used to skip frame load if we find no alarms.
|
|
||||||
echo "var monitorName = [];\n";
|
|
||||||
echo "var monitorLoading = [];\n";
|
|
||||||
echo "var monitorImageObject = [];\n";
|
|
||||||
echo "var monitorLoadingStageURL = [];\n";
|
|
||||||
echo "var monitorLoadStartTimems = [];\n";
|
|
||||||
echo "var monitorLoadEndTimems = [];\n";
|
|
||||||
echo "var monitorColour = [];\n";
|
|
||||||
echo "var monitorWidth = [];\n";
|
|
||||||
echo "var monitorHeight = [];\n";
|
|
||||||
echo "var monitorIndex = [];\n";
|
|
||||||
echo "var monitorNormalizeScale = [];\n";
|
|
||||||
echo "var monitorZoomScale = [];\n";
|
|
||||||
echo "var monitorCanvasObj = [];\n"; // stash location of these here so we don't have to search
|
|
||||||
echo "var monitorCanvasCtx = [];\n";
|
|
||||||
echo "var monitorPtr = []; // monitorName[monitorPtr[0]] is first monitor\n";
|
|
||||||
|
|
||||||
|
|
||||||
$numMonitors=0; // this array is indexed by the monitor ID for faster access later, so it may be sparse
|
|
||||||
$avgArea=floatval(0); // Calculations the normalizing scale
|
|
||||||
|
|
||||||
foreach ($monitors as $m) {
|
|
||||||
$avgArea = $avgArea + floatval($m['Width'] * $m['Height']);
|
|
||||||
$numMonitors++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($numMonitors>0) $avgArea= $avgArea / $numMonitors;
|
|
||||||
|
|
||||||
$numMonitors=0;
|
|
||||||
foreach ($monitors as $m) {
|
|
||||||
echo " monitorLoading[" . $m['Id'] . "]=false; ";
|
|
||||||
echo " monitorImageObject[" . $m['Id'] . "]=null; ";
|
|
||||||
echo " monitorLoadingStageURL[" . $m['Id'] . "] = ''; ";
|
|
||||||
echo " monitorColour[" . $m['Id'] . "]=\"" . $m['WebColour'] . "\"; ";
|
|
||||||
echo " monitorWidth[" . $m['Id'] . "]=" . $m['Width'] . "; ";
|
|
||||||
echo " monitorHeight[" . $m['Id'] . "]=" . $m['Height'] . "; ";
|
|
||||||
echo " monitorIndex[" . $m['Id'] . "]=" . $numMonitors . "; ";
|
|
||||||
echo " monitorName[" . $m['Id'] . "]=\"" . $m['Name'] . "\"; ";
|
|
||||||
echo " monitorLoadStartTimems[" . $m['Id'] . "]=0; ";
|
|
||||||
echo " monitorLoadEndTimems[" . $m['Id'] . "]=0; ";
|
|
||||||
echo " monitorCanvasObj[" . $m['Id'] . "]=document.getElementById('Monitor" . $m['Id'] . "'); ";
|
|
||||||
echo " monitorCanvasCtx[" . $m['Id'] . "]=monitorCanvasObj[" . $m['Id'] . "].getContext('2d'); ";
|
|
||||||
echo " monitorNormalizeScale[" . $m['Id'] . "]=" . sqrt($avgArea / ($m['Width'] * $m['Height'] )) . "; ";
|
|
||||||
$zoomScale=1.0;
|
|
||||||
if(isset($_REQUEST[ 'z' . $m['Id'] ]) )
|
|
||||||
$zoomScale = floatval( validHtmlStr($_REQUEST[ 'z' . $m['Id'] ]) );
|
|
||||||
echo " monitorZoomScale[" . $m['Id'] . "]=" . $zoomScale . ";";
|
|
||||||
echo " monitorPtr[" . $numMonitors . "]=" . $m['Id'] . ";\n";
|
|
||||||
$numMonitors += 1;
|
|
||||||
}
|
|
||||||
echo "var numMonitors = $numMonitors;\n";
|
|
||||||
echo "var minTimeSecs=" . $minTimeSecs . ";\n";
|
|
||||||
echo "var maxTimeSecs=" . $maxTimeSecs . ";\n";
|
|
||||||
echo "var rangeTimeSecs=" . ( $maxTimeSecs - $minTimeSecs + 1) . ";\n";
|
|
||||||
if(isset($defaultCurrentTime))
|
|
||||||
echo "var currentTimeSecs=" . strtotime($defaultCurrentTime) . ";\n";
|
|
||||||
else
|
|
||||||
echo "var currentTimeSecs=" . ($minTimeSecs + $maxTimeSecs)/2 . ";\n";
|
|
||||||
|
|
||||||
echo "var speeds=[";
|
|
||||||
for ($i=0; $i<count($speeds); $i++)
|
|
||||||
echo (($i>0)?", ":"") . $speeds[$i];
|
|
||||||
echo "];\n";
|
|
||||||
?>
|
|
||||||
|
|
||||||
var scrubAsObject=document.getElementById('scrub');
|
|
||||||
var cWidth; // save canvas width
|
|
||||||
var cHeight; // save canvas height
|
|
||||||
var canvas=document.getElementById("timeline"); // global canvas definition so we don't have to keep looking it up
|
|
||||||
var ctx=canvas.getContext('2d');
|
|
||||||
var underSlider; // use this to hold what is hidden by the slider
|
|
||||||
var underSliderX; // Where the above was taken from (left side, Y is zero)
|
|
||||||
|
|
||||||
function evaluateLoadTimes() {
|
|
||||||
// Only consider it a completed event if we load ALL monitors, then zero all and start again
|
|
||||||
var start=0;
|
|
||||||
var end=0;
|
|
||||||
if(liveMode!=1 && currentSpeed==0) return; // don't evaluate when we are not moving as we can do nothing really fast.
|
|
||||||
for(var i=0; i<monitorIndex.length; i++) {
|
|
||||||
if( monitorName[i]>"") {
|
|
||||||
if( monitorLoadEndTimems[i]==0) return; // if we have a monitor with no time yet just wait
|
|
||||||
if( start == 0 || start > monitorLoadStartTimems[i] ) start = monitorLoadStartTimems[i];
|
|
||||||
if( end == 0 || end < monitorLoadEndTimems[i] ) end = monitorLoadEndTimems[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(start==0 || end==0) return; // we really should not get here
|
|
||||||
for(var i=0; i<numMonitors; i++) {
|
|
||||||
monitorLoadStartTimems[monitorPtr[i]]=0;
|
|
||||||
monitorLoadEndTimems[monitorPtr[i]]=0;
|
|
||||||
}
|
|
||||||
freeTimeLastIntervals[imageLoadTimesEvaluated++] = 1 - ((end - start)/currentDisplayInterval);
|
|
||||||
if( imageLoadTimesEvaluated < imageLoadTimesNeeded ) return;
|
|
||||||
var avgFrac=0;
|
|
||||||
for(var i=0; i<imageLoadTimesEvaluated; i++)
|
|
||||||
avgFrac += freeTimeLastIntervals[i];
|
|
||||||
avgFrac = avgFrac / imageLoadTimesEvaluated;
|
|
||||||
// The larger this is(positive) the faster we can go
|
|
||||||
if (avgFrac >= 0.9) currentDisplayInterval = (currentDisplayInterval * 0.50).toFixed(1); // we can go much faster
|
|
||||||
else if (avgFrac >= 0.8) currentDisplayInterval = (currentDisplayInterval * 0.55).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.7) currentDisplayInterval = (currentDisplayInterval * 0.60).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.6) currentDisplayInterval = (currentDisplayInterval * 0.65).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.5) currentDisplayInterval = (currentDisplayInterval * 0.70).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.4) currentDisplayInterval = (currentDisplayInterval * 0.80).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.35) currentDisplayInterval = (currentDisplayInterval * 0.90).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.3) currentDisplayInterval = (currentDisplayInterval * 1.00).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.25) currentDisplayInterval = (currentDisplayInterval * 1.20).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.2) currentDisplayInterval = (currentDisplayInterval * 1.50).toFixed(1);
|
|
||||||
else if (avgFrac >= 0.1) currentDisplayInterval = (currentDisplayInterval * 2.00).toFixed(1);
|
|
||||||
else currentDisplayInterval = (currentDisplayInterval * 2.50).toFixed(1);
|
|
||||||
currentDisplayInterval=Math.min(Math.max(currentDisplayInterval, 30),10000); // limit this from about 30fps to .1 fps
|
|
||||||
imageLoadTimesEvaluated=0;
|
|
||||||
setSpeed(speedIndex);
|
|
||||||
$('fps').innerHTML="Display refresh rate is " + (1000 / currentDisplayInterval).toFixed(1) + " per second, avgFrac=" + avgFrac.toFixed(3) + ".";
|
|
||||||
}
|
|
||||||
|
|
||||||
function SetImageSource(monId,val) {
|
|
||||||
if(liveMode==1) {
|
|
||||||
// This uses the standard php routine to set up the url and authentication, but because it is called repeatedly the built in random number is not usable, so one is appended below for two total (yuck)
|
|
||||||
var effectiveScale = (100.0 * monitorCanvasObj[monId].width) / monitorWidth[monId];
|
|
||||||
var $x = "<?php echo getStreamSrc( array("mode=single"),"&" )?>" + "&monitor=" + monId.toString() + "&scale=" + effectiveScale + Math.random().toString() ;
|
|
||||||
return $x;
|
|
||||||
} else {
|
|
||||||
var zeropad = <?php echo sprintf("\"%0" . ZM_EVENT_IMAGE_DIGITS . "d\"",0); ?>;
|
|
||||||
for(var i=0, eIdlength = eId.length; i<eIdlength; i++) {
|
|
||||||
// Search for a match
|
|
||||||
if(eMonId[i]==monId && val >= eStartSecs[i] && val <= eEndSecs[i]) {
|
|
||||||
var frame=parseInt((val - eStartSecs[i])/(eEndSecs[i]-eStartSecs[i])*eventFrames[i])+1;
|
|
||||||
img = "index.php?view=image&eid=" + eId[i] + '&fid='+frame + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height;
|
|
||||||
return img;
|
|
||||||
}
|
|
||||||
} // end for
|
|
||||||
return "no data";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function imagedone(obj, monId, success) {
|
|
||||||
if(success) {
|
|
||||||
monitorCanvasCtx[monId].drawImage( monitorImageObject[monId], 0, 0, monitorCanvasObj[monId].width, monitorCanvasObj[monId].height);
|
|
||||||
var iconSize=(Math.max(monitorCanvasObj[monId].width,monitorCanvasObj[monId].height) * 0.10);
|
|
||||||
monitorCanvasCtx[monId].font = "600 " + iconSize.toString() + "px Arial";
|
|
||||||
monitorCanvasCtx[monId].fillStyle="white";
|
|
||||||
monitorCanvasCtx[monId].globalCompositeOperation="difference";
|
|
||||||
monitorCanvasCtx[monId].fillText("+",iconSize*0.2, iconSize*1.2);
|
|
||||||
monitorCanvasCtx[monId].fillText("-",monitorCanvasObj[monId].width - iconSize*1.2, iconSize*1.2);
|
|
||||||
monitorCanvasCtx[monId].globalCompositeOperation="source-over";
|
|
||||||
monitorLoadEndTimems[monId] = new Date().getTime(); // elapsed time to load
|
|
||||||
evaluateLoadTimes();
|
|
||||||
}
|
|
||||||
monitorLoading[monId]=false;
|
|
||||||
if(!success) {
|
|
||||||
// if we had a failrue queue up the no-data image
|
|
||||||
loadImage2Monitor(monId,"no data"); // leave the staged URL if there is one, just ignore it here.
|
|
||||||
} else {
|
|
||||||
if(monitorLoadingStageURL[monId]=="") return;
|
|
||||||
loadImage2Monitor(monId,monitorLoadingStageURL[monId]);
|
|
||||||
monitorLoadingStageURL[monId]="";
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadImage2Monitor(monId,url) {
|
|
||||||
if(monitorLoading[monId] && monitorImageObject[monId].src != url ) {
|
|
||||||
// never queue the same image twice (if it's loading it has to be defined, right?
|
|
||||||
monitorLoadingStageURL[monId]=url; // we don't care if we are overriting, it means it didn't change fast enough
|
|
||||||
} else {
|
|
||||||
var skipthis=0;
|
|
||||||
if( typeof monitorImageObject[monId] !== "undefined" && monitorImageObject[monId] != null && monitorImageObject[monId].src == url ) return; // do nothing if it's the same
|
|
||||||
if( monitorImageObject[monId] == null ) {
|
|
||||||
monitorImageObject[monId]=new Image();
|
|
||||||
monitorImageObject[monId].onload = function() {imagedone(this, monId,true )};
|
|
||||||
monitorImageObject[monId].onerror = function() {imagedone(this, monId,false)};
|
|
||||||
}
|
|
||||||
if(url=='no data') {
|
|
||||||
monitorCanvasCtx[monId].fillStyle="white";
|
|
||||||
monitorCanvasCtx[monId].fillRect(0,0,monitorCanvasObj[monId].width,monitorCanvasObj[monId].height);
|
|
||||||
var textSize=monitorCanvasObj[monId].width * 0.15;
|
|
||||||
var text="No Data";
|
|
||||||
monitorCanvasCtx[monId].font = "600 " + textSize.toString() + "px Arial";
|
|
||||||
monitorCanvasCtx[monId].fillStyle="black";
|
|
||||||
var textWidth = monitorCanvasCtx[monId].measureText(text).width;
|
|
||||||
monitorCanvasCtx[monId].fillText(text,monitorCanvasObj[monId].width/2 - textWidth/2,monitorCanvasObj[monId].height/2);
|
|
||||||
} else {
|
|
||||||
monitorLoading[monId]=true;
|
|
||||||
monitorLoadStartTimems[monId]=new Date().getTime();
|
|
||||||
monitorImageObject[monId].src=url; // starts a load but doesn't refresh yet, wait until ready
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function timerFire() {
|
|
||||||
// See if we need to reschedule
|
|
||||||
if(currentDisplayInterval != timerInterval || currentSpeed == 0) {
|
|
||||||
// zero just turn off interrupts
|
|
||||||
clearInterval(timerObj);
|
|
||||||
timerInterval=currentDisplayInterval;
|
|
||||||
if(currentSpeed>0 || liveMode!=0) timerObj=setInterval(timerFire,timerInterval); // don't fire out of live mode if speed is zero
|
|
||||||
}
|
|
||||||
|
|
||||||
if (liveMode) outputUpdate(currentTimeSecs); // In live mode we basically do nothing but redisplay
|
|
||||||
else if (currentTimeSecs + playSecsperInterval >= maxTimeSecs) // beyond the end just stop
|
|
||||||
{
|
|
||||||
setSpeed(0);
|
|
||||||
outputUpdate(currentTimeSecs);
|
|
||||||
}
|
|
||||||
else outputUpdate(currentTimeSecs + playSecsperInterval);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawSliderOnGraph(val) {
|
|
||||||
var sliderWidth=10;
|
|
||||||
var sliderLineWidth=1;
|
|
||||||
var sliderHeight=cHeight;
|
|
||||||
|
|
||||||
if(liveMode==1) {
|
|
||||||
val=Math.floor( Date.now() / 1000);
|
|
||||||
}
|
|
||||||
// Set some sizes
|
|
||||||
|
|
||||||
var labelpx = Math.max( 6, Math.min( 20, parseInt(cHeight * timeLabelsFractOfRow / (numMonitors+1)) ) );
|
|
||||||
var labbottom=parseInt(cHeight * 0.2 / (numMonitors+1)).toString() + "px"; // This is positioning same as row labels below, but from bottom so 1-position
|
|
||||||
var labfont=labelpx + "px Georgia"; // set this like below row labels
|
|
||||||
|
|
||||||
if(numMonitors>0) {
|
|
||||||
// if we have no data to display don't do the slider itself
|
|
||||||
var sliderX=parseInt( (val - minTimeSecs) / rangeTimeSecs * cWidth - sliderWidth/2); // position left side of slider
|
|
||||||
if(sliderX < 0) sliderX=0;
|
|
||||||
if(sliderX+sliderWidth > cWidth) sliderX=cWidth-sliderWidth-1;
|
|
||||||
|
|
||||||
// If we have data already saved first restore it from LAST time
|
|
||||||
|
|
||||||
if(typeof underSlider !== 'undefined')
|
|
||||||
{
|
|
||||||
ctx.putImageData(underSlider,underSliderX, 0, 0, 0, sliderWidth, sliderHeight);
|
|
||||||
underSlider=undefined;
|
|
||||||
}
|
|
||||||
if(liveMode==0) // we get rid of the slider if we switch to live (since it may not be in the "right" place)
|
|
||||||
{
|
|
||||||
// Now save where we are putting it THIS time
|
|
||||||
underSlider=ctx.getImageData(sliderX, 0, sliderWidth, sliderHeight);
|
|
||||||
// And add in the slider'
|
|
||||||
ctx.lineWidth=sliderLineWidth;
|
|
||||||
ctx.strokeStyle='black';
|
|
||||||
// looks like strokes are on the outside (or could be) so shrink it by the line width so we replace all the pixels
|
|
||||||
ctx.strokeRect(sliderX+sliderLineWidth,sliderLineWidth,sliderWidth - 2*sliderLineWidth, sliderHeight - 2*sliderLineWidth);
|
|
||||||
underSliderX=sliderX;
|
|
||||||
}
|
|
||||||
var o = $('scruboutput');
|
|
||||||
if(liveMode==1)
|
|
||||||
{
|
|
||||||
o.innerHTML="Live Feed @ " + (1000 / currentDisplayInterval).toFixed(1) + " fps";
|
|
||||||
o.style.color="red";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
o.innerHTML=secs2dbstr(val);
|
|
||||||
o.style.color="blue";
|
|
||||||
}
|
|
||||||
o.style.position="absolute";
|
|
||||||
o.style.bottom=labbottom;
|
|
||||||
o.style.font=labfont;
|
|
||||||
// try to get length and then when we get too close to the right switch to the left
|
|
||||||
var len = o.offsetWidth;
|
|
||||||
var x;
|
|
||||||
if(sliderX > cWidth/2)
|
|
||||||
x=sliderX - len - 10;
|
|
||||||
else
|
|
||||||
x=sliderX + 10;
|
|
||||||
o.style.left=x.toString() + "px";
|
|
||||||
}
|
|
||||||
|
|
||||||
// This displays (or not) the left/right limits depending on how close the slider is.
|
|
||||||
// Because these change widths if the slider is too close, use the slider width as an estimate for the left/right label length (i.e. don't recalculate len from above)
|
|
||||||
// If this starts to collide increase some of the extra space
|
|
||||||
|
|
||||||
var o = $('scrubleft');
|
|
||||||
o.innerHTML=secs2dbstr(minTimeSecs);
|
|
||||||
o.style.position="absolute";
|
|
||||||
o.style.bottom=labbottom;
|
|
||||||
o.style.font=labfont;
|
|
||||||
o.style.left="5px";
|
|
||||||
if(numMonitors==0) // we need a len calculation if we skipped the slider
|
|
||||||
len = o.offsetWidth;
|
|
||||||
// If the slider will overlay part of this suppress (this is the left side)
|
|
||||||
if(len + 10 > sliderX || cWidth < len * 4 ) // that last check is for very narrow browsers
|
|
||||||
o.style.display="none";
|
|
||||||
else
|
|
||||||
{
|
|
||||||
o.style.display="inline";
|
|
||||||
o.style.display="inline-flex"; // safari won't take this but will just ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
var o = $('scrubright');
|
|
||||||
o.innerHTML=secs2dbstr(maxTimeSecs);
|
|
||||||
o.style.position="absolute";
|
|
||||||
o.style.bottom=labbottom;
|
|
||||||
o.style.font=labfont;
|
|
||||||
// If the slider will overlay part of this suppress (this is the right side)
|
|
||||||
o.style.left=(cWidth - len - 15).toString() + "px";
|
|
||||||
if(sliderX > cWidth - len - 20 || cWidth < len * 4 )
|
|
||||||
o.style.display="none";
|
|
||||||
else
|
|
||||||
{
|
|
||||||
o.style.display="inline";
|
|
||||||
o.style.display="inline-flex";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawGraph()
|
|
||||||
{
|
|
||||||
var divWidth=$('timelinediv').clientWidth
|
|
||||||
canvas.width = cWidth = divWidth; // Let it float and determine width (it should be sized a bit smaller percentage of window)
|
|
||||||
canvas.height=cHeight = parseInt(window.innerHeight * 0.10);
|
|
||||||
if(eId.length==0)
|
|
||||||
{
|
|
||||||
ctx.font="40px Georgia";
|
|
||||||
ctx.fillStyle="Black";
|
|
||||||
ctx.globalAlpha=1;
|
|
||||||
var t="No data found in range - choose differently";
|
|
||||||
var l=ctx.measureText(t).width;
|
|
||||||
ctx.fillText(t,(cWidth - l)/2, cHeight-10);
|
|
||||||
underSlider=undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var rowHeight=parseInt(cHeight / (numMonitors + 1) ); // Leave room for a scale of some sort
|
|
||||||
|
|
||||||
// first fill in the bars for the events (not alarms)
|
|
||||||
|
|
||||||
for(var i=0; i<eId.length; i++) // Display all we loaded
|
|
||||||
{
|
|
||||||
var x1=parseInt( (eStartSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
|
|
||||||
var x2=parseInt( (eEndSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round high end up to be sure consecutive ones connect
|
|
||||||
ctx.fillStyle=monitorColour[eMonId[i]];
|
|
||||||
ctx.globalAlpha = 0.2; // light color for background
|
|
||||||
ctx.clearRect(x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight); // Erase any overlap so it doesn't look artificially darker
|
|
||||||
ctx.fillRect (x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight);
|
|
||||||
}
|
|
||||||
for(var i=0; (i<fScore.length) && (maxScore>0); i++) // Now put in scored frames (if any)
|
|
||||||
{
|
|
||||||
var x1=parseInt( (fTimeFromSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
|
|
||||||
var x2=parseInt( (fTimeToSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round up
|
|
||||||
if(x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide
|
|
||||||
ctx.fillStyle=monitorColour[fMonId[i]];
|
|
||||||
ctx.globalAlpha = 0.4 + 0.6 * (1 - fScore[i]/maxScore); // Background is scaled but even lowest is twice as dark as the background
|
|
||||||
ctx.fillRect(x1,monitorIndex[fMonId[i]]*rowHeight,x2-x1,rowHeight);
|
|
||||||
}
|
|
||||||
for(var i=0; i<numMonitors; i++) // Note that this may be a sparse array
|
|
||||||
{
|
|
||||||
ctx.font= parseInt(rowHeight * timeLabelsFractOfRow).toString() + "px Georgia";
|
|
||||||
ctx.fillStyle="Black";
|
|
||||||
ctx.globalAlpha=1;
|
|
||||||
ctx.fillText(monitorName[monitorPtr[i]], 0, (i + 1 - (1 - timeLabelsFractOfRow)/2 ) * rowHeight ); // This should roughly center font in row
|
|
||||||
}
|
|
||||||
underSlider=undefined; // flag we don't have a slider cached
|
|
||||||
drawSliderOnGraph(currentTimeSecs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function redrawScreen()
|
|
||||||
{
|
|
||||||
if(fitMode==0) // if we fit, then monitors were absolutely positioned already (or will be) otherwise release them to float
|
|
||||||
{
|
|
||||||
for(var i=0; i<numMonitors; i++)
|
|
||||||
monitorCanvasObj[monitorPtr[i]].style.position="";
|
|
||||||
$('monitors').setStyle('height',"auto");
|
|
||||||
}
|
|
||||||
if(liveMode==1) // if we are not in live view switch to history -- this has to come before fit in case we re-establish the timeline
|
|
||||||
{
|
|
||||||
$('SpeedDiv').style.display="none";
|
|
||||||
$('timelinediv').style.display="none";
|
|
||||||
$('live').innerHTML="History";
|
|
||||||
$('zoomin').style.display="none";
|
|
||||||
$('zoomout').style.display="none";
|
|
||||||
$('panleft').style.display="none";
|
|
||||||
$('panright').style.display="none";
|
|
||||||
|
|
||||||
}
|
|
||||||
else // switch out of liveview mode
|
|
||||||
{
|
|
||||||
$('SpeedDiv').style.display="inline";
|
|
||||||
$('SpeedDiv').style.display="inline-flex";
|
|
||||||
$('timelinediv').style.display=null;
|
|
||||||
$('live').innerHTML="Live";
|
|
||||||
$('zoomin').style.display="inline";
|
|
||||||
$('zoomin').style.display="inline-flex";
|
|
||||||
$('zoomout').style.display="inline";
|
|
||||||
$('zoomout').style.display="inline-flex";
|
|
||||||
$('panleft').style.display="inline";
|
|
||||||
$('panleft').style.display="inline-flex";
|
|
||||||
$('panright').style.display="inline";
|
|
||||||
$('panright').style.display="inline-flex";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(fitMode==1)
|
|
||||||
{
|
|
||||||
$('ScaleDiv').style.display="none";
|
|
||||||
$('fit').innerHTML="Scale";
|
|
||||||
var vh=window.innerHeight;
|
|
||||||
var vw=window.innerWidth;
|
|
||||||
var pos=$('monitors').getPosition();
|
|
||||||
var mh=(vh - pos.y - $('fps').getSize().y);
|
|
||||||
$('monitors').setStyle('height',mh.toString() + "px"); // leave a small gap at bottom
|
|
||||||
if(maxfit2($('monitors').getSize().x,$('monitors').getSize().y) == 0) /// if we fail to fix we back out of fit mode -- ??? This may need some better handling
|
|
||||||
fitMode=1-fitMode;
|
|
||||||
}
|
|
||||||
else // switch out of fit mode
|
|
||||||
{
|
|
||||||
$('ScaleDiv').style.display="inline";
|
|
||||||
$('ScaleDiv').style.display="inline-flex";
|
|
||||||
$('fit').innerHTML="Fit";
|
|
||||||
setScale(currentScale);
|
|
||||||
}
|
|
||||||
drawGraph();
|
|
||||||
outputUpdate(currentTimeSecs);
|
|
||||||
timerFire(); // force a fire in case it's not timing
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function outputUpdate(val)
|
|
||||||
{
|
|
||||||
drawSliderOnGraph(val);
|
|
||||||
for(var i=0; i<numMonitors; i++)
|
|
||||||
{
|
|
||||||
loadImage2Monitor(monitorPtr[i],SetImageSource(monitorPtr[i],val));
|
|
||||||
}
|
|
||||||
var currentTimeMS = new Date(val*1000);
|
|
||||||
currentTimeSecs=val;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Found this here: http://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element
|
|
||||||
function relMouseCoords(event){
|
|
||||||
var totalOffsetX = 0;
|
|
||||||
var totalOffsetY = 0;
|
|
||||||
var canvasX = 0;
|
|
||||||
var canvasY = 0;
|
|
||||||
var currentElement = this;
|
|
||||||
|
|
||||||
do{
|
|
||||||
totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
|
|
||||||
totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
|
|
||||||
}
|
|
||||||
while(currentElement = currentElement.offsetParent)
|
|
||||||
|
|
||||||
canvasX = event.pageX - totalOffsetX;
|
|
||||||
canvasY = event.pageY - totalOffsetY;
|
|
||||||
|
|
||||||
return {x:canvasX, y:canvasY}
|
|
||||||
}
|
|
||||||
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
|
|
||||||
|
|
||||||
// These are the functions for mouse movement in the timeline. Note that touch is treated as a mouse move with mouse down
|
|
||||||
|
|
||||||
var mouseisdown=false;
|
|
||||||
function mdown(event) {mouseisdown=true; mmove(event);}
|
|
||||||
function mup(event) {mouseisdown=false;}
|
|
||||||
function mout(event) {mouseisdown=false;} // if we go outside treat it as release
|
|
||||||
function tmove(event) {mouseisdown=true; mmove(event);}
|
|
||||||
|
|
||||||
function mmove(event) {
|
|
||||||
if(mouseisdown) {
|
|
||||||
// only do anything if the mouse is depressed while on the sheet
|
|
||||||
var sec = minTimeSecs + rangeTimeSecs / event.target.width * event.target.relMouseCoords(event).x;
|
|
||||||
outputUpdate(sec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function secs2dbstr (s)
|
|
||||||
{
|
|
||||||
var st = (new Date(s * 1000)).format("%Y-%m-%d %H:%M:%S");
|
|
||||||
return st;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFit(value)
|
|
||||||
{
|
|
||||||
fitMode=value;
|
|
||||||
redrawScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showScale(newscale) // updates slider only
|
|
||||||
{
|
|
||||||
$('scaleslideroutput').innerHTML = parseFloat(newscale).toFixed(2).toString() + " x";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setScale(newscale) // makes actual change
|
|
||||||
{
|
|
||||||
showScale(newscale);
|
|
||||||
for(var i=0; i<numMonitors; i++)
|
|
||||||
{
|
|
||||||
monitorCanvasObj[monitorPtr[i]].width=monitorWidth[monitorPtr[i]]*monitorNormalizeScale[monitorPtr[i]]*monitorZoomScale[monitorPtr[i]]*newscale;
|
|
||||||
monitorCanvasObj[monitorPtr[i]].height=monitorHeight[monitorPtr[i]]*monitorNormalizeScale[monitorPtr[i]]*monitorZoomScale[monitorPtr[i]]*newscale;
|
|
||||||
}
|
|
||||||
currentScale=newscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSpeed(val) // updates slider only
|
|
||||||
{
|
|
||||||
$('speedslideroutput').innerHTML = parseFloat(speeds[val]).toFixed(2).toString() + " x";
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSpeed(val) // Note parameter is the index not the speed
|
|
||||||
{
|
|
||||||
var t;
|
|
||||||
if(liveMode==1) return; // we shouldn't actually get here but just in case
|
|
||||||
currentSpeed=parseFloat(speeds[val]);
|
|
||||||
speedIndex=val;
|
|
||||||
playSecsperInterval = currentSpeed * currentDisplayInterval / 1000;
|
|
||||||
showSpeed(val);
|
|
||||||
if( timerInterval != currentDisplayInterval || currentSpeed == 0 ) timerFire(); // if the timer isn't firing we need to trigger it to update
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLive(value)
|
|
||||||
{
|
|
||||||
liveMode=value;
|
|
||||||
redrawScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
|
|
||||||
// The section below are to reload this program with new parameters
|
|
||||||
|
|
||||||
function clicknav(minSecs,maxSecs,arch,live) // we use the current time if we can
|
|
||||||
{
|
|
||||||
var now = new Date() / 1000;
|
|
||||||
var minStr="";
|
|
||||||
var maxStr="";
|
|
||||||
var currentStr="";
|
|
||||||
if(minSecs>0)
|
|
||||||
{
|
|
||||||
if(maxSecs > now)
|
|
||||||
maxSecs = parseInt(now);
|
|
||||||
maxStr="&maxTime=" + secs2dbstr(maxSecs);
|
|
||||||
}
|
|
||||||
if(maxSecs>0)
|
|
||||||
minStr="&minTime=" + secs2dbstr(minSecs);
|
|
||||||
if(maxSecs==0 && minSecs==0)
|
|
||||||
{
|
|
||||||
minStr="&minTime=01/01/1950 12:00:00";
|
|
||||||
maxStr="&maxTime=12/31/2035 12:00:00";
|
|
||||||
}
|
|
||||||
var intervalStr="&displayinterval=" + currentDisplayInterval.toString();
|
|
||||||
if(minSecs && maxSecs)
|
|
||||||
{
|
|
||||||
if(currentTimeSecs > minSecs && currentTimeSecs < maxSecs) // make sure time is in the new range
|
|
||||||
currentStr="¤t=" + secs2dbstr(currentTimeSecs);
|
|
||||||
}
|
|
||||||
|
|
||||||
var liveStr="&live=0";
|
|
||||||
if(live==1)
|
|
||||||
liveStr="&live=1";
|
|
||||||
|
|
||||||
var fitStr="&fit=0";
|
|
||||||
if(fitMode==1)
|
|
||||||
fitStr="&fit=1";
|
|
||||||
|
|
||||||
var zoomStr="";
|
|
||||||
for(var i=0; i<numMonitors; i++)
|
|
||||||
if(monitorZoomScale[monitorPtr[i]] < 0.99 || monitorZoomScale[monitorPtr[i]] > 1.01) // allow for some up/down changes and just treat as 1 of almost 1
|
|
||||||
zoomStr += "&z" + monitorPtr[i].toString() + "=" + monitorZoomScale[monitorPtr[i]].toFixed(2);
|
|
||||||
|
|
||||||
var groupStr=<?php if($group=="") echo '""'; else echo "\"&group=$group\""; ?>;
|
|
||||||
var uri = "?view=" + currentView + fitStr + groupStr + minStr + maxStr + currentStr + intervalStr + liveStr + zoomStr + "&scale=" + document.getElementById("scaleslider").value + "&speed=" + speeds[document.getElementById("speedslider").value];
|
|
||||||
window.location=uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
function lastHour()
|
|
||||||
{
|
|
||||||
var now = new Date() / 1000;
|
|
||||||
clicknav(now - 3600 + 1, now,1,0);
|
|
||||||
}
|
|
||||||
function lastEight()
|
|
||||||
{
|
|
||||||
var now = new Date() / 1000;
|
|
||||||
clicknav(now - 3600*8 + 1, now,1,0);
|
|
||||||
}
|
|
||||||
function zoomin()
|
|
||||||
{
|
|
||||||
rangeTimeSecs = parseInt(rangeTimeSecs / 2);
|
|
||||||
minTimeSecs = parseInt(currentTimeSecs - rangeTimeSecs/2); // this is the slider current time, we center on that
|
|
||||||
maxTimeSecs = parseInt(currentTimeSecs + rangeTimeSecs/2);
|
|
||||||
clicknav(minTimeSecs,maxTimeSecs,1,0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function zoomout()
|
|
||||||
{
|
|
||||||
rangeTimeSecs = parseInt(rangeTimeSecs * 2);
|
|
||||||
minTimeSecs = parseInt(currentTimeSecs - rangeTimeSecs/2); // this is the slider current time, we center on that
|
|
||||||
maxTimeSecs = parseInt(currentTimeSecs + rangeTimeSecs/2);
|
|
||||||
clicknav(minTimeSecs,maxTimeSecs,1,0);
|
|
||||||
}
|
|
||||||
function panleft()
|
|
||||||
{
|
|
||||||
minTimeSecs = parseInt(minTimeSecs - rangeTimeSecs/2);
|
|
||||||
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
|
|
||||||
clicknav(minTimeSecs,maxTimeSecs,1,0);
|
|
||||||
}
|
|
||||||
function panright()
|
|
||||||
{
|
|
||||||
minTimeSecs = parseInt(minTimeSecs + rangeTimeSecs/2);
|
|
||||||
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
|
|
||||||
clicknav(minTimeSecs,maxTimeSecs,1,0);
|
|
||||||
}
|
|
||||||
function allof()
|
|
||||||
{
|
|
||||||
clicknav(0,0,1,0);
|
|
||||||
}
|
|
||||||
function allnon()
|
|
||||||
{
|
|
||||||
clicknav(0,0,0,0);
|
|
||||||
}
|
|
||||||
/// >>>>>>>>>>>>>>>>> handles packing different size/aspect monitors on screen <<<<<<<<<<<<<<<<<<<<<<<<
|
|
||||||
|
|
||||||
function compSize(a, b) // sort array by some size parameter - height seems to work best. A semi-greedy algorithm
|
|
||||||
{
|
|
||||||
if ( monitorHeight[a] * monitorWidth[a] * monitorNormalizeScale[a] * monitorZoomScale[a] * monitorNormalizeScale[a] * monitorZoomScale[a] > monitorHeight[b] * monitorWidth[b] * monitorNormalizeScale[b] * monitorZoomScale[b] * monitorNormalizeScale[b] * monitorZoomScale[b]) return -1;
|
|
||||||
else if ( monitorHeight[a] * monitorWidth[a] * monitorNormalizeScale[a] * monitorZoomScale[a] * monitorNormalizeScale[a] * monitorZoomScale[a] == monitorHeight[b] * monitorWidth[b] * monitorNormalizeScale[b] * monitorZoomScale[b] * monitorNormalizeScale[b] * monitorZoomScale[b]) return 0;
|
|
||||||
else return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function maxfit2(divW, divH)
|
|
||||||
{
|
|
||||||
var bestFitX=[]; // how we arranged the so-far best match
|
|
||||||
var bestFitX2=[];
|
|
||||||
var bestFitY=[];
|
|
||||||
var bestFitY2=[];
|
|
||||||
var bestFitScale;
|
|
||||||
|
|
||||||
var minScale=0.05;
|
|
||||||
var maxScale=5.00;
|
|
||||||
var bestFitArea=0;
|
|
||||||
|
|
||||||
var borders=-1;
|
|
||||||
|
|
||||||
monitorPtr.sort(compSize);
|
|
||||||
|
|
||||||
while(1)
|
|
||||||
{
|
|
||||||
if( maxScale - minScale < 0.01 ) break;
|
|
||||||
var thisScale = (maxScale + minScale) / 2;
|
|
||||||
var allFit=1;
|
|
||||||
var thisArea=0;
|
|
||||||
var thisX=[]; // top left
|
|
||||||
var thisY=[];
|
|
||||||
var thisX2=[]; // bottom right
|
|
||||||
var thisY2=[];
|
|
||||||
|
|
||||||
for(var m=0; m<numMonitors; m++)
|
|
||||||
{
|
|
||||||
// this loop places each monitor (if it can)
|
|
||||||
|
|
||||||
function doesItFit(x,y,w,h,d)
|
|
||||||
{ // does block (w,h) fit at position (x,y) relative to edge and other nodes already done (0..d)
|
|
||||||
if(x+w>=divW) return 0;
|
|
||||||
if(y+h>=divH) return 0;
|
|
||||||
for(var i=0; i<=d; i++)
|
|
||||||
if( !( thisX[i]>x+w-1 || thisX2[i] < x || thisY[i] > y+h-1 || thisY2[i] < y ) ) return 0;
|
|
||||||
return 1; // it's OK
|
|
||||||
}
|
|
||||||
|
|
||||||
if(borders<=0) borders=$("Monitor"+monitorPtr[m]).getStyle("border").toInt() * 2; // assume fixed size border, and added to both sides and top/bottom
|
|
||||||
// try fitting over first, then down. Each new one must land at either upper right or lower left corner of last (try in that order)
|
|
||||||
// Pick the one with the smallest Y, then smallest X if Y equal
|
|
||||||
var fitX = 999999999;
|
|
||||||
var fitY = 999999999;
|
|
||||||
for( adjacent=0; adjacent<m; adjacent++)
|
|
||||||
{
|
|
||||||
// try top right of adjacent
|
|
||||||
if( doesItFit(thisX2[adjacent]+1, thisY[adjacent], monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, m-1) == 1 )
|
|
||||||
{
|
|
||||||
if(thisY[adjacent]<fitY || ( thisY[adjacent]==fitY && thisX2[adjacent]+1 < fitX ))
|
|
||||||
{
|
|
||||||
fitX=thisX2[adjacent]+1;
|
|
||||||
fitY=thisY[adjacent];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// try bottom left
|
|
||||||
if ( doesItFit(thisX[adjacent], thisY2[adjacent]+1, monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, m-1) == 1 )
|
|
||||||
{
|
|
||||||
if(thisY2[adjacent]+1<fitY || ( thisY2[adjacent]+1 == fitY && thisX[adjacent]<fitX ))
|
|
||||||
{
|
|
||||||
fitX=thisX[adjacent];
|
|
||||||
fitY=thisY2[adjacent]+1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(m==0) // note for teh very first one there were no adjacents so the above loop didn't run
|
|
||||||
{
|
|
||||||
if( doesItFit(0,0,monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders, -1) == 1 )
|
|
||||||
{
|
|
||||||
fitX=0;
|
|
||||||
fitY=0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(fitX==999999999)
|
|
||||||
{
|
|
||||||
allFit=0;
|
|
||||||
break; // break out of monitor loop flagging we didn't fit
|
|
||||||
}
|
|
||||||
thisX[m] =fitX;
|
|
||||||
thisX2[m]=fitX + monitorWidth[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders;
|
|
||||||
thisY[m] =fitY;
|
|
||||||
thisY2[m]=fitY + monitorHeight[monitorPtr[m]] * thisScale * monitorNormalizeScale[monitorPtr[m]] * monitorZoomScale[monitorPtr[m]] + borders;
|
|
||||||
thisArea += (thisX2[m] - thisX[m])*(thisY2[m] - thisY[m]);
|
|
||||||
}
|
|
||||||
if(allFit==1)
|
|
||||||
{
|
|
||||||
minScale=thisScale;
|
|
||||||
if(bestFitArea<thisArea)
|
|
||||||
{
|
|
||||||
bestFitArea=thisArea;
|
|
||||||
bestFitX=thisX;
|
|
||||||
bestFitY=thisY;
|
|
||||||
bestFitX2=thisX2;
|
|
||||||
bestFitY2=thisY2;
|
|
||||||
bestFitScale=thisScale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // didn't fit
|
|
||||||
{
|
|
||||||
maxScale=thisScale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(bestFitArea>0) // only rearrange if we could fit -- otherwise just do nothing, let them start coming out, whatever
|
|
||||||
{
|
|
||||||
for(m=0; m<numMonitors; m++)
|
|
||||||
{
|
|
||||||
c = $("Monitor" + monitorPtr[m]);
|
|
||||||
c.style.position="absolute";
|
|
||||||
c.style.left=bestFitX[m].toString() + "px";
|
|
||||||
c.style.top=bestFitY[m].toString() + "px";
|
|
||||||
c.width = bestFitX2[m] - bestFitX[m] + 1 - borders;
|
|
||||||
c.height= bestFitY2[m] - bestFitY[m] + 1 - borders;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// >>>>>>>>>>>>>>>> Handles individual monitor clicks and navigation to the standard event/watch display
|
|
||||||
|
|
||||||
function showOneMonitor(monId) // link out to the normal view of one event's data
|
|
||||||
{
|
|
||||||
// We know the monitor, need to determine the event based on current time
|
|
||||||
var url;
|
|
||||||
if(liveMode!=0) url="?view=watch&mid=" + monId.toString();
|
|
||||||
else
|
|
||||||
for(var i=0; i<eId.length; i++)
|
|
||||||
if(eMonId[i]==monId && currentTimeSecs >= eStartSecs[i] && currentTimeSecs <= eEndSecs[i])
|
|
||||||
url="?view=event&eid=" + eId[i] + '&fid=' + parseInt(Math.max(1, Math.min(eventFrames[i], eventFrames[i] * (currentTimeSecs - eStartSecs[i]) / (eEndSecs[i] - eStartSecs[i] + 1) ) ));
|
|
||||||
createPopup(url, 'zmEvent', 'event', monitorWidth[eMonId[i]], monitorHeight[eMonId[i]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function zoom(monId,scale)
|
|
||||||
{
|
|
||||||
var lastZoomMonPriorScale=monitorZoomScale[monId];
|
|
||||||
monitorZoomScale[monId] *= scale;
|
|
||||||
if(redrawScreen()==0) // failure here is probably because we zoomed too far
|
|
||||||
{
|
|
||||||
monitorZoomScale[monId]=lastZoomMonPriorScale;
|
|
||||||
alert("You can't zoom that far -- rolling back");
|
|
||||||
redrawScreen(); // put things back and hope it works
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickMonitor(event,monId)
|
|
||||||
{
|
|
||||||
var pos_x = event.offsetX ? (event.offsetX) : event.pageX - $("Monitor"+monId.toString()).offsetLeft;
|
|
||||||
var pos_y = event.offsetY ? (event.offsetY) : event.pageY - $("Monitor"+monId.toString()).offsetTop;
|
|
||||||
if(pos_x < $("Monitor"+monId.toString()).width/4 && pos_y < $("Monitor"+monId.toString()).height/4) zoom(monId,1.15);
|
|
||||||
else if(pos_x > $("Monitor"+monId.toString()).width * 3/4 && pos_y < $("Monitor"+monId.toString()).height/4) zoom(monId,1/1.15);
|
|
||||||
else showOneMonitor(monId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// >>>>>>>>> Initialization that runs on window load by being at the bottom
|
|
||||||
|
|
||||||
drawGraph();
|
|
||||||
setSpeed(speedIndex);
|
|
||||||
setFit(fitMode); // will redraw
|
|
||||||
setLive(liveMode); // will redraw
|
|
||||||
window.addEventListener("resize",redrawScreen);
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -34,8 +34,7 @@ require_once( $skinJsPhpFile );
|
||||||
<script type="text/javascript" src="<?php echo $skinJsFile ?>"></script>
|
<script type="text/javascript" src="<?php echo $skinJsFile ?>"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
<?php
|
<?php
|
||||||
if ( !$debug )
|
if ( !$debug ) {
|
||||||
{
|
|
||||||
?>
|
?>
|
||||||
closeWindow();
|
closeWindow();
|
||||||
<?php
|
<?php
|
||||||
|
|
|
@ -18,10 +18,9 @@
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
//
|
//
|
||||||
|
|
||||||
if ( !canEdit( 'Monitors' ) )
|
if ( !canEdit( 'Monitors' ) ) {
|
||||||
{
|
$view = 'error';
|
||||||
$view = "error";
|
return;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$cameras = array();
|
$cameras = array();
|
||||||
|
@ -41,90 +40,81 @@ function execONVIF( $cmd ) {
|
||||||
$html_output<br/><br/>
|
$html_output<br/><br/>
|
||||||
Please the following command from a command line for more information:<br/><br/>$shell_command"
|
Please the following command from a command line for more information:<br/><br/>$shell_command"
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
Logger::Debug( "Results from probe: " . implode( '<br/>', $output ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
function probeCameras( $localIp )
|
function probeCameras( $localIp ) {
|
||||||
{
|
$cameras = array();
|
||||||
$cameras = array();
|
if ( $lines = @execONVIF( 'probe' ) ) {
|
||||||
$count = 0;
|
foreach ( $lines as $line ) {
|
||||||
if ( $lines = @execONVIF( "probe" ) )
|
$line = rtrim( $line );
|
||||||
{
|
if ( preg_match( '|^(.+),(.+),\s\((.*)\)$|', $line, $matches ) ) {
|
||||||
foreach ( $lines as $line )
|
$device_ep = $matches[1];
|
||||||
{
|
$soapversion = $matches[2];
|
||||||
$line = rtrim( $line );
|
$camera = array(
|
||||||
if ( preg_match( '|^(.+),(.+),\s\((.*)\)$|', $line, $matches ) )
|
'model' => 'Unknown ONVIF Camera',
|
||||||
{
|
'monitor' => array(
|
||||||
$device_ep = $matches[1];
|
'Function' => 'Monitor',
|
||||||
$soapversion = $matches[2];
|
'Type' => 'Ffmpeg',
|
||||||
$camera = array(
|
'Host' => $device_ep,
|
||||||
'model' => "Unknown ONVIF Camera",
|
'SOAP' => $soapversion,
|
||||||
'monitor' => array(
|
),
|
||||||
'Function' => "Monitor",
|
);
|
||||||
'Type' => 'Ffmpeg',
|
foreach ( preg_split('|,\s*|', $matches[3]) as $attr_val ) {
|
||||||
'Host' => $device_ep,
|
if ( preg_match( '|(.+)=\'(.*)\'|', $attr_val, $tokens ) ) {
|
||||||
'SOAP' => $soapversion,
|
if ( $tokens[1] == 'hardware' ) {
|
||||||
),
|
$camera['model'] = $tokens[2];
|
||||||
);
|
} elseif ( $tokens[1] == 'name' ) {
|
||||||
foreach ( preg_split('|,\s*|', $matches[3]) as $attr_val ) {
|
$camera['monitor']['Name'] = $tokens[2];
|
||||||
if( preg_match( '|(.+)=\'(.*)\'|', $attr_val, $tokens ) )
|
} elseif ( $tokens[1] == 'location' ) {
|
||||||
{
|
// $camera['location'] = $tokens[2];
|
||||||
if($tokens[1] == "hardware") {
|
} else {
|
||||||
$camera['model'] = $tokens[2];
|
Logger::Debug('Unknown token ' . $tokens[1] );
|
||||||
}
|
|
||||||
elseif($tokens[1] == "name") {
|
|
||||||
$camera['monitor']['Name'] = $tokens[2];
|
|
||||||
}
|
|
||||||
elseif($tokens[1] == "location") {
|
|
||||||
// $camera['location'] = $tokens[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$cameras[$count ++] = $camera;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} // end foreach token
|
||||||
return( $cameras );
|
$cameras[] = $camera;
|
||||||
|
}
|
||||||
|
} // end foreach line
|
||||||
|
}
|
||||||
|
return( $cameras );
|
||||||
}
|
}
|
||||||
|
|
||||||
function probeProfiles( $device_ep, $soapversion, $username, $password )
|
function probeProfiles( $device_ep, $soapversion, $username, $password ) {
|
||||||
{
|
$profiles = array();
|
||||||
$profiles = array();
|
if ( $lines = @execONVIF( "profiles $device_ep $soapversion $username $password" ) ) {
|
||||||
$count = 0;
|
foreach ( $lines as $line ) {
|
||||||
if ( $lines = @execONVIF( "profiles $device_ep $soapversion $username $password" ) )
|
$line = rtrim( $line );
|
||||||
{
|
if ( preg_match( '|^(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+)\s*$|', $line, $matches ) ) {
|
||||||
foreach ( $lines as $line )
|
$stream_uri = $matches[7];
|
||||||
{
|
// add user@pass to URI
|
||||||
$line = rtrim( $line );
|
if ( preg_match( '|^(\S+://)(.+)$|', $stream_uri, $tokens ) ) {
|
||||||
if ( preg_match( '|^(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+)\s*$|', $line, $matches ) )
|
$stream_uri = $tokens[1].$username.':'.$password.'@'.$tokens[2];
|
||||||
{
|
}
|
||||||
$stream_uri = $matches[7];
|
|
||||||
// add user@pass to URI
|
$profile = array( # 'monitor' part of camera
|
||||||
if( preg_match( '|^(\S+://)(.+)$|', $stream_uri, $tokens ) )
|
'Type' => 'Ffmpeg',
|
||||||
{
|
'Width' => $matches[4],
|
||||||
$stream_uri = $tokens[1].$username.':'.$password.'@'.$tokens[2];
|
'Height' => $matches[5],
|
||||||
}
|
'MaxFPS' => $matches[6],
|
||||||
|
'Path' => $stream_uri,
|
||||||
$profile = array( # 'monitor' part of camera
|
// local-only:
|
||||||
'Type' => 'Ffmpeg',
|
'Profile' => $matches[1],
|
||||||
'Width' => $matches[4],
|
'Name' => $matches[2],
|
||||||
'Height' => $matches[5],
|
'Encoding' => $matches[3],
|
||||||
'MaxFPS' => $matches[6],
|
|
||||||
'Path' => $stream_uri,
|
);
|
||||||
// local-only:
|
$profiles[] = $profile;
|
||||||
'Profile' => $matches[1],
|
} else {
|
||||||
'Name' => $matches[2],
|
Logger::Debug("Line did not match preg: $line");
|
||||||
'Encoding' => $matches[3],
|
}
|
||||||
|
|
||||||
);
|
|
||||||
$profiles[$count ++] = $profile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return( $profiles );
|
}
|
||||||
|
return( $profiles );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -137,25 +127,19 @@ xhtmlHeaders(__FILE__, translate('MonitorProbe') );
|
||||||
if( !isset($_REQUEST['step']) || ($_REQUEST['step'] == "1")) {
|
if( !isset($_REQUEST['step']) || ($_REQUEST['step'] == "1")) {
|
||||||
|
|
||||||
$monitors = array();
|
$monitors = array();
|
||||||
foreach ( dbFetchAll( "select Id, Name, Host from Monitors where Type = 'Remote' order by Host" ) as $monitor )
|
foreach ( dbFetchAll( "select Id, Name, Host from Monitors where Type = 'Remote' order by Host" ) as $monitor ) {
|
||||||
{
|
if ( preg_match( '/^(.+)@(.+)$/', $monitor['Host'], $matches ) ) {
|
||||||
if ( preg_match( '/^(.+)@(.+)$/', $monitor['Host'], $matches ) )
|
|
||||||
{
|
|
||||||
//echo "1: ".$matches[2]." = ".gethostbyname($matches[2])."<br/>";
|
//echo "1: ".$matches[2]." = ".gethostbyname($matches[2])."<br/>";
|
||||||
$monitors[gethostbyname($matches[2])] = $monitor;
|
$monitors[gethostbyname($matches[2])] = $monitor;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
//echo "2: ".$monitor['Host']." = ".gethostbyname($monitor['Host'])."<br/>";
|
//echo "2: ".$monitor['Host']." = ".gethostbyname($monitor['Host'])."<br/>";
|
||||||
$monitors[gethostbyname($monitor['Host'])] = $monitor;
|
$monitors[gethostbyname($monitor['Host'])] = $monitor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$detcameras = probeCameras( '' );
|
$detcameras = probeCameras( '' );
|
||||||
foreach ( $detcameras as $camera )
|
foreach ( $detcameras as $camera ) {
|
||||||
{
|
if ( preg_match( '|([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)|', $camera['monitor']['Host'], $matches ) ) {
|
||||||
if ( preg_match( '|([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)|', $camera['monitor']['Host'], $matches ) )
|
|
||||||
{
|
|
||||||
$ip = $matches[1];
|
$ip = $matches[1];
|
||||||
}
|
}
|
||||||
$host = $ip;
|
$host = $ip;
|
||||||
|
@ -230,10 +214,8 @@ else if($_REQUEST['step'] == "2")
|
||||||
#empty($_REQUEST['password']) )
|
#empty($_REQUEST['password']) )
|
||||||
|
|
||||||
$probe = unserialize(base64_decode($_REQUEST['probe']));
|
$probe = unserialize(base64_decode($_REQUEST['probe']));
|
||||||
foreach ( $probe as $name=>$value )
|
foreach ( $probe as $name=>$value ) {
|
||||||
{
|
if ( isset($value) ) {
|
||||||
if ( isset($value) )
|
|
||||||
{
|
|
||||||
$monitor[$name] = $value;
|
$monitor[$name] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,8 +224,7 @@ else if($_REQUEST['step'] == "2")
|
||||||
//print $monitor['Host'].", ".$_REQUEST['username'].", ".$_REQUEST['password']."<br/>";
|
//print $monitor['Host'].", ".$_REQUEST['username'].", ".$_REQUEST['password']."<br/>";
|
||||||
|
|
||||||
$detprofiles = probeProfiles( $monitor['Host'], $monitor['SOAP'], $_REQUEST['username'], $_REQUEST['password']);
|
$detprofiles = probeProfiles( $monitor['Host'], $monitor['SOAP'], $_REQUEST['username'], $_REQUEST['password']);
|
||||||
foreach ( $detprofiles as $profile )
|
foreach ( $detprofiles as $profile ) {
|
||||||
{
|
|
||||||
$monitor = $camera['monitor'];
|
$monitor = $camera['monitor'];
|
||||||
|
|
||||||
$sourceString = "${profile['Name']} : ${profile['Encoding']}" .
|
$sourceString = "${profile['Name']} : ${profile['Encoding']}" .
|
||||||
|
|
|
@ -18,14 +18,13 @@
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
//
|
//
|
||||||
|
|
||||||
if ( !canEdit( 'System' ) )
|
if ( !canEdit( 'System' ) ) {
|
||||||
{
|
$view = 'error';
|
||||||
$view = "error";
|
return;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
$running = daemonCheck();
|
$running = daemonCheck();
|
||||||
|
|
||||||
$states = dbFetchAll( "select * from States" );
|
$states = dbFetchAll( 'SELECT * FROM States' );
|
||||||
$focusWindow = true;
|
$focusWindow = true;
|
||||||
|
|
||||||
xhtmlHeaders(__FILE__, translate('RunState') );
|
xhtmlHeaders(__FILE__, translate('RunState') );
|
||||||
|
@ -36,34 +35,29 @@ xhtmlHeaders(__FILE__, translate('RunState') );
|
||||||
<h2><?php echo translate('RunState') ?></h2>
|
<h2><?php echo translate('RunState') ?></h2>
|
||||||
</div>
|
</div>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<form name="contentForm" id="contentForm" method="get" action="<?php echo $_SERVER['PHP_SELF'] ?>">
|
<form name="contentForm" id="contentForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>">
|
||||||
<?php
|
<?php
|
||||||
if ( empty($_REQUEST['apply']) )
|
if ( empty($_REQUEST['apply']) ) {
|
||||||
{
|
|
||||||
?>
|
?>
|
||||||
<input type="hidden" name="view" value="<?php echo $view ?>"/>
|
<input type="hidden" name="view" value="<?php echo $view ?>"/>
|
||||||
<input type="hidden" name="action" value=""/>
|
<input type="hidden" name="action" value=""/>
|
||||||
<input type="hidden" name="apply" value="1"/>
|
<input type="hidden" name="apply" value="1"/>
|
||||||
<p>
|
<p>
|
||||||
<select name="runState" onchange="checkState( this );">
|
<select name="runState" onchange="checkState(this);">
|
||||||
<?php
|
<?php
|
||||||
if ( $running )
|
if ( $running ) {
|
||||||
{
|
|
||||||
?>
|
?>
|
||||||
<option value="stop" selected="selected"><?php echo translate('Stop') ?></option>
|
<option value="stop" selected="selected"><?php echo translate('Stop') ?></option>
|
||||||
<option value="restart"><?php echo translate('Restart') ?></option>
|
<option value="restart"><?php echo translate('Restart') ?></option>
|
||||||
<?php
|
<?php
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
?>
|
?>
|
||||||
<option value="start" selected="selected"><?php echo translate('Start') ?></option>
|
<option value="start" selected="selected"><?php echo translate('Start') ?></option>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<?php
|
<?php
|
||||||
foreach ( $states as $state )
|
foreach ( $states as $state ) {
|
||||||
{
|
|
||||||
?>
|
?>
|
||||||
<option value="<?php echo $state['Name'] ?>"><?php echo $state['Name'] ?></option>
|
<option value="<?php echo $state['Name'] ?>"><?php echo $state['Name'] ?></option>
|
||||||
<?php
|
<?php
|
||||||
|
@ -75,9 +69,7 @@ if ( empty($_REQUEST['apply']) )
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row"><?php echo translate('NewState') ?></th>
|
<th scope="row"><?php echo translate('NewState') ?></th>
|
||||||
<!-- PP - added oninput so that changes are detected immediately -->
|
<td><input type="text" name="newState" value="" size="16" oninput="checkState(this);" onchange="checkState(this);"/></td>
|
||||||
<!-- PP - retained onchange for older browsers -->
|
|
||||||
<td><input type="text" name="newState" value="" size="16" oninput="checkState( this );" onchange="checkState(this);"/></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -88,9 +80,7 @@ if ( empty($_REQUEST['apply']) )
|
||||||
<input type="button" value="<?php echo translate('Cancel') ?>" onclick="closeWindow()"/>
|
<input type="button" value="<?php echo translate('Cancel') ?>" onclick="closeWindow()"/>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
?>
|
?>
|
||||||
<input type="hidden" name="view" value="none"/>
|
<input type="hidden" name="view" value="none"/>
|
||||||
<input type="hidden" name="action" value="state"/>
|
<input type="hidden" name="action" value="state"/>
|
||||||
|
|
|
@ -18,91 +18,82 @@
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
//
|
//
|
||||||
|
|
||||||
if ( !canView( 'Events' ) )
|
if ( !canView( 'Events' ) ) {
|
||||||
{
|
$view = "error";
|
||||||
$view = "error";
|
return;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require_once('includes/Event.php');
|
||||||
|
|
||||||
$eid = validInt($_REQUEST['eid']);
|
$eid = validInt($_REQUEST['eid']);
|
||||||
|
|
||||||
$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 = ?';
|
$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 = ?';
|
||||||
$sql_values = array( $eid );
|
$sql_values = array( $eid );
|
||||||
|
|
||||||
if ( $user['MonitorIds'] ) {
|
if ( $user['MonitorIds'] ) {
|
||||||
$monitor_ids = explode( ',', $user['MonitorIds'] );
|
$monitor_ids = explode( ',', $user['MonitorIds'] );
|
||||||
$sql .= ' AND MonitorId IN (' .implode( ',', array_fill(0,count($monitor_ids),'?') ) . ')';
|
$sql .= ' AND MonitorId IN (' .implode( ',', array_fill(0,count($monitor_ids),'?') ) . ')';
|
||||||
$sql_values = array_merge( $sql_values, $monitor_ids );
|
$sql_values = array_merge( $sql_values, $monitor_ids );
|
||||||
}
|
}
|
||||||
$event = dbFetchOne( $sql, NULL, $sql_values );
|
$event = dbFetchOne( $sql, NULL, $sql_values );
|
||||||
|
|
||||||
if ( isset( $_REQUEST['rate'] ) )
|
if ( isset( $_REQUEST['rate'] ) )
|
||||||
$rate = validInt($_REQUEST['rate']);
|
$rate = validInt($_REQUEST['rate']);
|
||||||
else
|
else
|
||||||
$rate = reScale( RATE_BASE, $event['DefaultRate'], ZM_WEB_DEFAULT_RATE );
|
$rate = reScale( RATE_BASE, $event['DefaultRate'], ZM_WEB_DEFAULT_RATE );
|
||||||
if ( isset( $_REQUEST['scale'] ) )
|
if ( isset( $_REQUEST['scale'] ) )
|
||||||
$scale = validInt($_REQUEST['scale']);
|
$scale = validInt($_REQUEST['scale']);
|
||||||
else
|
else
|
||||||
$scale = reScale( SCALE_BASE, $event['DefaultScale'], ZM_WEB_DEFAULT_SCALE );
|
$scale = reScale( SCALE_BASE, $event['DefaultScale'], ZM_WEB_DEFAULT_SCALE );
|
||||||
|
|
||||||
$eventPath = ZM_DIR_EVENTS.'/'.getEventPath( $event );
|
$Event = new Event( $event['Id'] );
|
||||||
|
$eventPath = $Event->Path();
|
||||||
|
|
||||||
$videoFormats = array();
|
$videoFormats = array();
|
||||||
$ffmpegFormats = preg_split( '/\s+/', ZM_FFMPEG_FORMATS );
|
$ffmpegFormats = preg_split( '/\s+/', ZM_FFMPEG_FORMATS );
|
||||||
foreach ( $ffmpegFormats as $ffmpegFormat )
|
foreach ( $ffmpegFormats as $ffmpegFormat ) {
|
||||||
{
|
if ( preg_match( '/^([^*]+)(\*\*?)$/', $ffmpegFormat, $matches ) ) {
|
||||||
if ( preg_match( '/^([^*]+)(\*\*?)$/', $ffmpegFormat, $matches ) )
|
$videoFormats[$matches[1]] = $matches[1];
|
||||||
{
|
if ( !isset($videoFormat) && $matches[2] == '*' ) {
|
||||||
$videoFormats[$matches[1]] = $matches[1];
|
$videoFormat = $matches[1];
|
||||||
if ( !isset($videoFormat) && $matches[2] == "*" )
|
|
||||||
{
|
|
||||||
$videoFormat = $matches[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$videoFormats[$ffmpegFormat] = $ffmpegFormat;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$videoFormats[$ffmpegFormat] = $ffmpegFormat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$videoFiles = array();
|
$videoFiles = array();
|
||||||
if ( $dir = opendir( $eventPath ) )
|
if ( $dir = opendir( $eventPath ) ) {
|
||||||
{
|
while ( ($file = readdir( $dir )) !== false ) {
|
||||||
while ( ($file = readdir( $dir )) !== false )
|
$file = $eventPath.'/'.$file;
|
||||||
{
|
if ( is_file( $file ) ) {
|
||||||
$file = $eventPath.'/'.$file;
|
if ( preg_match( '/\.(?:'.join( '|', $videoFormats ).')$/', $file ) ) {
|
||||||
if ( is_file( $file ) )
|
$videoFiles[] = $file;
|
||||||
{
|
}
|
||||||
if ( preg_match( '/\.(?:'.join( '|', $videoFormats ).')$/', $file ) )
|
|
||||||
{
|
|
||||||
$videoFiles[] = $file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
closedir( $dir );
|
}
|
||||||
|
closedir( $dir );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isset($_REQUEST['deleteIndex']) )
|
if ( isset($_REQUEST['deleteIndex']) ) {
|
||||||
{
|
$deleteIndex = validInt($_REQUEST['deleteIndex']);
|
||||||
$deleteIndex = validInt($_REQUEST['deleteIndex']);
|
unlink( $videoFiles[$deleteIndex] );
|
||||||
unlink( $videoFiles[$deleteIndex] );
|
unset( $videoFiles[$deleteIndex] );
|
||||||
unset( $videoFiles[$deleteIndex] );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isset($_REQUEST['downloadIndex']) )
|
if ( isset($_REQUEST['downloadIndex']) ) {
|
||||||
{
|
$downloadIndex = validInt($_REQUEST['downloadIndex']);
|
||||||
$downloadIndex = validInt($_REQUEST['downloadIndex']);
|
header( "Pragma: public" );
|
||||||
header( "Pragma: public" );
|
header( "Expires: 0" );
|
||||||
header( "Expires: 0" );
|
header( "Cache-Control: must-revalidate, post-check=0, pre-check=0" );
|
||||||
header( "Cache-Control: must-revalidate, post-check=0, pre-check=0" );
|
header( "Cache-Control: private", false ); // required by certain browsers
|
||||||
header( "Cache-Control: private", false ); // required by certain browsers
|
header( "Content-Description: File Transfer" );
|
||||||
header( "Content-Description: File Transfer" );
|
header( 'Content-disposition: attachment; filename="'.basename($videoFiles[$downloadIndex]).'"' ); // basename is required because the video index contains the path and firefox doesn't strip the path but simply replaces the slashes with an underscore.
|
||||||
header( 'Content-disposition: attachment; filename="'.basename($videoFiles[$downloadIndex]).'"' ); // basename is required because the video index contains the path and firefox doesn't strip the path but simply replaces the slashes with an underscore.
|
header( "Content-Transfer-Encoding: binary" );
|
||||||
header( "Content-Transfer-Encoding: binary" );
|
header( "Content-Type: application/force-download" );
|
||||||
header( "Content-Type: application/force-download" );
|
header( "Content-Length: ".filesize($videoFiles[$downloadIndex]) );
|
||||||
header( "Content-Length: ".filesize($videoFiles[$downloadIndex]) );
|
readfile( $videoFiles[$downloadIndex] );
|
||||||
readfile( $videoFiles[$downloadIndex] );
|
exit;
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$focusWindow = true;
|
$focusWindow = true;
|
||||||
|
@ -119,19 +110,16 @@ xhtmlHeaders(__FILE__, translate('Video') );
|
||||||
</div>
|
</div>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<?php
|
<?php
|
||||||
if ( isset($_REQUEST['showIndex']) )
|
if ( isset($_REQUEST['showIndex']) ) {
|
||||||
{
|
$showIndex = validInt($_REQUEST['showIndex']);
|
||||||
$showIndex = validInt($_REQUEST['showIndex']);
|
preg_match( '/([^\/]+)\.([^.]+)$/', $videoFiles[$showIndex], $matches );
|
||||||
preg_match( '/([^\/]+)\.([^.]+)$/', $videoFiles[$showIndex], $matches );
|
$name = $matches[1];
|
||||||
$name = $matches[1];
|
$videoFormat = $matches[2];
|
||||||
$videoFormat = $matches[2];
|
|
||||||
?>
|
?>
|
||||||
<h3 id="videoFile"><?php echo substr( $videoFiles[$showIndex], strlen(ZM_DIR_EVENTS)+1 ) ?></h3>
|
<h3 id="videoFile"><?php echo substr( $videoFiles[$showIndex], strlen(ZM_DIR_EVENTS)+1 ) ?></h3>
|
||||||
<div id="imageFeed"><?php outputVideoStream( 'videoStream', $videoFiles[$showIndex], validInt($_REQUEST['width']), validInt($_REQUEST['height']), $videoFormat, $name ) ?></div>
|
<div id="imageFeed"><?php outputVideoStream( 'videoStream', $videoFiles[$showIndex], validInt($_REQUEST['width']), validInt($_REQUEST['height']), $videoFormat, $name ) ?></div>
|
||||||
<?php
|
<?php
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
?>
|
?>
|
||||||
<form name="contentForm" id="contentForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>">
|
<form name="contentForm" id="contentForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>">
|
||||||
<input type="hidden" name="id" value="<?php echo $event['Id'] ?>"/>
|
<input type="hidden" name="id" value="<?php echo $event['Id'] ?>"/>
|
||||||
|
@ -158,29 +146,23 @@ else
|
||||||
<input type="button" value="<?php echo translate('GenerateVideo') ?>" onclick="generateVideo( this.form );"<?php if ( !ZM_OPT_FFMPEG ) { ?> disabled="disabled"<?php } ?>/>
|
<input type="button" value="<?php echo translate('GenerateVideo') ?>" onclick="generateVideo( this.form );"<?php if ( !ZM_OPT_FFMPEG ) { ?> disabled="disabled"<?php } ?>/>
|
||||||
</form>
|
</form>
|
||||||
<?php
|
<?php
|
||||||
if ( isset($_REQUEST['generated']) )
|
if ( isset($_REQUEST['generated']) ) {
|
||||||
{
|
|
||||||
?>
|
?>
|
||||||
<h2 id="videoProgress" class="<?php echo $_REQUEST['generated']?'infoText':'errorText' ?>"><span id="videoProgressText"><?php echo $_REQUEST['generated']?translate('VideoGenSucceeded'):translate('VideoGenFailed') ?></span><span id="videoProgressTicker"></span></h2>
|
<h2 id="videoProgress" class="<?php echo $_REQUEST['generated']?'infoText':'errorText' ?>"><span id="videoProgressText"><?php echo $_REQUEST['generated']?translate('VideoGenSucceeded'):translate('VideoGenFailed') ?></span><span id="videoProgressTicker"></span></h2>
|
||||||
<?php
|
<?php
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
?>
|
?>
|
||||||
<h2 id="videoProgress" class="hidden warnText"><span id="videoProgressText"><?php echo translate('GeneratingVideo') ?></span><span id="videoProgressTicker"></span></h2>
|
<h2 id="videoProgress" class="hidden warnText"><span id="videoProgressText"><?php echo translate('GeneratingVideo') ?></span><span id="videoProgressTicker"></span></h2>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<h2 id="videoFilesHeader"><?php echo translate('VideoGenFiles') ?></h2>
|
<h2 id="videoFilesHeader"><?php echo translate('VideoGenFiles') ?></h2>
|
||||||
<?php
|
<?php
|
||||||
if ( count($videoFiles) == 0 )
|
if ( count($videoFiles) == 0 ) {
|
||||||
{
|
|
||||||
?>
|
?>
|
||||||
<h3 id="videoNoFiles"><?php echo translate('VideoGenNoFiles') ?></h3>
|
<h3 id="videoNoFiles"><?php echo translate('VideoGenNoFiles') ?></h3>
|
||||||
<?php
|
<?php
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
?>
|
?>
|
||||||
<table id="videoTable" class="major" cellspacing="0">
|
<table id="videoTable" class="major" cellspacing="0">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -194,32 +176,24 @@ else
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php
|
<?php
|
||||||
$index = 0;
|
$index = 0;
|
||||||
foreach ( $videoFiles as $file )
|
foreach ( $videoFiles as $file ) {
|
||||||
{
|
if ( filesize( $file ) > 0 ) {
|
||||||
if ( filesize( $file ) > 0 )
|
preg_match( '/^(.+)-((?:r[_\d]+)|(?:F[_\d]+))-((?:s[_\d]+)|(?:S[0-9a-z]+))\.([^.]+)$/', $file, $matches );
|
||||||
{
|
if ( preg_match( '/^r(.+)$/', $matches[2], $temp_matches ) ) {
|
||||||
preg_match( '/^(.+)-((?:r[_\d]+)|(?:F[_\d]+))-((?:s[_\d]+)|(?:S[0-9a-z]+))\.([^.]+)$/', $file, $matches );
|
$rate = (int)(100 * preg_replace( '/_/', '.', $temp_matches[1] ) );
|
||||||
if ( preg_match( '/^r(.+)$/', $matches[2], $temp_matches ) )
|
$rateText = isset($rates[$rate])?$rates[$rate]:($rate."x");
|
||||||
{
|
} elseif ( preg_match( '/^F(.+)$/', $matches[2], $temp_matches ) ) {
|
||||||
$rate = (int)(100 * preg_replace( '/_/', '.', $temp_matches[1] ) );
|
$rateText = $temp_matches[1]."fps";
|
||||||
$rateText = isset($rates[$rate])?$rates[$rate]:($rate."x");
|
}
|
||||||
}
|
if ( preg_match( '/^s(.+)$/', $matches[3], $temp_matches ) ) {
|
||||||
elseif ( preg_match( '/^F(.+)$/', $matches[2], $temp_matches ) )
|
$scale = (int)(100 * preg_replace( '/_/', '.', $temp_matches[1] ) );
|
||||||
{
|
$scaleText = isset($scales[$scale])?$scales[$scale]:($scale."x");
|
||||||
$rateText = $temp_matches[1]."fps";
|
} elseif ( preg_match( '/^S(.+)$/', $matches[3], $temp_matches ) ) {
|
||||||
}
|
$scaleText = $temp_matches[1];
|
||||||
if ( preg_match( '/^s(.+)$/', $matches[3], $temp_matches ) )
|
}
|
||||||
{
|
$width = $scale?reScale( $event['Width'], $scale ):$event['Width'];
|
||||||
$scale = (int)(100 * preg_replace( '/_/', '.', $temp_matches[1] ) );
|
$height = $scale?reScale( $event['Height'], $scale ):$event['Height'];
|
||||||
$scaleText = isset($scales[$scale])?$scales[$scale]:($scale."x");
|
|
||||||
}
|
|
||||||
elseif ( preg_match( '/^S(.+)$/', $matches[3], $temp_matches ) )
|
|
||||||
{
|
|
||||||
$scaleText = $temp_matches[1];
|
|
||||||
}
|
|
||||||
$width = $scale?reScale( $event['Width'], $scale ):$event['Width'];
|
|
||||||
$height = $scale?reScale( $event['Height'], $scale ):$event['Height'];
|
|
||||||
?>
|
?>
|
||||||
<tr>
|
<tr>
|
||||||
<td><?php echo $matches[4] ?></td>
|
<td><?php echo $matches[4] ?></td>
|
||||||
|
@ -229,14 +203,14 @@ else
|
||||||
<td><?php echo makePopupLink( '?view='.$view.'&eid='.$event['Id'].'&width='.$width.'&height='.$height.'&showIndex='.$index, 'zmVideo'.$event['Id'].'-'.$scale, array( 'videoview', $width, $height ), translate('View') ); ?> / <a href="<?php echo substr( $file, strlen(ZM_DIR_EVENTS)+1 ) ?>" onclick="downloadVideo( <?php echo $index ?> ); return( false );"><?php echo translate('Download') ?></a> / <a href="#" onclick="deleteVideo( <?php echo $index ?> ); return( false );"><?php echo translate('Delete') ?></a></td>
|
<td><?php echo makePopupLink( '?view='.$view.'&eid='.$event['Id'].'&width='.$width.'&height='.$height.'&showIndex='.$index, 'zmVideo'.$event['Id'].'-'.$scale, array( 'videoview', $width, $height ), translate('View') ); ?> / <a href="<?php echo substr( $file, strlen(ZM_DIR_EVENTS)+1 ) ?>" onclick="downloadVideo( <?php echo $index ?> ); return( false );"><?php echo translate('Download') ?></a> / <a href="#" onclick="deleteVideo( <?php echo $index ?> ); return( false );"><?php echo translate('Delete') ?></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php
|
<?php
|
||||||
$index++;
|
$index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program; if not, write to the Free Software
|
// 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.
|
||||||
//
|
//
|
||||||
|
|
||||||
// Calling sequence: ... /zm/index.php?view=video&event_id=123
|
// Calling sequence: ... /zm/index.php?view=video&event_id=123
|
||||||
|
|
59
zm.conf.in
59
zm.conf.in
|
@ -1,16 +1,15 @@
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
#
|
#
|
||||||
# ZoneMinder Base Configuration File
|
# ZoneMinder Base Configuration
|
||||||
#
|
#
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
#
|
#
|
||||||
# *** DO NOT EDIT THIS FILE ***
|
# *** DO NOT EDIT THIS FILE ***
|
||||||
# Changes made directly to this configuration file are no longer supported.
|
|
||||||
# They will be overwritten during an upgrade.
|
|
||||||
#
|
#
|
||||||
# Instead, create a custom configuration file, with an extention of ".conf"
|
# To make custom changes to the variables below, create a new configuration
|
||||||
# under the @ZM_CONFIG_SUBDIR@ subfolder containing your desired modifications.
|
# file, with an extention of .conf, under the @ZM_CONFIG_SUBDIR@
|
||||||
#
|
# folder, containing your desired modifications.
|
||||||
|
#
|
||||||
|
|
||||||
# Path to installed data directory, used mostly for finding DB upgrade scripts
|
# Path to installed data directory, used mostly for finding DB upgrade scripts
|
||||||
ZM_PATH_DATA=@PKGDATADIR@
|
ZM_PATH_DATA=@PKGDATADIR@
|
||||||
|
@ -50,43 +49,19 @@ ZM_DB_USER=@ZM_DB_USER@
|
||||||
# ZoneMinder database password
|
# ZoneMinder database password
|
||||||
ZM_DB_PASS=@ZM_DB_PASS@
|
ZM_DB_PASS=@ZM_DB_PASS@
|
||||||
|
|
||||||
# Full path to the folder events are recorded to.
|
# SSL CA certificate for ZoneMinder database
|
||||||
# The web account user must have full read/write permission to this folder.
|
ZM_DB_SSL_CA_CERT=@ZM_DB_SSL_CA_CERT@
|
||||||
ZM_DIR_EVENTS=@ZM_DIR_EVENTS@
|
|
||||||
|
|
||||||
# Full path to the folder images, not directly associated with events,
|
# SSL client key for ZoneMinder database
|
||||||
# are recorded to.
|
ZM_DB_SSL_CLIENT_KEY=@ZM_DB_SSL_CLIENT_KEY@
|
||||||
# The web account user must have full read/write permission to this folder.
|
|
||||||
ZM_DIR_IMAGES=@ZM_DIR_IMAGES@
|
|
||||||
|
|
||||||
# Foldername under the webroot where ZoneMinder looks for optional sound files
|
# SSL client cert for ZoneMinder database
|
||||||
# to play when an alarm is detected.
|
ZM_DB_SSL_CLIENT_CERT=@ZM_DB_SSL_CLIENT_CERT@
|
||||||
ZM_DIR_SOUNDS=@ZM_DIR_SOUNDS@
|
|
||||||
|
|
||||||
# Full path to the folder where exported archives are stored
|
# Do NOT set ZM_SERVER_HOST if you are not using Multi-Server
|
||||||
# The web account user must have full read/write permission to this folder.
|
# You have been warned
|
||||||
ZM_DIR_EXPORTS=@ZM_TMPDIR@
|
#
|
||||||
|
# The name specified here must have a corresponding entry
|
||||||
# ZoneMinder url path to the zms streaming server
|
# in the Servers tab under Options
|
||||||
ZM_PATH_ZMS=@ZM_PATH_ZMS@
|
ZM_SERVER_HOST=
|
||||||
|
|
||||||
# Full Path to ZoneMinder's mapped memory files
|
|
||||||
# The web account user must have full read/write permission to this folder.
|
|
||||||
ZM_PATH_MAP=@ZM_PATH_MAP@
|
|
||||||
|
|
||||||
# Full Path to ZoneMinder's socket folder
|
|
||||||
# The web account user must have full read/write permission to this folder.
|
|
||||||
ZM_PATH_SOCKS=@ZM_SOCKDIR@
|
|
||||||
|
|
||||||
# Full path to ZoneMinder's log folder
|
|
||||||
# The web account user must have full read/write permission to this folder.
|
|
||||||
ZM_PATH_LOGS=@ZM_LOGDIR@
|
|
||||||
|
|
||||||
# Full path to ZoneMinder's swap folder
|
|
||||||
# The web account user must have full read/write permission to this folder.
|
|
||||||
ZM_PATH_SWAP=@ZM_TMPDIR@
|
|
||||||
|
|
||||||
# Full path to optional arp binary
|
|
||||||
# ZoneMinder will find the arp binary automatically on most systems
|
|
||||||
ZM_PATH_ARP=@ZM_PATH_ARP@
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue