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
|
||||
"The group apache or the local web server runs on,
|
||||
Leave empty to be the same as the web user")
|
||||
set(ZM_DIR_EVENTS "events" CACHE PATH
|
||||
"Location where events are recorded to, default: events")
|
||||
set(ZM_DIR_IMAGES "events" CACHE PATH
|
||||
set(ZM_DIR_EVENTS "${ZM_CONTENTDIR}/events" CACHE PATH
|
||||
"Location where events are recorded to, default: ZM_CONTENTDIR/events")
|
||||
set(ZM_DIR_IMAGES "${ZM_CONTENTDIR}/images" CACHE PATH
|
||||
"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
|
||||
"Location to look for optional sound files, default: sounds")
|
||||
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.")
|
||||
set(ZM_CONFIG_DIR "/${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH
|
||||
"Location of ZoneMinder configuration, default system config directory")
|
||||
set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/conf.d" CACHE PATH
|
||||
set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/zm/conf.d" CACHE PATH
|
||||
"Location of ZoneMinder configuration subfolder, default: ZM_CONFIG_DIR/conf.d")
|
||||
set(ZM_EXTRA_LIBS "" CACHE STRING
|
||||
"A list of optional libraries, separated by semicolons, e.g. ssl;theora")
|
||||
|
@ -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(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))
|
||||
|
||||
# Generate files from the .in files
|
||||
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(zmconfgen.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmconfgen.pl" @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(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
|
||||
configure_file(
|
||||
|
|
11
INSTALL
11
INSTALL
|
@ -45,9 +45,20 @@ Possible configuration options:
|
|||
ZM_DB_NAME Name of ZoneMinder database, default: zm
|
||||
ZM_DB_USER Name of ZoneMinder database user, default: zmuser
|
||||
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_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:
|
||||
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_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
|
||||
|
|
|
@ -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)
|
||||
- Debian from their [default repository](https://packages.debian.org/search?searchon=names&keywords=zoneminder)
|
||||
- RHEL/CentOS and clones via [zmrepo](http://zmrepo.zoneminder.com/)
|
||||
- Fedora via [zmrepo](http://zmrepo.zoneminder.com/)
|
||||
- RHEL/CentOS and clones via [RPM Fusion](http://rpmfusion.org)
|
||||
- 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)
|
||||
- Mageia from their default repository
|
||||
|
||||
|
|
|
@ -55,6 +55,9 @@ echo "Database host : $ZM_DB_HOST"
|
|||
echo "Database name : $ZM_DB_NAME"
|
||||
echo "Database user : $ZM_DB_USER"
|
||||
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"
|
||||
|
@ -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_USER \"$ZM_DB_USER\" $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 "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',
|
||||
`V4LMultiBuffer` tinyint(1) unsigned,
|
||||
`V4LCapturesPerFrame` tinyint(3) unsigned,
|
||||
`Protocol` varchar(16) NOT NULL default '',
|
||||
`Protocol` varchar(16) default '',
|
||||
`Method` varchar(16) NOT NULL default '',
|
||||
`Host` varchar(64),
|
||||
`Port` varchar(8) NOT NULL default '',
|
||||
`SubPath` varchar(64) NOT NULL default '',
|
||||
`Path` varchar(255),
|
||||
`Options` varchar(255) not null default '',
|
||||
`Options` varchar(255) default '',
|
||||
`User` varchar(64),
|
||||
`Pass` varchar(64),
|
||||
`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,'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,'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
|
||||
|
|
|
@ -6,3 +6,4 @@ usr/share/perl5/ZoneMinder.pm
|
|||
usr/share/zoneminder/db
|
||||
usr/share/zoneminder/www
|
||||
etc/zm
|
||||
etc/zm/conf.d/*
|
||||
|
|
|
@ -18,7 +18,6 @@ override_dh_auto_configure:
|
|||
-DZM_TMPDIR=/var/tmp/zm \
|
||||
-DZM_LOGDIR=/var/log/zm \
|
||||
-DZM_WEBDIR=/usr/share/zoneminder/www \
|
||||
-DZM_CONTENTDIR=/var/cache/zoneminder \
|
||||
-DZM_CGIDIR=/usr/lib/zoneminder/cgi-bin \
|
||||
-DZM_WEB_USER=www-data \
|
||||
-DZM_WEB_GROUP=www-data \
|
||||
|
|
|
@ -9,15 +9,15 @@ else(ZM_TARGET_DISTRO MATCHES "^el")
|
|||
message([WARNING] "Unknown Build Option Detected" ...)
|
||||
endif(ZM_TARGET_DISTRO MATCHES "^el")
|
||||
|
||||
if((ZM_TARGET_DISTRO STREQUAL "el6") AND (ZM_WEB_USER STREQUAL "nginx"))
|
||||
message([FATAL_ERROR] "Nginx is Not a Supported Build Option on EL6 Target Distro" ...)
|
||||
endif((ZM_TARGET_DISTRO STREQUAL "el6") AND (ZM_WEB_USER STREQUAL "nginx"))
|
||||
if((NOT ZM_TARGET_DISTRO MATCHES "^fc") AND (ZM_WEB_USER STREQUAL "nginx"))
|
||||
message([FATAL_ERROR] "Experimental Nginx support is currently only supported on Fedora" ...)
|
||||
endif((NOT ZM_TARGET_DISTRO MATCHES "^fc") AND (ZM_WEB_USER STREQUAL "nginx"))
|
||||
|
||||
# Configure the zoneminder service files
|
||||
if(ZM_TARGET_DISTRO STREQUAL "el6")
|
||||
configure_file(sysvinit/zoneminder.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.sysvinit @ONLY)
|
||||
configure_file(sysvinit/zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY)
|
||||
configure_file(sysvinit/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
|
||||
configure_file(apache/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
|
||||
else(ZM_TARGET_DISTRO STREQUAL "el6")
|
||||
configure_file(systemd/zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY)
|
||||
if(ZM_WEB_USER STREQUAL "nginx")
|
||||
|
@ -28,7 +28,7 @@ else(ZM_TARGET_DISTRO STREQUAL "el6")
|
|||
configure_file(nginx/README.Fedora ${CMAKE_CURRENT_SOURCE_DIR}/readme/README.Fedora COPYONLY)
|
||||
else(ZM_WEB_USER STREQUAL "nginx")
|
||||
configure_file(systemd/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY)
|
||||
configure_file(systemd/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
|
||||
configure_file(apache/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
|
||||
configure_file(systemd/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY)
|
||||
endif(ZM_WEB_USER STREQUAL "nginx")
|
||||
endif(ZM_TARGET_DISTRO STREQUAL "el6")
|
||||
|
@ -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 events images temp DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
|
||||
# Create symlinks
|
||||
install(CODE "execute_process(COMMAND ln -sf ../../../../var/lib/zoneminder/events \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/events\")")
|
||||
install(CODE "execute_process(COMMAND ln -sf ../../../../var/lib/zoneminder/images \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/images\")")
|
||||
install(CODE "execute_process(COMMAND ln -sf ../../../../var/lib/zoneminder/temp \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/temp\")")
|
||||
# Symlink the cake php temp folder to the ZoneMinder temp folder
|
||||
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
|
||||
|
|
|
@ -16,7 +16,7 @@ Alias /zm "@ZM_WEBDIR@"
|
|||
DirectoryIndex index.php
|
||||
SSLRequireSSL
|
||||
Options -Indexes +MultiViews +FollowSymLinks
|
||||
AllowOverride All
|
||||
AllowOverride None
|
||||
<IfModule mod_authz_core.c>
|
||||
# Apache 2.4
|
||||
Require all granted
|
||||
|
@ -31,7 +31,7 @@ Alias /zm "@ZM_WEBDIR@"
|
|||
ScriptAlias /cgi-bin-zm "@ZM_CGIDIR@"
|
||||
<Directory "@ZM_CGIDIR@">
|
||||
SSLRequireSSL
|
||||
AllowOverride All
|
||||
AllowOverride None
|
||||
Options +ExecCGI +FollowSymLinks
|
||||
<IfModule mod_authz_core.c>
|
||||
# Apache 2.4
|
||||
|
@ -44,3 +44,28 @@ ScriptAlias /cgi-bin-zm "@ZM_CGIDIR@"
|
|||
</IfModule>
|
||||
</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
|
||||
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 start mariadb.service
|
||||
mysql_secure_installation
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# ZoneMinder systemd unit file for CentOS 7
|
||||
# ZoneMinder systemd unit file for RedHat distros and clones
|
||||
|
||||
[Unit]
|
||||
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::FTP)
|
||||
Requires: perl(LWP::Protocol::https)
|
||||
Requires: ca-certificates
|
||||
|
||||
%{?with_init_systemd:Requires(post): systemd}
|
||||
%{?with_init_systemd:Requires(post): systemd-sysv}
|
||||
|
@ -162,7 +163,14 @@ too much degradation of performance.
|
|||
%make_install
|
||||
|
||||
# Remove unwanted files and folders
|
||||
find %{buildroot} \( -name .packlist -or -name .git -or -name .gitignore -or -name .gitattributes -or -name .travis.yml \) -type f -delete > /dev/null 2>&1 || :
|
||||
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
|
||||
%if 0%{?with_init_sysv}
|
||||
|
@ -278,12 +286,18 @@ rm -rf %{_docdir}/%{name}-%{version}
|
|||
%files
|
||||
%license COPYING
|
||||
%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/conf.d
|
||||
|
||||
# Config folder contents contain sensitive info
|
||||
# and should not be readable by normal users
|
||||
%{_sysconfdir}/zm/conf.d/README
|
||||
# Always overwrite zm.conf now that ZoneMinder supports conf.d folder
|
||||
%attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/zm.conf
|
||||
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/zm.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) %{_sysconfdir}/logrotate.d/zoneminder
|
||||
|
@ -297,6 +311,7 @@ rm -rf %{_docdir}/%{name}-%{version}
|
|||
%{_unitdir}/zoneminder.service
|
||||
%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy
|
||||
%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
|
||||
%{_bindir}/zmsystemctl.pl
|
||||
%endif
|
||||
|
||||
%if 0%{?with_init_sysv}
|
||||
|
@ -318,7 +333,6 @@ rm -rf %{_docdir}/%{name}-%{version}
|
|||
%{_bindir}/zmvideo.pl
|
||||
%{_bindir}/zmwatch.pl
|
||||
%{_bindir}/zmcamtool.pl
|
||||
%{_bindir}/zmsystemctl.pl
|
||||
%{_bindir}/zmtelemetry.pl
|
||||
%{_bindir}/zmx10.pl
|
||||
%{_bindir}/zmonvif-probe.pl
|
||||
|
|
|
@ -25,7 +25,6 @@ override_dh_auto_configure:
|
|||
-DZM_SOCKDIR="/var/run/zm" \
|
||||
-DZM_TMPDIR="/tmp/zm" \
|
||||
-DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \
|
||||
-DZM_CONTENTDIR="/var/cache/zoneminder" \
|
||||
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
||||
-DZM_DIR_IMAGES="/var/cache/zoneminder/images" \
|
||||
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms" \
|
||||
|
|
|
@ -4,3 +4,5 @@ var/cache/zoneminder/events
|
|||
var/cache/zoneminder/images
|
||||
var/cache/zoneminder/temp
|
||||
usr/share/zoneminder/db
|
||||
etc/zm
|
||||
etc/zm/conf.d
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
etc/zm/zm.conf
|
||||
etc/zm/conf.d/*
|
||||
usr/bin
|
||||
usr/lib/zoneminder
|
||||
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
|
||||
|
|
|
@ -4,51 +4,51 @@ set -e
|
|||
|
||||
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
|
||||
chown www-data:root /var/log/zm
|
||||
chown www-data:www-data /var/lib/zm
|
||||
if [ -z "$2" ]; then
|
||||
chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/*
|
||||
fi
|
||||
# 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:www-data /var/lib/zm
|
||||
if [ -z "$2" ]; then
|
||||
chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/*
|
||||
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 [ -e "/etc/init.d/mysql" ]; then
|
||||
#
|
||||
# Get mysql started if it isn't
|
||||
#
|
||||
if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then
|
||||
invoke-rc.d mysql start
|
||||
fi
|
||||
if $(/etc/init.d/mysql status >/dev/null 2>&1); then
|
||||
mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload
|
||||
# test if database if already present...
|
||||
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
|
||||
# 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
|
||||
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
|
||||
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.'
|
||||
if [ "$ZM_DB_HOST" = "localhost" ]; then
|
||||
if [ -e "/etc/init.d/mysql" ]; then
|
||||
#
|
||||
# Get mysql started if it isn't
|
||||
#
|
||||
if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then
|
||||
invoke-rc.d mysql start
|
||||
fi
|
||||
if $(/etc/init.d/mysql status >/dev/null 2>&1); then
|
||||
mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload
|
||||
# test if database if already present...
|
||||
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
|
||||
# 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
|
||||
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
|
||||
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
|
||||
echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)"
|
||||
echo 'mysql not found, assuming remote server.'
|
||||
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
|
||||
|
||||
#DEBHELPER#
|
||||
|
|
|
@ -58,7 +58,6 @@ override_dh_auto_configure:
|
|||
-DZM_TMPDIR=/var/tmp/zm \
|
||||
-DZM_LOGDIR=/var/log/zm \
|
||||
-DZM_WEBDIR=/usr/share/zoneminder \
|
||||
-DZM_CONTENTDIR=/var/cache/zoneminder \
|
||||
-DZM_CGIDIR=/usr/lib/cgi-bin \
|
||||
-DZM_WEB_USER=www-data \
|
||||
-DZM_WEB_GROUP=www-data \
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
etc/zm
|
||||
etc/zm/conf.d/*
|
||||
usr/bin
|
||||
usr/share/polkit-1/actions
|
||||
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_TMPDIR="/tmp/zm" \
|
||||
-DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \
|
||||
-DZM_CONTENTDIR="/var/cache/zoneminder" \
|
||||
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
||||
-DZM_DIR_IMAGES="/var/cache/zoneminder/images" \
|
||||
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
etc/zm/zm.conf
|
||||
etc/zm/conf.d/*
|
||||
usr/bin
|
||||
usr/lib/zoneminder
|
||||
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
|
||||
|
|
|
@ -21,6 +21,8 @@ if [ "$1" = "configure" ]; then
|
|||
# Ensure zoneminder is stopped
|
||||
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 [ -e "/etc/init.d/mysql" ]; then
|
||||
|
@ -46,10 +48,10 @@ if [ "$1" = "configure" ]; then
|
|||
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.'
|
||||
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.'
|
||||
echo 'mysql not found, assuming remote server.'
|
||||
fi
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
- Zmrepo assumes you have installed the underlying distribution **using the official installation media for that distro**. Third party "Spins" 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.
|
||||
|
||||
- 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
|
||||
**********
|
||||
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:
|
||||
|
||||
|
@ -67,22 +105,20 @@ The method documented below was chosen because:
|
|||
|
||||
- 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***
|
||||
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
|
||||
***********************
|
||||
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:
|
||||
|
@ -96,73 +132,67 @@ Your build environment is now set up.
|
|||
|
||||
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:
|
||||
|
||||
::
|
||||
|
||||
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:
|
||||
|
||||
::
|
||||
|
||||
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:
|
||||
|
||||
::
|
||||
|
||||
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.
|
||||
|
||||
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/>`_
|
||||
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:
|
||||
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:
|
||||
|
||||
::
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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:
|
||||
cd ~\ZoneMinder
|
||||
git pull origin master
|
||||
|
||||
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:
|
||||
|
||||
::
|
||||
|
||||
cd ~\ZoneMinder
|
||||
rpmbuild -bs --nodeps distros/redhat/zoneminder.spec
|
||||
|
||||
Notice we used the rpm specfile that is part of the latest master branch you just downloaded, rather than the one that may be in your ~/rpmbbuild/SOURCES folder.
|
||||
|
||||
This step will overwrite the SRPM you originally downloaded, so you may want to back it up prior to completing this step. Note that the name of the specfile will vary slightly depending on the target distro.
|
||||
|
||||
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.
|
||||
This step will create a source rpm and it will tell you where it was saved. For example:
|
||||
|
||||
::
|
||||
|
||||
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
|
||||
^^^^^^
|
||||
This is the recommended source type for most modern ip cameras.
|
||||
|
||||
Source Path
|
||||
Use this field to enter the full URL of the stream or file 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:
|
||||
|
||||
* 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)
|
||||
* 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)
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
@ZM_LOGDIR@/*.log {
|
||||
missingok
|
||||
notifempty
|
||||
delaycompress
|
||||
compress
|
||||
sharedscripts
|
||||
postrotate
|
||||
/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();
|
||||
|
||||
foreach my $profile ( @{ $profiles } ) {
|
||||
foreach my $profile ( @{ $profiles } ) {
|
||||
|
||||
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.
|
||||
# http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl#op.GetStreamUri
|
||||
|
@ -278,9 +271,18 @@ sub profiles
|
|||
die $result if not $result;
|
||||
# print $result . "\n";
|
||||
|
||||
print $result->get_MediaUri()->get_Uri() .
|
||||
"\n";
|
||||
}
|
||||
my $VideoEncoderConfiguration = $profile->get_VideoEncoderConfiguration();
|
||||
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 ???
|
||||
|
|
|
@ -33,8 +33,11 @@ FOREACH(PERLSCRIPT ${perlscripts})
|
|||
ENDFOREACH(PERLSCRIPT ${perlscripts})
|
||||
|
||||
# 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)
|
||||
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)
|
||||
|
||||
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 {
|
||||
$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}
|
||||
.$socket
|
||||
.$socket.$sslOptions
|
||||
, $Config{ZM_DB_USER}
|
||||
, $Config{ZM_DB_PASS}
|
||||
) 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 {
|
||||
$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}
|
||||
.$socket . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '' )
|
||||
.$socket . $sslOptions . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '' )
|
||||
, $Config{ZM_DB_USER}
|
||||
, $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
|
||||
# will save memory.
|
||||
our %EXPORT_TAGS = (
|
||||
'constants' => [ qw(
|
||||
constants => [ qw(
|
||||
DEBUG
|
||||
INFO
|
||||
WARNING
|
||||
|
@ -50,7 +50,7 @@ our %EXPORT_TAGS = (
|
|||
PANIC
|
||||
NOLOG
|
||||
) ],
|
||||
'functions' => [ qw(
|
||||
functions => [ qw(
|
||||
logInit
|
||||
logReinit
|
||||
logTerm
|
||||
|
@ -72,13 +72,14 @@ our %EXPORT_TAGS = (
|
|||
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 Carp;
|
||||
use POSIX;
|
||||
use IO::Handle;
|
||||
use Data::Dumper;
|
||||
use Time::HiRes qw/gettimeofday/;
|
||||
use Sys::Syslog;
|
||||
use DBI;
|
||||
use Carp;
|
||||
use POSIX;
|
||||
use IO::Handle;
|
||||
use Data::Dumper;
|
||||
use Time::HiRes qw/gettimeofday/;
|
||||
use Sys::Syslog;
|
||||
|
||||
use constant {
|
||||
DEBUG => 1,
|
||||
INFO => 0,
|
||||
WARNING => -1,
|
||||
ERROR => -2,
|
||||
FATAL => -3,
|
||||
PANIC => -4,
|
||||
NOLOG => -5
|
||||
};
|
||||
use constant {
|
||||
DEBUG => 1,
|
||||
INFO => 0,
|
||||
WARNING => -1,
|
||||
ERROR => -2,
|
||||
FATAL => -3,
|
||||
PANIC => -4,
|
||||
NOLOG => -5
|
||||
};
|
||||
|
||||
our %codes = (
|
||||
&DEBUG => "DBG",
|
||||
&INFO => "INF",
|
||||
&WARNING => "WAR",
|
||||
&ERROR => "ERR",
|
||||
&FATAL => "FAT",
|
||||
&PANIC => "PNC",
|
||||
&NOLOG => "OFF"
|
||||
&DEBUG => 'DBG',
|
||||
&INFO => 'INF',
|
||||
&WARNING => 'WAR',
|
||||
&ERROR => 'ERR',
|
||||
&FATAL => 'FAT',
|
||||
&PANIC => 'PNC',
|
||||
&NOLOG => 'OFF'
|
||||
);
|
||||
|
||||
our %priorities = (
|
||||
&DEBUG => "debug",
|
||||
&INFO => "info",
|
||||
&WARNING => "warning",
|
||||
&ERROR => "err",
|
||||
&FATAL => "err",
|
||||
&PANIC => "err"
|
||||
&DEBUG => 'debug',
|
||||
&INFO => 'info',
|
||||
&WARNING => 'warning',
|
||||
&ERROR => 'err',
|
||||
&FATAL => 'err',
|
||||
&PANIC => 'err'
|
||||
);
|
||||
|
||||
our $logger;
|
||||
|
@ -134,10 +135,10 @@ sub new {
|
|||
|
||||
$this->{initialised} = undef;
|
||||
|
||||
#$this->{id} = "zmundef";
|
||||
#$this->{id} = 'zmundef';
|
||||
( $this->{id} ) = $0 =~ m|^(?:.*/)?([^/]+?)(?:\.[^/.]+)?$|;
|
||||
$this->{idRoot} = $this->{id};
|
||||
$this->{idArgs} = "";
|
||||
$this->{idArgs} = '';
|
||||
|
||||
$this->{level} = INFO;
|
||||
$this->{termLevel} = NOLOG;
|
||||
|
@ -151,7 +152,7 @@ sub new {
|
|||
|
||||
( $this->{fileName} = $0 ) =~ s|^.*/||;
|
||||
$this->{logPath} = $Config{ZM_PATH_LOGS};
|
||||
$this->{logFile} = $this->{logPath}."/".$this->{id}.".log";
|
||||
$this->{logFile} = $this->{logPath}.'/'.$this->{id}.".log";
|
||||
|
||||
$this->{trace} = 0;
|
||||
|
||||
|
@ -196,7 +197,7 @@ sub initialise( @ ) {
|
|||
$this->{logPath} = $options{logPath} if ( defined($options{logPath}) );
|
||||
|
||||
my $tempLogFile;
|
||||
$tempLogFile = $this->{logPath}."/".$this->{id}.".log";
|
||||
$tempLogFile = $this->{logPath}.'/'.$this->{id}.".log";
|
||||
$tempLogFile = $options{logFile} if ( defined($options{logFile}) );
|
||||
if ( my $logFile = $this->getTargettedEnv('LOG_FILE') ) {
|
||||
$tempLogFile = $logFile;
|
||||
|
@ -231,7 +232,6 @@ sub initialise( @ ) {
|
|||
|
||||
my $level;
|
||||
$tempLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL')) );
|
||||
|
||||
$tempTermLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_TERM')) );
|
||||
$tempDatabaseLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_DATABASE')) );
|
||||
$tempFileLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_FILE')) );
|
||||
|
@ -240,9 +240,9 @@ sub initialise( @ ) {
|
|||
if ( $Config{ZM_LOG_DEBUG} ) {
|
||||
foreach my $target ( split( /\|/, $Config{ZM_LOG_DEBUG_TARGET} ) ) {
|
||||
if ( $target eq $this->{id}
|
||||
|| $target eq "_".$this->{id}
|
||||
|| $target eq '_'.$this->{id}
|
||||
|| $target eq $this->{idRoot}
|
||||
|| $target eq "_".$this->{idRoot}
|
||||
|| $target eq '_'.$this->{idRoot}
|
||||
|| $target eq ""
|
||||
) {
|
||||
if ( $Config{ZM_LOG_DEBUG_LEVEL} > NOLOG ) {
|
||||
|
@ -271,13 +271,13 @@ sub initialise( @ ) {
|
|||
|
||||
$this->{initialised} = !undef;
|
||||
|
||||
Debug( "LogOpts: level=".$codes{$this->{level}}
|
||||
."/".$codes{$this->{effectiveLevel}}
|
||||
.", screen=".$codes{$this->{termLevel}}
|
||||
.", database=".$codes{$this->{databaseLevel}}
|
||||
.", logfile=".$codes{$this->{fileLevel}}
|
||||
."->".$this->{logFile}
|
||||
.", syslog=".$codes{$this->{syslogLevel}}
|
||||
Debug( 'LogOpts: level='.$codes{$this->{level}}
|
||||
.'/'.$codes{$this->{effectiveLevel}}
|
||||
.', screen='.$codes{$this->{termLevel}}
|
||||
.', database='.$codes{$this->{databaseLevel}}
|
||||
.', logfile='.$codes{$this->{fileLevel}}
|
||||
.'->'.$this->{logFile}
|
||||
.', syslog='.$codes{$this->{syslogLevel}}
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -293,24 +293,28 @@ sub terminate {
|
|||
sub reinitialise {
|
||||
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} );
|
||||
|
||||
# Bit of a nasty hack to reopen connections to log files and the DB
|
||||
my $syslogLevel = $this->syslogLevel();
|
||||
$this->syslogLevel( NOLOG );
|
||||
$this->syslogLevel( $syslogLevel ) if ( $syslogLevel > NOLOG );
|
||||
|
||||
my $logfileLevel = $this->fileLevel();
|
||||
$this->fileLevel( NOLOG );
|
||||
$this->fileLevel( $logfileLevel ) if ( $logfileLevel > NOLOG );
|
||||
|
||||
my $databaseLevel = $this->databaseLevel();
|
||||
$this->databaseLevel( NOLOG );
|
||||
$this->databaseLevel( $databaseLevel ) if ( $databaseLevel > NOLOG );
|
||||
|
||||
my $screenLevel = $this->termLevel();
|
||||
$this->termLevel( NOLOG );
|
||||
|
||||
$this->syslogLevel( $syslogLevel ) if ( $syslogLevel > NOLOG );
|
||||
$this->fileLevel( $logfileLevel ) if ( $logfileLevel > NOLOG );
|
||||
$this->databaseLevel( $databaseLevel ) if ( $databaseLevel > NOLOG );
|
||||
$this->databaseLevel( $databaseLevel ) if ( $databaseLevel > NOLOG );
|
||||
$this->termLevel( $screenLevel ) if ( $screenLevel > NOLOG );
|
||||
}
|
||||
|
||||
# Prevents undefined logging levels
|
||||
sub limit {
|
||||
my $this = shift;
|
||||
my $level = shift;
|
||||
|
@ -322,11 +326,11 @@ sub limit {
|
|||
sub getTargettedEnv {
|
||||
my $this = shift;
|
||||
my $name = shift;
|
||||
my $envName = $name."_".$this->{id};
|
||||
my $envName = $name.'_'.$this->{id};
|
||||
my $value;
|
||||
$value = $ENV{$envName} if ( defined($ENV{$envName}) );
|
||||
if ( !defined($value) && $this->{id} ne $this->{idRoot} ) {
|
||||
$envName = $name."_".$this->{idRoot};
|
||||
$envName = $name.'_'.$this->{idRoot};
|
||||
$value = $ENV{$envName} if ( defined($ENV{$envName}) );
|
||||
}
|
||||
if ( !defined($value) ) {
|
||||
|
@ -371,12 +375,16 @@ sub level {
|
|||
my $level = shift;
|
||||
if ( defined($level) ) {
|
||||
$this->{level} = $this->limit( $level );
|
||||
|
||||
# effectiveLevel is the highest logging level used by any of the outputs.
|
||||
$this->{effectiveLevel} = NOLOG;
|
||||
$this->{effectiveLevel} = $this->{termLevel} if ( $this->{termLevel} > $this->{effectiveLevel} );
|
||||
$this->{effectiveLevel} = $this->{databaseLevel} if ( $this->{databaseLevel} > $this->{effectiveLevel} );
|
||||
$this->{effectiveLevel} = $this->{fileLevel} if ( $this->{fileLevel} > $this->{effectiveLevel} );
|
||||
$this->{effectiveLevel} = $this->{syslogLevel} if ( $this->{syslogLevel} > $this->{level} );
|
||||
$this->{effectiveLevel} = $this->{level} if ( $this->{effectiveLevel} > $this->{level} );
|
||||
$this->{effectiveLevel} = $this->{syslogLevel} if ( $this->{syslogLevel} > $this->{effectiveLevel} );
|
||||
|
||||
# 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} );
|
||||
}
|
||||
|
@ -396,6 +404,7 @@ sub termLevel {
|
|||
my $this = shift;
|
||||
my $termLevel = shift;
|
||||
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 = $this->limit( $termLevel );
|
||||
if ( $this->{termLevel} != $termLevel ) {
|
||||
|
@ -425,8 +434,17 @@ sub databaseLevel {
|
|||
} else {
|
||||
$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}
|
||||
.$socket
|
||||
.$socket.$sslOptions
|
||||
, $Config{ZM_DB_USER}
|
||||
, $Config{ZM_DB_PASS}
|
||||
);
|
||||
|
@ -490,7 +508,7 @@ sub syslogLevel {
|
|||
|
||||
sub openSyslog {
|
||||
my $this = shift;
|
||||
openlog( $this->{id}, "pid", "local1" );
|
||||
openlog( $this->{id}, 'pid', "local1" );
|
||||
}
|
||||
|
||||
sub closeSyslog {
|
||||
|
@ -523,6 +541,7 @@ sub openFile {
|
|||
}
|
||||
} else {
|
||||
$this->fileLevel( NOLOG );
|
||||
$this->termLevel( INFO );
|
||||
Error( "Can't open log file '".$this->{logFile}."': $!" );
|
||||
}
|
||||
}
|
||||
|
@ -544,10 +563,8 @@ sub logPrint {
|
|||
|
||||
my ($seconds, $microseconds) = gettimeofday();
|
||||
my $message = sprintf(
|
||||
"%s.%06d %s[%d].%s [%s]"
|
||||
, strftime( "%x %H:%M:%S"
|
||||
,localtime( $seconds )
|
||||
)
|
||||
'%s.%06d %s[%d].%s [%s]'
|
||||
, strftime( '%x %H:%M:%S' ,localtime( $seconds ) )
|
||||
, $microseconds
|
||||
, $this->{id}
|
||||
, $$
|
||||
|
@ -564,7 +581,7 @@ sub logPrint {
|
|||
}
|
||||
print( $LOGFILE $message ) if ( $level <= $this->{fileLevel} );
|
||||
if ( $level <= $this->{databaseLevel} ) {
|
||||
my $sql = "insert into Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, NULL )";
|
||||
my $sql = 'insert into Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, NULL )';
|
||||
$this->{sth} = $this->{dbh}->prepare_cached( $sql );
|
||||
if ( !$this->{sth} ) {
|
||||
$this->{databaseLevel} = NOLOG;
|
||||
|
@ -589,7 +606,7 @@ sub logPrint {
|
|||
|
||||
sub logInit( ;@ ) {
|
||||
my %options = @_ ? @_ : ();
|
||||
$logger = ZoneMinder::Logger->new() if ( !$logger );
|
||||
$logger = ZoneMinder::Logger->new() if !$logger;
|
||||
$logger->initialise( %options );
|
||||
}
|
||||
|
||||
|
@ -646,14 +663,14 @@ sub logSyslogLevel {
|
|||
sub Mark {
|
||||
my $level = shift;
|
||||
$level = DEBUG unless( defined($level) );
|
||||
my $tag = "Mark";
|
||||
my $tag = 'Mark';
|
||||
fetch()->logPrint( $level, $tag );
|
||||
}
|
||||
|
||||
sub Dump {
|
||||
my $var = shift;
|
||||
my $label = shift;
|
||||
$label = "VAR" unless( defined($label) );
|
||||
$label = 'VAR' unless( defined($label) );
|
||||
fetch()->logPrint( DEBUG, Data::Dumper->Dump( [ $var ], [ $label ] ) );
|
||||
}
|
||||
|
||||
|
@ -695,10 +712,10 @@ ZoneMinder::Logger - ZoneMinder Logger module
|
|||
use ZoneMinder::Logger;
|
||||
use ZoneMinder::Logger qw(:all);
|
||||
|
||||
logInit( "myproc", DEBUG );
|
||||
logInit( 'myproc', DEBUG );
|
||||
|
||||
Debug( "This is what is happening" );
|
||||
Info( "Something interesting is happening" );
|
||||
Debug( 'This is what is happening' );
|
||||
Info( 'Something interesting is happening' );
|
||||
Warning( "Something might be going wrong." );
|
||||
Error( "Something has gone wrong!!" );
|
||||
Fatal( "Something has gone badly wrong, gotta stop!!" );
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# ==========================================================================
|
||||
#
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# ==========================================================================
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
||||
zmtelemetry.pl - Send usage information to the ZoneMinder development team
|
||||
|
@ -44,300 +342,3 @@ console under Options.
|
|||
none currently
|
||||
|
||||
=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
|
||||
#
|
||||
# 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
|
||||
|
||||
zmtrigger.pl - ZoneMinder External Trigger Script
|
||||
|
@ -104,504 +526,6 @@ likely to be easier.
|
|||
|
||||
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
|
||||
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
|
||||
|
||||
zmvideo.pl [ -e <event_id>,--event=<event_id> | --filter=<filter name> ] [--format <format>]
|
||||
[--rate=<rate>]
|
||||
[--scale=<scale>]
|
||||
[--fps=<fps>]
|
||||
[--size=<size>]
|
||||
[--overwrite]
|
||||
zmvideo.pl [ -e <event_id>,--event=<event_id> | --filter_name=<filter name> | --filter_id=<filter id> ]
|
||||
[--concat=filename]
|
||||
[--format <format>]
|
||||
[--rate=<rate>]
|
||||
[--scale=<scale>]
|
||||
[--fps=<fps>]
|
||||
[--size=<size>]
|
||||
[--overwrite]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
|
@ -94,20 +96,11 @@ my $size = '';
|
|||
my $overwrite = 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(
|
||||
'concat|c:s' =>\$concat_name,
|
||||
'concat|c:s' =>\$concat_name,
|
||||
'event|e=i' =>\$event_id,
|
||||
'filter_name=s' =>\$filter_name,
|
||||
'filter_id=i' =>\$filter_id,
|
||||
'filter_id=i' =>\$filter_id,
|
||||
'format|f=s' =>\$format,
|
||||
'rate|r=f' =>\$rate,
|
||||
'scale|s=f' =>\$scale,
|
||||
|
@ -115,51 +108,51 @@ GetOptions(
|
|||
'size|S=s' =>\$size,
|
||||
'overwrite' =>\$overwrite,
|
||||
'version' =>\$version
|
||||
) or pod2usage(-exitstatus => -1);
|
||||
) or pod2usage(-exitstatus => -1);
|
||||
|
||||
if ( $version ) {
|
||||
print ZoneMinder::Base::ZM_VERSION . "\n";
|
||||
exit(0);
|
||||
print ZoneMinder::Base::ZM_VERSION . "\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ( !( $filter_id or $filter_name or $event_id ) || $event_id < 0 )
|
||||
{
|
||||
print( STDERR "Please give a valid event id or filter name\n" );
|
||||
pod2usage(-exitstatus => -1);
|
||||
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" );
|
||||
pod2usage(-exitstatus => -1);
|
||||
}
|
||||
|
||||
if ( ! $Config{ZM_OPT_FFMPEG} )
|
||||
{
|
||||
print( STDERR "Mpeg encoding is not currently enabled\n" );
|
||||
exit(-1);
|
||||
if ( ! $Config{ZM_OPT_FFMPEG} ) {
|
||||
print( STDERR "Mpeg encoding is not currently enabled\n" );
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if ( !$rate && !$fps )
|
||||
{
|
||||
$rate = 1;
|
||||
my @formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} );
|
||||
for ( my $i = 0; $i < @formats; $i++ ) {
|
||||
if ( $i =~ /^(.+)\*$/ ) {
|
||||
$format = $formats[$i] = $1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$scale && !$size )
|
||||
{
|
||||
$scale = 1;
|
||||
if ( !$rate && !$fps ) {
|
||||
$rate = 1;
|
||||
}
|
||||
|
||||
if ( $rate && ($rate < 0.25 || $rate > 100) )
|
||||
{
|
||||
print( STDERR "Rate is out of range, 0.25 >= rate <= 100\n" );
|
||||
pod2usage(-exitstatus => -1);
|
||||
if ( !$scale && !$size ) {
|
||||
$scale = 1;
|
||||
}
|
||||
|
||||
if ( $scale && ($scale < 0.25 || $scale > 4) )
|
||||
{
|
||||
print( STDERR "Scale is out of range, 0.25 >= scale <= 4\n" );
|
||||
pod2usage(-exitstatus => -1);
|
||||
if ( $rate && ($rate < 0.25 || $rate > 100) ) {
|
||||
print( STDERR "Rate is out of range, 0.25 >= rate <= 100\n" );
|
||||
pod2usage(-exitstatus => -1);
|
||||
}
|
||||
|
||||
if ( $fps && ($fps > 30) )
|
||||
{
|
||||
print( STDERR "FPS is out of range, <= 30\n" );
|
||||
pod2usage(-exitstatus => -1);
|
||||
if ( $scale && ($scale < 0.25 || $scale > 4) ) {
|
||||
print( STDERR "Scale is out of range, 0.25 >= scale <= 4\n" );
|
||||
pod2usage(-exitstatus => -1);
|
||||
}
|
||||
|
||||
if ( $fps && ($fps > 30) ) {
|
||||
print( STDERR "FPS is out of range, <= 30\n" );
|
||||
pod2usage(-exitstatus => -1);
|
||||
}
|
||||
|
||||
my ( $detaint_format ) = $format =~ /^(\w+)$/;
|
||||
|
@ -181,79 +174,85 @@ my $cwd = getcwd;
|
|||
my $video_name;
|
||||
my @event_ids;
|
||||
if ( $event_id ) {
|
||||
@event_ids = ( $event_id );
|
||||
|
||||
@event_ids = ( $event_id );
|
||||
|
||||
} elsif ( $filter_name or $filter_id ) {
|
||||
my $Filter = ZoneMinder::Filter->find_one(
|
||||
($filter_name ? ( Name => $filter_name ) : () ),
|
||||
($filter_id ? ( Id => $filter_name ) : () ),
|
||||
);
|
||||
if ( ! $Filter ) {
|
||||
Fatal("Filter $filter_name $filter_id not found.");
|
||||
}
|
||||
@event_ids = map { $_->{Id} }$Filter->Execute();
|
||||
Fatal( "No events found for $filter_name") if ! @event_ids;
|
||||
$concat_name = $filter_name if $concat_name eq '';
|
||||
my $Filter = ZoneMinder::Filter->find_one(
|
||||
($filter_name ? ( Name => $filter_name ) : () ),
|
||||
($filter_id ? ( Id => $filter_name ) : () ),
|
||||
);
|
||||
if ( ! $Filter ) {
|
||||
Fatal("Filter $filter_name $filter_id not found.");
|
||||
}
|
||||
@event_ids = map { $_->{Id} } $Filter->Execute();
|
||||
if ( ! @event_ids ) {
|
||||
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,
|
||||
E.*,
|
||||
unix_timestamp(E.StartTime) as Time,
|
||||
my $sql = " SELECT (SELECT max(Delta) FROM Frames WHERE EventId=Events.Id)-(SELECT min(Delta) FROM Frames WHERE EventId=Events.Id) as FullLength,
|
||||
Events.*,
|
||||
unix_timestamp(Events.StartTime) as Time,
|
||||
M.Name as MonitorName,
|
||||
M.Width as MonitorWidth,
|
||||
M.Height as MonitorHeight,
|
||||
M.Palette
|
||||
FROM Frames as F
|
||||
INNER JOIN Events as E on F.EventId = E.Id
|
||||
INNER JOIN Monitors as M on E.MonitorId = M.Id
|
||||
WHERE EventId = ?
|
||||
GROUP BY F.EventId"
|
||||
;
|
||||
FROM Events
|
||||
INNER JOIN Monitors as M on Events.MonitorId = M.Id
|
||||
WHERE Events.Id = ?
|
||||
";
|
||||
my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
Debug($sql);
|
||||
|
||||
my @video_files;
|
||||
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 )
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
my $event = $sth->fetchrow_hashref();
|
||||
|
||||
my $Event = new ZoneMinder::Event( $$event{Id}, $event );
|
||||
my $video_file = $Event->GenerateVideo( $rate, $fps, $scale, $size, $overwrite, $format );
|
||||
if ( $video_file ) {
|
||||
push @video_files, $video_file;
|
||||
print( STDOUT $video_file."\n" );
|
||||
}
|
||||
my $Event = new ZoneMinder::Event( $$event{Id}, $event );
|
||||
my $video_file = $Event->GenerateVideo( $rate, $fps, $scale, $size, $overwrite, $format );
|
||||
if ( $video_file ) {
|
||||
push @video_files, $video_file;
|
||||
print( STDOUT $video_file."\n" );
|
||||
} else {
|
||||
Warning("No video file generated for event $event_id");
|
||||
}
|
||||
} # end foreach event_id
|
||||
|
||||
if ( $concat_name ) {
|
||||
($cwd) = $cwd =~ /(.*)/; # detaint
|
||||
chdir( $cwd );
|
||||
($concat_name ) = $concat_name =~ /([\-A-Za-z0-9_\.]*)/;
|
||||
my $concat_list_file = "/tmp/$concat_name.concat.lst";
|
||||
($cwd) = $cwd =~ /(.*)/; # detaint
|
||||
chdir( $cwd );
|
||||
($concat_name ) = $concat_name =~ /([\-A-Za-z0-9_\.]*)/;
|
||||
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: $!";
|
||||
foreach ( @video_files ) {
|
||||
print $fd "file '$_'\n";
|
||||
}
|
||||
close $fd;
|
||||
my $command = $Config{ZM_PATH_FFMPEG}
|
||||
. " -f concat -i $concat_list_file -c copy "
|
||||
." '$video_file' > ffmpeg.log 2>&1"
|
||||
;
|
||||
Debug( $command."\n" );
|
||||
my $output = qx($command);
|
||||
open( my $fd, '>', $concat_list_file ) or die "Can't open $concat_list_file: $!";
|
||||
foreach ( @video_files ) {
|
||||
print $fd "file '$_'\n";
|
||||
}
|
||||
close $fd;
|
||||
my $command = $Config{ZM_PATH_FFMPEG}
|
||||
. " -f concat -safe 0 -i $concat_list_file -c copy "
|
||||
.$Config{ZM_FFMPEG_OUTPUT_OPTIONS}
|
||||
." '$video_file' > $Config{ZM_PATH_LOGS}/ffmpeg_${concat_name}.log 2>&1"
|
||||
;
|
||||
Debug( $command."\n" );
|
||||
my $output = qx($command);
|
||||
|
||||
my $status = $? >> 8;
|
||||
my $status = $? >> 8;
|
||||
|
||||
unlink $concat_list_file;
|
||||
if ( $status )
|
||||
{
|
||||
Error( "Unable to generate video, check /ffmpeg.log for details");
|
||||
exit(-1);
|
||||
}
|
||||
unlink $concat_list_file;
|
||||
if ( $status ) {
|
||||
Error( "Unable to generate video, check $Config{ZM_PATH_LOGS}/ffmpeg_${concat_name}.log for details");
|
||||
exit(-1);
|
||||
}
|
||||
print( STDOUT $video_file."\n" );
|
||||
}
|
||||
exit( 0 );
|
||||
|
||||
__END__
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY)
|
||||
|
||||
# Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc)
|
||||
set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_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.
|
||||
add_library(zm STATIC ${ZM_BIN_SRC_FILES})
|
||||
|
|
|
@ -150,6 +150,12 @@ void process_configfile( char* configFile) {
|
|||
staticConfig.DB_USER = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_DB_PASS" ) == 0 )
|
||||
staticConfig.DB_PASS = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_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 )
|
||||
staticConfig.PATH_WEB = std::string(val_ptr);
|
||||
else if ( strcasecmp( name_ptr, "ZM_SERVER_HOST" ) == 0 )
|
||||
|
|
|
@ -67,6 +67,9 @@ struct StaticConfig
|
|||
std::string DB_NAME;
|
||||
std::string DB_USER;
|
||||
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 SERVER_NAME;
|
||||
unsigned int SERVER_ID;
|
||||
|
|
|
@ -37,6 +37,8 @@ void zmDbConnect()
|
|||
my_bool reconnect = 1;
|
||||
if ( mysql_options( &dbconn, MYSQL_OPT_RECONNECT, &reconnect ) )
|
||||
Fatal( "Can't set database auto reconnect option: %s", mysql_error( &dbconn ) );
|
||||
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( ":" );
|
||||
if ( colonIndex == std::string::npos )
|
||||
{
|
||||
|
|
783
src/zm_event.cpp
783
src/zm_event.cpp
|
@ -31,21 +31,10 @@
|
|||
#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"
|
||||
}
|
||||
|
||||
#if HAVE_SYS_SENDFILE_H
|
||||
#include <sys/sendfile.h>
|
||||
#endif
|
||||
|
||||
//#define USE_PREPARED_SQL 1
|
||||
|
||||
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 <limits.h>
|
||||
|
||||
/* Workaround for GNU/kFreeBSD */
|
||||
#if defined(__FreeBSD_kernel__)
|
||||
/* Workaround for GNU/kFreeBSD and FreeBSD */
|
||||
#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
|
||||
#ifndef ENODATA
|
||||
#define ENODATA ENOATTR
|
||||
#endif
|
||||
|
|
|
@ -195,7 +195,7 @@ void Logger::initialise( const std::string &id, const Options &options ) {
|
|||
level( tempLevel );
|
||||
|
||||
mFlush = false;
|
||||
if (envPtr = getenv( "LOG_FLUSH")) {
|
||||
if ( (envPtr = getenv("LOG_FLUSH")) ) {
|
||||
mFlush = atoi( envPtr );
|
||||
} else if ( config.log_debug ) {
|
||||
mFlush = true;
|
||||
|
@ -341,6 +341,8 @@ Logger::Level Logger::databaseLevel( Logger::Level databaseLevel ) {
|
|||
my_bool reconnect = 1;
|
||||
if ( mysql_options( &mDbConnection, MYSQL_OPT_RECONNECT, &reconnect ) )
|
||||
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( ":" );
|
||||
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 ) ) {
|
||||
|
|
|
@ -24,6 +24,10 @@ extern "C" {
|
|||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/time.h>
|
||||
#endif // __FreeBSD__
|
||||
|
||||
class ZMPacket {
|
||||
public:
|
||||
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
#ifdef HAVE_SENDFILE4_SUPPORT
|
||||
#include <sys/sendfile.h>
|
||||
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);
|
||||
if (err < 0)
|
||||
return -errno;
|
||||
err = sendfile(out_fd, in_fd, offset, size);
|
||||
if (err < 0)
|
||||
return -errno;
|
||||
|
||||
return err;
|
||||
return err;
|
||||
}
|
||||
#elif HAVE_SENDFILE7_SUPPORT
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/uio.h>
|
||||
int zm_sendfile(int out_fd, int in_fd, off_t *offset, off_t size) {
|
||||
int err;
|
||||
err = sendfile(in_fd, out_fd, *offset, size, NULL, &size, 0);
|
||||
if (err && errno != EAGAIN)
|
||||
return -errno;
|
||||
int err;
|
||||
err = sendfile(in_fd, out_fd, *offset, size, NULL, &size, 0);
|
||||
if (err && errno != EAGAIN)
|
||||
return -errno;
|
||||
|
||||
if (size) {
|
||||
*offset += size;
|
||||
return size;
|
||||
}
|
||||
if (size) {
|
||||
*offset += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
return -EAGAIN;
|
||||
return -EAGAIN;
|
||||
}
|
||||
#else
|
||||
#error "Your platform does not support sendfile. Sorry."
|
||||
|
|
|
@ -63,13 +63,13 @@ RETSIGTYPE zm_die_handler(int signal)
|
|||
ucontext_t *uc = (ucontext_t *) context;
|
||||
cr2 = info->si_addr;
|
||||
#if defined(__x86_64__)
|
||||
#ifdef __FreeBSD_kernel__
|
||||
#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
|
||||
ip = (void *)(uc->uc_mcontext.mc_rip);
|
||||
#else
|
||||
ip = (void *)(uc->uc_mcontext.gregs[REG_RIP]);
|
||||
#endif
|
||||
#else
|
||||
#ifdef __FreeBSD_kernel__
|
||||
#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
|
||||
ip = (void *)(uc->uc_mcontext.mc_eip);
|
||||
#else
|
||||
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) )
|
||||
|
||||
class StreamBase
|
||||
{
|
||||
class StreamBase {
|
||||
public:
|
||||
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;
|
||||
|
||||
public:
|
||||
StreamBase()
|
||||
{
|
||||
StreamBase() {
|
||||
monitor = 0;
|
||||
|
||||
type = DEFAULT_TYPE;
|
||||
|
@ -145,32 +143,27 @@ public:
|
|||
}
|
||||
virtual ~StreamBase();
|
||||
|
||||
void setStreamType( StreamType p_type )
|
||||
{
|
||||
void setStreamType( StreamType p_type ) {
|
||||
type = p_type;
|
||||
}
|
||||
void setStreamFormat( const char *p_format )
|
||||
{
|
||||
void setStreamFormat( const char *p_format ) {
|
||||
format = p_format;
|
||||
}
|
||||
void setStreamScale( int p_scale )
|
||||
{
|
||||
void setStreamScale( int p_scale ) {
|
||||
scale = p_scale;
|
||||
if ( ! scale )
|
||||
scale = DEFAULT_SCALE;
|
||||
}
|
||||
void setStreamReplayRate( int p_rate )
|
||||
{
|
||||
void setStreamReplayRate( int p_rate ) {
|
||||
replay_rate = p_rate;
|
||||
}
|
||||
void setStreamMaxFPS( double p_maxfps )
|
||||
{
|
||||
void setStreamMaxFPS( double p_maxfps ) {
|
||||
maxfps = p_maxfps;
|
||||
}
|
||||
void setStreamBitrate( int p_bitrate )
|
||||
{
|
||||
void setStreamBitrate( int p_bitrate ) {
|
||||
bitrate = p_bitrate;
|
||||
}
|
||||
void setStreamQueue( int p_connkey )
|
||||
{
|
||||
void setStreamQueue( int p_connkey ) {
|
||||
connkey = p_connkey;
|
||||
}
|
||||
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
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
//
|
||||
//
|
||||
#include "zm.h"
|
||||
#include "zm_video.h"
|
||||
#include "zm_image.h"
|
||||
#include "zm_utils.h"
|
||||
#include "zm_rgb.h"
|
||||
#include <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) :
|
||||
container(p_container), codec(p_codec), path(p_path), width(p_width), height(p_height), colours(p_colours), subpixelorder(p_subpixelorder), frame_count(0) {
|
||||
Debug(7,"Video object created");
|
||||
|
||||
/* Parameter checking */
|
||||
if(path.empty()) {
|
||||
Error("Invalid file path");
|
||||
}
|
||||
if(!width || !height) {
|
||||
Error("Invalid width or height");
|
||||
}
|
||||
VideoWriter::VideoWriter(
|
||||
const char* p_container,
|
||||
const char* p_codec,
|
||||
const char* p_path,
|
||||
const unsigned int p_width,
|
||||
const unsigned int p_height,
|
||||
const unsigned int p_colours,
|
||||
const unsigned int p_subpixelorder) :
|
||||
container(p_container),
|
||||
codec(p_codec),
|
||||
path(p_path),
|
||||
width(p_width),
|
||||
height(p_height),
|
||||
colours(p_colours),
|
||||
subpixelorder(p_subpixelorder),
|
||||
frame_count(0) {
|
||||
Debug(7, "Video object created");
|
||||
|
||||
/* Parameter checking */
|
||||
if ( path.empty() ) {
|
||||
Error("Invalid file path");
|
||||
}
|
||||
if ( !width || !height ) {
|
||||
Error("Invalid width or height");
|
||||
}
|
||||
}
|
||||
|
||||
VideoWriter::~VideoWriter() {
|
||||
Debug(7,"Video object destroyed");
|
||||
|
||||
Debug(7, "Video object destroyed");
|
||||
}
|
||||
|
||||
int VideoWriter::Reset(const char* new_path) {
|
||||
/* Common variables reset */
|
||||
/* Common variables reset */
|
||||
|
||||
/* If there is a new path, use it */
|
||||
if(new_path != NULL) {
|
||||
path = new_path;
|
||||
}
|
||||
/* If there is a new path, use it */
|
||||
if ( new_path != NULL ) {
|
||||
path = new_path;
|
||||
}
|
||||
|
||||
/* Reset frame counter */
|
||||
frame_count = 0;
|
||||
/* Reset frame counter */
|
||||
frame_count = 0;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#if ZM_HAVE_VIDEOWRITER_X264MP4
|
||||
X264MP4Writer::X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector<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();
|
||||
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) {
|
||||
/* Initialize ffmpeg if it hasn't been initialized yet */
|
||||
FFMPEGInit();
|
||||
|
||||
/* Initialize swscale */
|
||||
zm_pf = GetFFMPEGPixelFormat(colours,subpixelorder);
|
||||
if(zm_pf == 0) {
|
||||
Error("Unable to match ffmpeg pixelformat");
|
||||
}
|
||||
codec_pf = AV_PIX_FMT_YUV420P;
|
||||
/* Initialize swscale */
|
||||
zm_pf = GetFFMPEGPixelFormat(colours, subpixelorder);
|
||||
if ( zm_pf == 0 ) {
|
||||
Error("Unable to match ffmpeg pixelformat");
|
||||
}
|
||||
codec_pf = AV_PIX_FMT_YUV420P;
|
||||
|
||||
swscaleobj.SetDefaults(zm_pf, codec_pf, width, height);
|
||||
swscaleobj.SetDefaults(zm_pf, codec_pf, width, height);
|
||||
|
||||
/* Calculate the image sizes. We will need this for parameter checking */
|
||||
zm_imgsize = colours * width * height;
|
||||
/* Calculate the image sizes. We will need this for parameter checking */
|
||||
zm_imgsize = colours * width * height;
|
||||
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
|
||||
codec_imgsize = av_image_get_buffer_size( codec_pf, width, height, 1 );
|
||||
codec_imgsize = av_image_get_buffer_size(codec_pf, width, height, 1);
|
||||
#else
|
||||
codec_imgsize = avpicture_get_size( codec_pf, width, height);
|
||||
codec_imgsize = avpicture_get_size(codec_pf, width, height);
|
||||
#endif
|
||||
if(!codec_imgsize) {
|
||||
Error("Failed calculating codec pixel format image size");
|
||||
}
|
||||
if ( !codec_imgsize ) {
|
||||
Error("Failed calculating codec pixel format image size");
|
||||
}
|
||||
|
||||
/* If supplied with user parameters to the encoder, copy them */
|
||||
if(p_user_params != NULL) {
|
||||
user_params = *p_user_params;
|
||||
}
|
||||
|
||||
/* Setup x264 parameters */
|
||||
if(x264config() < 0) {
|
||||
Error("Failed setting x264 parameters");
|
||||
}
|
||||
|
||||
/* Allocate x264 input picture */
|
||||
x264_picture_alloc(&x264picin, X264_CSP_I420, x264params.i_width, x264params.i_height);
|
||||
|
||||
/* If supplied with user parameters to the encoder, copy them */
|
||||
if ( p_user_params != NULL ) {
|
||||
user_params = *p_user_params;
|
||||
}
|
||||
|
||||
/* Setup x264 parameters */
|
||||
if ( x264config() < 0 ) {
|
||||
Error("Failed setting x264 parameters");
|
||||
}
|
||||
|
||||
/* Allocate x264 input picture */
|
||||
x264_picture_alloc(
|
||||
&x264picin,
|
||||
X264_CSP_I420,
|
||||
x264params.i_width,
|
||||
x264params.i_height);
|
||||
}
|
||||
|
||||
X264MP4Writer::~X264MP4Writer() {
|
||||
/* Free x264 input picture */
|
||||
x264_picture_clean(&x264picin);
|
||||
|
||||
/* Free x264 input picture */
|
||||
x264_picture_clean(&x264picin);
|
||||
|
||||
if(bOpen)
|
||||
Close();
|
||||
|
||||
if ( bOpen )
|
||||
Close();
|
||||
}
|
||||
|
||||
int X264MP4Writer::Open() {
|
||||
/* Open the encoder */
|
||||
x264enc = x264_encoder_open(&x264params);
|
||||
if ( x264enc == NULL ) {
|
||||
Error("Failed opening x264 encoder");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Open the encoder */
|
||||
x264enc = x264_encoder_open(&x264params);
|
||||
if(x264enc == NULL) {
|
||||
Error("Failed opening x264 encoder");
|
||||
return -1;
|
||||
}
|
||||
// Debug(4,"x264 maximum delayed frames: %d",
|
||||
// x264_encoder_maximum_delayed_frames(x264enc));
|
||||
|
||||
// Debug(4,"x264 maximum delayed frames: %d",x264_encoder_maximum_delayed_frames(x264enc));
|
||||
|
||||
x264_nal_t* nals;
|
||||
int i_nals;
|
||||
if(!x264_encoder_headers(x264enc,&nals,&i_nals)) {
|
||||
Error("Failed getting encoder headers");
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* Search SPS NAL for AVC information */
|
||||
for(int i=0;i<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;
|
||||
}
|
||||
x264_nal_t* nals;
|
||||
int i_nals;
|
||||
if ( !x264_encoder_headers(x264enc, &nals, &i_nals) ) {
|
||||
Error("Failed getting encoder headers");
|
||||
return -2;
|
||||
}
|
||||
|
||||
bOpen = true;
|
||||
|
||||
return 0;
|
||||
/* Search SPS NAL for AVC information */
|
||||
for ( unsigned 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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 */
|
||||
for(int i = (x264_encoder_delayed_frames(x264enc) + 1); i > 0; i-- ) {
|
||||
x264encodeloop(true);
|
||||
}
|
||||
/* Close the encoder */
|
||||
x264_encoder_close(x264enc);
|
||||
|
||||
/* Close the encoder */
|
||||
x264_encoder_close(x264enc);
|
||||
/* Close MP4 handle */
|
||||
MP4Close(mp4h);
|
||||
|
||||
/* Close MP4 handle */
|
||||
MP4Close(mp4h);
|
||||
|
||||
/* Required for proper HTTP streaming */
|
||||
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 */
|
||||
unlink((path + ".incomplete").c_str());
|
||||
|
||||
bOpen = false;
|
||||
/* Delete the temporary file */
|
||||
unlink((path + ".incomplete").c_str());
|
||||
|
||||
Debug(7, "Video closed. Total frames: %d", frame_count);
|
||||
|
||||
return 0;
|
||||
bOpen = false;
|
||||
|
||||
Debug(7, "Video closed. Total frames: %d", frame_count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int X264MP4Writer::Reset(const char* new_path) {
|
||||
|
||||
/* Close the encoder and file */
|
||||
if(bOpen)
|
||||
Close();
|
||||
/* Close the encoder and file */
|
||||
if ( bOpen )
|
||||
Close();
|
||||
|
||||
/* Reset common variables */
|
||||
VideoWriter::Reset(new_path);
|
||||
/* Reset common variables */
|
||||
VideoWriter::Reset(new_path);
|
||||
|
||||
/* Reset local variables */
|
||||
bFirstFrame = true;
|
||||
bGotH264AVCInfo = false;
|
||||
prevnals.clear();
|
||||
prevpayload.clear();
|
||||
|
||||
/* Reset x264 parameters */
|
||||
x264config();
|
||||
/* Reset local variables */
|
||||
bFirstFrame = true;
|
||||
bGotH264AVCInfo = false;
|
||||
prevnals.clear();
|
||||
prevpayload.clear();
|
||||
|
||||
/* Open the encoder */
|
||||
Open();
|
||||
|
||||
return 0;
|
||||
/* Reset x264 parameters */
|
||||
x264config();
|
||||
|
||||
/* Open the encoder */
|
||||
Open();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int X264MP4Writer::Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) {
|
||||
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 == NULL) {
|
||||
Error("NULL buffer");
|
||||
return -1;
|
||||
}
|
||||
if ( data_size != zm_imgsize ) {
|
||||
Error("The data buffer size (%d) != expected (%d)", data_size, zm_imgsize);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if(data_size != zm_imgsize) {
|
||||
Error("The data buffer size does not match the expected size. Expected: %d Current: %d", zm_imgsize, data_size);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if(!bOpen) {
|
||||
Warning("The encoder was not initialized, initializing now");
|
||||
Open();
|
||||
}
|
||||
|
||||
/* Convert the image into the x264 input picture */
|
||||
if(swscaleobj.ConvertDefaults(data, data_size, x264picin.img.plane[0], codec_imgsize) < 0) {
|
||||
Error("Image conversion failed");
|
||||
return -3;
|
||||
}
|
||||
if ( !bOpen ) {
|
||||
Warning("The encoder was not initialized, initializing now");
|
||||
Open();
|
||||
}
|
||||
|
||||
/* Set PTS */
|
||||
x264picin.i_pts = frame_time;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* Do the encoding */
|
||||
x264encodeloop();
|
||||
/* Set PTS */
|
||||
x264picin.i_pts = frame_time;
|
||||
|
||||
/* Increment frame counter */
|
||||
frame_count++;
|
||||
/* Do the encoding */
|
||||
x264encodeloop();
|
||||
|
||||
return 0;
|
||||
/* Increment frame counter */
|
||||
frame_count++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int X264MP4Writer::Encode(const Image* img, const unsigned int frame_time) {
|
||||
|
||||
if(img->Width() != width) {
|
||||
Error("Source image width differs. Source: %d Output: %d",img->Width(), width);
|
||||
return -12;
|
||||
}
|
||||
if ( img->Width() != width ) {
|
||||
Error("Source image width differs. Source: %d Output: %d", img->Width(), width);
|
||||
return -12;
|
||||
}
|
||||
|
||||
if(img->Height() != height) {
|
||||
Error("Source image height differs. Source: %d Output: %d",img->Height(), height);
|
||||
return -13;
|
||||
}
|
||||
|
||||
return Encode(img->Buffer(),img->Size(),frame_time);
|
||||
if ( img->Height() != height ) {
|
||||
Error("Source image height differs. Source: %d Output: %d", img->Height(), height);
|
||||
return -13;
|
||||
}
|
||||
|
||||
return Encode(img->Buffer(), img->Size(), frame_time);
|
||||
}
|
||||
|
||||
int X264MP4Writer::x264config() {
|
||||
/* Sets up the encoder configuration */
|
||||
/* Sets up the encoder configuration */
|
||||
|
||||
int x264ret;
|
||||
int x264ret;
|
||||
|
||||
/* Defaults */
|
||||
const char* preset = "veryfast";
|
||||
const char* tune = "stillimage";
|
||||
const char* profile = "main";
|
||||
/* Defaults */
|
||||
const char* preset = "veryfast";
|
||||
const char* tune = "stillimage";
|
||||
const char* profile = "main";
|
||||
|
||||
/* Search the user parameters for preset, tune and profile */
|
||||
for(unsigned int i=0; i < user_params.size(); i++) {
|
||||
if(strcmp(user_params[i].pname, "preset") == 0) {
|
||||
/* Got preset */
|
||||
preset = user_params[i].pvalue;
|
||||
} else if(strcmp(user_params[i].pname, "tune") == 0) {
|
||||
/* Got tune */
|
||||
tune = user_params[i].pvalue;
|
||||
} else if(strcmp(user_params[i].pname, "profile") == 0) {
|
||||
/* Got profile */
|
||||
profile = user_params[i].pvalue;
|
||||
}
|
||||
}
|
||||
/* Search the user parameters for preset, tune and profile */
|
||||
for ( unsigned int i = 0; i < user_params.size(); i++ ) {
|
||||
if ( strcmp(user_params[i].pname, "preset") == 0 ) {
|
||||
/* Got preset */
|
||||
preset = user_params[i].pvalue;
|
||||
} else if ( strcmp(user_params[i].pname, "tune") == 0 ) {
|
||||
/* Got tune */
|
||||
tune = user_params[i].pvalue;
|
||||
} else if ( strcmp(user_params[i].pname, "profile") == 0 ) {
|
||||
/* Got profile */
|
||||
profile = user_params[i].pvalue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the defaults and preset and tune */
|
||||
x264ret = x264_param_default_preset(&x264params, preset, tune);
|
||||
if(x264ret != 0) {
|
||||
Error("Failed setting x264 preset %s and tune %s : %d",preset,tune,x264ret);
|
||||
}
|
||||
|
||||
/* Set the profile */
|
||||
x264ret = x264_param_apply_profile(&x264params, profile);
|
||||
if(x264ret != 0) {
|
||||
Error("Failed setting x264 profile %s : %d",profile,x264ret);
|
||||
}
|
||||
|
||||
/* Input format */
|
||||
x264params.i_width = width;
|
||||
x264params.i_height = height;
|
||||
x264params.i_csp = X264_CSP_I420;
|
||||
|
||||
/* Quality control */
|
||||
x264params.rc.i_rc_method = X264_RC_CRF;
|
||||
x264params.rc.f_rf_constant = 23.0;
|
||||
/* Set the defaults and preset and tune */
|
||||
x264ret = x264_param_default_preset(&x264params, preset, tune);
|
||||
if ( x264ret != 0 ) {
|
||||
Error("Failed setting x264 preset %s and tune %s : %d", preset, tune, x264ret);
|
||||
}
|
||||
|
||||
/* Enable b-frames */
|
||||
x264params.i_bframe = 16;
|
||||
x264params.i_bframe_adaptive = 1;
|
||||
|
||||
/* Timebase */
|
||||
x264params.i_timebase_num = 1;
|
||||
x264params.i_timebase_den = 1000;
|
||||
/* Set the profile */
|
||||
x264ret = x264_param_apply_profile(&x264params, profile);
|
||||
if ( x264ret != 0 ) {
|
||||
Error("Failed setting x264 profile %s : %d", profile, x264ret);
|
||||
}
|
||||
|
||||
/* Enable variable frame rate */
|
||||
x264params.b_vfr_input = 1;
|
||||
/* Input format */
|
||||
x264params.i_width = width;
|
||||
x264params.i_height = height;
|
||||
x264params.i_csp = X264_CSP_I420;
|
||||
|
||||
/* Disable annex-b (start codes) */
|
||||
x264params.b_annexb = 0;
|
||||
|
||||
/* TODO: Setup error handler */
|
||||
//x264params.i_log_level = X264_LOG_DEBUG;
|
||||
/* Quality control */
|
||||
x264params.rc.i_rc_method = X264_RC_CRF;
|
||||
x264params.rc.f_rf_constant = 23.0;
|
||||
|
||||
/* Process user parameters (excluding preset, tune and profile) */
|
||||
for(unsigned int i=0; i < user_params.size(); i++) {
|
||||
/* Skip preset, tune and profile */
|
||||
if( (strcmp(user_params[i].pname, "preset") == 0) || (strcmp(user_params[i].pname, "tune") == 0) || (strcmp(user_params[i].pname, "profile") == 0) ) {
|
||||
continue;
|
||||
}
|
||||
/* Enable b-frames */
|
||||
x264params.i_bframe = 16;
|
||||
x264params.i_bframe_adaptive = 1;
|
||||
|
||||
/* Pass the name and value to x264 */
|
||||
x264ret = x264_param_parse(&x264params, user_params[i].pname, user_params[i].pvalue);
|
||||
/* Timebase */
|
||||
x264params.i_timebase_num = 1;
|
||||
x264params.i_timebase_den = 1000;
|
||||
|
||||
/* 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;
|
||||
/* Enable variable frame rate */
|
||||
x264params.b_vfr_input = 1;
|
||||
|
||||
/* Disable annex-b (start codes) */
|
||||
x264params.b_annexb = 0;
|
||||
|
||||
/* TODO: Setup error handler */
|
||||
// x264params.i_log_level = X264_LOG_DEBUG;
|
||||
|
||||
/* Process user parameters (excluding preset, tune and profile) */
|
||||
for ( unsigned int i = 0; i < user_params.size(); i++ ) {
|
||||
/* Skip preset, tune and profile */
|
||||
if (
|
||||
(strcmp(user_params[i].pname, "preset") == 0) ||
|
||||
(strcmp(user_params[i].pname, "tune") == 0) ||
|
||||
(strcmp(user_params[i].pname, "profile") == 0) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Pass the name and value to x264 */
|
||||
x264ret = x264_param_parse(&x264params, user_params[i].pname, user_params[i].pvalue);
|
||||
|
||||
/* Error checking */
|
||||
if ( x264ret != 0 ) {
|
||||
if ( x264ret == X264_PARAM_BAD_NAME ) {
|
||||
Error("Failed processing x264 user parameter %s=%s : Bad name",
|
||||
user_params[i].pname, user_params[i].pvalue);
|
||||
} else if ( x264ret == X264_PARAM_BAD_VALUE ) {
|
||||
Error("Failed processing x264 user parameter %s=%s : Bad value",
|
||||
user_params[i].pname, user_params[i].pvalue);
|
||||
} else {
|
||||
Error("Failed processing x264 user parameter %s=%s : Unknown error (%d)",
|
||||
user_params[i].pname, user_params[i].pvalue, x264ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void X264MP4Writer::x264encodeloop(bool bFlush) {
|
||||
x264_nal_t* nals;
|
||||
int i_nals;
|
||||
int frame_size;
|
||||
|
||||
x264_nal_t* nals;
|
||||
int i_nals;
|
||||
int frame_size;
|
||||
if ( bFlush ) {
|
||||
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, NULL, &x264picout);
|
||||
} else {
|
||||
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, &x264picin, &x264picout);
|
||||
}
|
||||
|
||||
if(bFlush) {
|
||||
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, NULL, &x264picout);
|
||||
} else {
|
||||
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, &x264picin, &x264picout);
|
||||
}
|
||||
if ( frame_size > 0 || bFlush ) {
|
||||
Debug(8, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",
|
||||
frame_count, x264picout.i_pts, x264picout.i_dts, frame_size);
|
||||
|
||||
if (frame_size > 0 || bFlush) {
|
||||
Debug(8, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",frame_count, x264picout.i_pts, x264picout.i_dts, frame_size);
|
||||
/* Handle the previous frame */
|
||||
if ( !bFirstFrame ) {
|
||||
buffer.clear();
|
||||
|
||||
/* Handle the previous frame */
|
||||
if(!bFirstFrame) {
|
||||
/* Process the NALs for the previous frame */
|
||||
for ( unsigned int i = 0; i < prevnals.size(); i++ ) {
|
||||
Debug(9, "Processing NAL: Type %d Size %d",
|
||||
prevnals[i].i_type,
|
||||
prevnals[i].i_payload);
|
||||
|
||||
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 */
|
||||
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);
|
||||
/* Calculate frame duration and offset */
|
||||
int duration = x264picout.i_dts - prevDTS;
|
||||
int offset = prevPTS - prevDTS;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
/* Write the sample */
|
||||
if ( !buffer.empty() ) {
|
||||
if ( !MP4WriteSample(
|
||||
mp4h,
|
||||
mp4vtid,
|
||||
buffer.extract(buffer.size()),
|
||||
buffer.size(),
|
||||
duration,
|
||||
offset,
|
||||
prevKeyframe) ) {
|
||||
Error("Failed writing sample");
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate frame duration and offset */
|
||||
int duration = x264picout.i_dts - prevDTS;
|
||||
int offset = prevPTS - prevDTS;
|
||||
/* Cleanup */
|
||||
prevnals.clear();
|
||||
prevpayload.clear();
|
||||
}
|
||||
|
||||
/* Write the sample */
|
||||
if(!buffer.empty()) {
|
||||
if(!MP4WriteSample(mp4h, mp4vtid, buffer.extract(buffer.size()), buffer.size(), duration, offset, prevKeyframe)) {
|
||||
Error("Failed writing sample");
|
||||
}
|
||||
}
|
||||
/* Got a frame. Copy this new frame into the previous frame */
|
||||
if ( frame_size > 0 ) {
|
||||
/* Copy the NALs and the payloads */
|
||||
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 */
|
||||
prevnals.clear();
|
||||
prevpayload.clear();
|
||||
/* Update the payload pointers */
|
||||
/* This is done in a separate loop because the previous loop might reallocate memory when appending,
|
||||
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 */
|
||||
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);
|
||||
}
|
||||
bFirstFrame = false;
|
||||
}
|
||||
|
||||
/* Update the payload pointers */
|
||||
/* This is done in a separate loop because the previous loop might reallocate memory when appending,
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
} 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) {
|
||||
if(vec == NULL) {
|
||||
Error("NULL Encoder parameters vector pointer");
|
||||
return -1;
|
||||
}
|
||||
int ParseEncoderParameters(
|
||||
const char* str,
|
||||
std::vector<EncoderParameter_t>* vec
|
||||
) {
|
||||
if ( vec == NULL ) {
|
||||
Error("NULL Encoder parameters vector pointer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(str == NULL) {
|
||||
Error("NULL Encoder parameters string");
|
||||
return -2;
|
||||
}
|
||||
if ( str == NULL ) {
|
||||
Error("NULL Encoder parameters string");
|
||||
return -2;
|
||||
}
|
||||
|
||||
vec->clear();
|
||||
vec->clear();
|
||||
|
||||
if(str[0] == 0) {
|
||||
/* Empty */
|
||||
return 0;
|
||||
}
|
||||
if ( str[0] == 0 ) {
|
||||
/* Empty */
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
std::stringstream ss(str);
|
||||
size_t valueoffset;
|
||||
size_t valuelen;
|
||||
unsigned int lineno = 0;
|
||||
EncoderParameter_t param;
|
||||
std::string line;
|
||||
std::stringstream ss(str);
|
||||
size_t valueoffset;
|
||||
size_t valuelen;
|
||||
unsigned int lineno = 0;
|
||||
EncoderParameter_t param;
|
||||
|
||||
while(std::getline(ss, line) ) {
|
||||
lineno++;
|
||||
while ( std::getline(ss, line) ) {
|
||||
lineno++;
|
||||
|
||||
/* Remove CR if exists */
|
||||
if(line.length() >= 1 && line[line.length()-1] == '\r') {
|
||||
line.erase(line.length()-1);
|
||||
}
|
||||
/* Remove CR if exists */
|
||||
if ( line.length() >= 1 && line[line.length()-1] == '\r' ) {
|
||||
line.erase(line.length() - 1);
|
||||
}
|
||||
|
||||
/* Skip comments and empty lines */
|
||||
if(line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
/* Skip comments and empty lines */
|
||||
if ( line.empty() || line[0] == '#' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
valueoffset = line.find('=');
|
||||
if(valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0) {
|
||||
Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno);
|
||||
continue;
|
||||
}
|
||||
valueoffset = line.find('=');
|
||||
if ( valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0 ) {
|
||||
Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( valueoffset > (sizeof(param.pname) - 1 ) ) {
|
||||
Warning("Failed parsing encoder parameters line %d: Name too long", lineno);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(valueoffset > (sizeof(param.pname)-1) ) {
|
||||
Warning("Failed parsing encoder parameters line %d: Name too long", lineno);
|
||||
continue;
|
||||
}
|
||||
valuelen = line.length() - (valueoffset+1);
|
||||
|
||||
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) ) {
|
||||
Warning("Failed parsing encoder parameters line %d: Value too long", lineno);
|
||||
continue;
|
||||
}
|
||||
/* Copy and NULL terminate */
|
||||
line.copy(param.pname, valueoffset, 0);
|
||||
line.copy(param.pvalue, valuelen, valueoffset+1);
|
||||
param.pname[valueoffset] = 0;
|
||||
param.pvalue[valuelen] = 0;
|
||||
|
||||
/* Copy and NULL terminate */
|
||||
line.copy(param.pname, valueoffset, 0);
|
||||
line.copy(param.pvalue, valuelen, valueoffset+1);
|
||||
param.pname[valueoffset] = 0;
|
||||
param.pvalue[valuelen] = 0;
|
||||
/* Push to the vector */
|
||||
vec->push_back(param);
|
||||
|
||||
/* Push to the vector */
|
||||
vec->push_back(param);
|
||||
Debug(7, "Parsed encoder parameter: %s = %s", param.pname, param.pvalue);
|
||||
}
|
||||
|
||||
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_input_context = NULL;
|
||||
audio_output_stream = NULL;
|
||||
input_frame = NULL;
|
||||
output_frame = NULL;
|
||||
#ifdef HAVE_LIBAVRESAMPLE
|
||||
resample_context = NULL;
|
||||
#endif
|
||||
|
@ -391,6 +393,15 @@ Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts
|
|||
|
||||
/* free the stream */
|
||||
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() {
|
||||
|
@ -495,13 +506,13 @@ bool VideoStore::setup_resampler() {
|
|||
}
|
||||
|
||||
/** 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");
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 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");
|
||||
av_frame_free( &input_frame );
|
||||
return false;
|
||||
|
@ -618,7 +629,7 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) {
|
|||
int duration;
|
||||
|
||||
//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 ) {
|
||||
// This is the first packet.
|
||||
|
@ -687,18 +698,12 @@ int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) {
|
|||
opkt.data = ipkt->data;
|
||||
opkt.size = ipkt->size;
|
||||
|
||||
// Some camera have audio on stream 0 and video on stream 1. So when we remove the audio, video stream has to go on 0
|
||||
if ( ipkt->stream_index > 0 and ! audio_output_stream ) {
|
||||
Debug(1,"Setting stream index to 0 instead of %d", ipkt->stream_index );
|
||||
opkt.stream_index = 0;
|
||||
} else {
|
||||
opkt.stream_index = ipkt->stream_index;
|
||||
}
|
||||
opkt.stream_index = video_output_stream->index;
|
||||
|
||||
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)) {
|
||||
Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ );
|
||||
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_pts = opkt.pts;
|
||||
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
|
||||
Warning("%s:%d: Writing frame [av_interleaved_write_frame()] failed: %s(%d) ", __FILE__, __LINE__, av_make_error_string(ret).c_str(), (ret));
|
||||
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
|
||||
opkt.pos = -1;
|
||||
opkt.stream_index = ipkt->stream_index;
|
||||
Debug(2, "Stream index is %d", opkt.stream_index );
|
||||
opkt.stream_index = audio_output_stream->index;//ipkt->stream_index;
|
||||
|
||||
AVPacket safepkt;
|
||||
memcpy(&safepkt, &opkt, sizeof(AVPacket));
|
||||
|
|
151
src/zms.cpp
151
src/zms.cpp
|
@ -26,32 +26,26 @@
|
|||
#include "zm_signal.h"
|
||||
#include "zm_monitor.h"
|
||||
|
||||
bool ValidateAccess( User *user, int mon_id )
|
||||
{
|
||||
bool ValidateAccess( User *user, int mon_id ) {
|
||||
bool allowed = true;
|
||||
|
||||
if ( mon_id > 0 )
|
||||
{
|
||||
if ( mon_id > 0 ) {
|
||||
if ( user->getStream() < User::PERM_VIEW )
|
||||
allowed = false;
|
||||
if ( !user->canAccess( mon_id ) )
|
||||
allowed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if ( user->getEvents() < User::PERM_VIEW )
|
||||
allowed = false;
|
||||
}
|
||||
if ( !allowed )
|
||||
{
|
||||
if ( !allowed ) {
|
||||
Error( "Error, insufficient privileges for requested action" );
|
||||
exit( -1 );
|
||||
}
|
||||
return( allowed );
|
||||
}
|
||||
|
||||
int main( int argc, const char *argv[] )
|
||||
{
|
||||
int main( int argc, const char *argv[] ) {
|
||||
self = argv[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
|
||||
basename = argv[0];
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -97,8 +90,7 @@ int main( int argc, const char *argv[] )
|
|||
zmSetDefaultDieHandler();
|
||||
|
||||
const char *query = getenv( "QUERY_STRING" );
|
||||
if ( query )
|
||||
{
|
||||
if ( query ) {
|
||||
Debug( 1, "Query: %s", query );
|
||||
|
||||
char temp_query[1024];
|
||||
|
@ -106,84 +98,68 @@ int main( int argc, const char *argv[] )
|
|||
char *q_ptr = temp_query;
|
||||
char *parms[16]; // Shouldn't be more than this
|
||||
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++;
|
||||
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 *value = strtok( NULL, "=" );
|
||||
if ( !value )
|
||||
value = (char *)"";
|
||||
if ( !strcmp( name, "source" ) )
|
||||
{
|
||||
if ( !strcmp( name, "source" ) ) {
|
||||
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, "raw" )?ZMS_RAW:mode;
|
||||
mode = !strcmp( value, "zip" )?ZMS_ZIP:mode;
|
||||
mode = !strcmp( value, "single" )?ZMS_SINGLE:mode;
|
||||
}
|
||||
else if ( !strcmp( name, "format" ) )
|
||||
} else if ( !strcmp( name, "format" ) ) {
|
||||
strncpy( format, value, sizeof(format) );
|
||||
else if ( !strcmp( name, "monitor" ) )
|
||||
} else if ( !strcmp( name, "monitor" ) ) {
|
||||
monitor_id = atoi( value );
|
||||
else if ( !strcmp( name, "time" ) )
|
||||
} else if ( !strcmp( name, "time" ) ) {
|
||||
event_time = atoi( value );
|
||||
else if ( !strcmp( name, "event" ) )
|
||||
} else if ( !strcmp( name, "event" ) ) {
|
||||
event_id = strtoull( value, (char **)NULL, 10 );
|
||||
else if ( !strcmp( name, "frame" ) )
|
||||
} else if ( !strcmp( name, "frame" ) ) {
|
||||
frame_id = strtoull( value, (char **)NULL, 10 );
|
||||
else if ( !strcmp( name, "scale" ) )
|
||||
} else if ( !strcmp( name, "scale" ) ) {
|
||||
scale = atoi( value );
|
||||
else if ( !strcmp( name, "rate" ) )
|
||||
} else if ( !strcmp( name, "rate" ) ) {
|
||||
rate = atoi( value );
|
||||
else if ( !strcmp( name, "maxfps" ) )
|
||||
} else if ( !strcmp( name, "maxfps" ) ) {
|
||||
maxfps = atof( value );
|
||||
else if ( !strcmp( name, "bitrate" ) )
|
||||
} else if ( !strcmp( name, "bitrate" ) ) {
|
||||
bitrate = atoi( value );
|
||||
else if ( !strcmp( name, "ttl" ) )
|
||||
} else if ( !strcmp( name, "ttl" ) ) {
|
||||
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, "all" )?EventStream::MODE_ALL:replay;
|
||||
}
|
||||
else if ( !strcmp( name, "connkey" ) )
|
||||
} else if ( !strcmp( name, "connkey" ) ) {
|
||||
connkey = atoi(value);
|
||||
else if ( !strcmp( name, "buffer" ) )
|
||||
} else if ( !strcmp( name, "buffer" ) ) {
|
||||
playback_buffer = atoi(value);
|
||||
else if ( config.opt_use_auth )
|
||||
{
|
||||
if ( strcmp( config.auth_relay, "none" ) == 0 )
|
||||
{
|
||||
if ( !strcmp( name, "user" ) )
|
||||
{
|
||||
} else if ( config.opt_use_auth ) {
|
||||
if ( strcmp( config.auth_relay, "none" ) == 0 ) {
|
||||
if ( !strcmp( name, "user" ) ) {
|
||||
username = value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
|
||||
{
|
||||
if ( !strcmp( name, "auth" ) )
|
||||
{
|
||||
if ( !strcmp( name, "auth" ) ) {
|
||||
strncpy( auth, value, sizeof(auth) );
|
||||
}
|
||||
}
|
||||
//else if ( strcmp( config.auth_relay, "plain" ) == 0 )
|
||||
{
|
||||
if ( !strcmp( name, "user" ) )
|
||||
{
|
||||
if ( !strcmp( name, "user" ) ) {
|
||||
username = UriDecode( value );
|
||||
}
|
||||
if ( !strcmp( name, "pass" ) )
|
||||
{
|
||||
if ( !strcmp( name, "pass" ) ) {
|
||||
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;
|
||||
|
||||
if ( strcmp( config.auth_relay, "none" ) == 0 )
|
||||
{
|
||||
if ( username.length() )
|
||||
{
|
||||
if ( strcmp( config.auth_relay, "none" ) == 0 ) {
|
||||
if ( username.length() ) {
|
||||
user = zmLoadUser( username.c_str() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
//if ( strcmp( config.auth_relay, "hashed" ) == 0 )
|
||||
{
|
||||
if ( *auth )
|
||||
{
|
||||
if ( *auth ) {
|
||||
user = zmLoadAuthUser( auth, config.auth_hash_ips );
|
||||
}
|
||||
}
|
||||
//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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !user )
|
||||
{
|
||||
if ( !user ) {
|
||||
Error( "Unable to authenticate user" );
|
||||
logTerm();
|
||||
zmDbClose();
|
||||
|
@ -231,8 +199,7 @@ int main( int argc, const char *argv[] )
|
|||
}
|
||||
|
||||
setbuf( stdout, 0 );
|
||||
if ( nph )
|
||||
{
|
||||
if ( nph ) {
|
||||
fprintf( stdout, "HTTP/1.0 200 OK\r\n" );
|
||||
}
|
||||
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");
|
||||
//}
|
||||
|
||||
if ( source == ZMS_MONITOR )
|
||||
{
|
||||
if ( source == ZMS_MONITOR ) {
|
||||
MonitorStream stream;
|
||||
stream.setStreamScale( scale );
|
||||
stream.setStreamReplayRate( rate );
|
||||
|
@ -269,24 +235,15 @@ int main( int argc, const char *argv[] )
|
|||
return( -1 );
|
||||
}
|
||||
|
||||
if ( mode == ZMS_JPEG )
|
||||
{
|
||||
if ( mode == ZMS_JPEG ) {
|
||||
stream.setStreamType( MonitorStream::STREAM_JPEG );
|
||||
}
|
||||
else if ( mode == ZMS_RAW )
|
||||
{
|
||||
} else if ( mode == ZMS_RAW ) {
|
||||
stream.setStreamType( MonitorStream::STREAM_RAW );
|
||||
}
|
||||
else if ( mode == ZMS_ZIP )
|
||||
{
|
||||
} else if ( mode == ZMS_ZIP ) {
|
||||
stream.setStreamType( MonitorStream::STREAM_ZIP );
|
||||
}
|
||||
else if ( mode == ZMS_SINGLE )
|
||||
{
|
||||
} else if ( mode == ZMS_SINGLE ) {
|
||||
stream.setStreamType( MonitorStream::STREAM_SINGLE );
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
#if HAVE_LIBAVCODEC
|
||||
stream.setStreamFormat( format );
|
||||
stream.setStreamBitrate( bitrate );
|
||||
|
@ -300,29 +257,21 @@ int main( int argc, const char *argv[] )
|
|||
#endif // HAVE_LIBAVCODEC
|
||||
}
|
||||
stream.runStream();
|
||||
}
|
||||
else if ( source == ZMS_EVENT )
|
||||
{
|
||||
} else if ( source == ZMS_EVENT ) {
|
||||
EventStream stream;
|
||||
stream.setStreamScale( scale );
|
||||
stream.setStreamReplayRate( rate );
|
||||
stream.setStreamMaxFPS( maxfps );
|
||||
stream.setStreamMode( replay );
|
||||
stream.setStreamQueue( connkey );
|
||||
if ( monitor_id && event_time )
|
||||
{
|
||||
if ( monitor_id && event_time ) {
|
||||
stream.setStreamStart( monitor_id, event_time );
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
stream.setStreamStart( event_id, frame_id );
|
||||
}
|
||||
if ( mode == ZMS_JPEG )
|
||||
{
|
||||
if ( mode == ZMS_JPEG ) {
|
||||
stream.setStreamType( EventStream::STREAM_JPEG );
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
#if HAVE_LIBAVCODEC
|
||||
stream.setStreamFormat( format );
|
||||
stream.setStreamBitrate( bitrate );
|
||||
|
|
|
@ -259,23 +259,19 @@ fi
|
|||
|
||||
if [ $TYPE == "binary" ]; then
|
||||
if [ "$INTERACTIVE" != "no" ]; then
|
||||
read -p "Not doing dput since it's a binary release. Do you want to install it? (Y/N)"
|
||||
read -p "Not doing dput since it's a binary release. Do you want to install it? (y/N)"
|
||||
if [[ $REPLY == [yY] ]]; then
|
||||
sudo dpkg -i $DIRECTORY*.deb
|
||||
else
|
||||
echo $REPLY;
|
||||
fi;
|
||||
if [ "$DISTRO" == "jessie" ]; then
|
||||
read -p "Do you want to upload this binary to zmrepo? (y/N)"
|
||||
if [[ $REPLY == [yY] ]]; then
|
||||
if [ "$RELEASE" != "" ]; then
|
||||
scp "zoneminder_${VERSION}-${DISTRO}*" "zmrepo@zmrepo.connortechnology.com:debian/stable/mini-dinstall/incoming/"
|
||||
read -p "Do you want to upload this binary to zmrepo? (y/N)"
|
||||
if [[ $REPLY == [yY] ]]; 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/"
|
||||
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
|
||||
if [ "$BRANCH" == "" ]; then
|
||||
scp "zoneminder_${VERSION}-${DISTRO}*" "zmrepo@zmrepo.connortechnology.com:debian/master/mini-dinstall/incoming/"
|
||||
else
|
||||
scp "$DIRECTORY-${DISTRO}*" "zmrepo@zmrepo.connortechnology.com:debian/${BRANCH}/mini-dinstall/incoming/"
|
||||
fi;
|
||||
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/"
|
||||
fi;
|
||||
fi;
|
||||
fi;
|
||||
|
@ -295,11 +291,10 @@ else
|
|||
|
||||
dput="Y";
|
||||
if [ "$INTERACTIVE" != "no" ]; then
|
||||
echo "Ready to dput $SC to $PPA ? Y/N...";
|
||||
read dput
|
||||
fi
|
||||
if [ "$dput" == [Yy] ]; then
|
||||
dput $PPA $SC
|
||||
read -p "Ready to dput $SC to $PPA ? Y/N...";
|
||||
if [[ "$REPLY" == [yY] ]]; then
|
||||
dput $PPA $SC
|
||||
fi;
|
||||
fi;
|
||||
fi;
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ if [ "${TRAVIS_EVENT_TYPE}" == "cron" ]; then
|
|||
|
||||
# Don't keep packages older than 5 days
|
||||
find ./zmrepo/$targetfolder/ -maxdepth 1 -type f -mtime +5 -delete
|
||||
rsync -vzh --ignore-errors build/* zmrepo/$targetfolder/
|
||||
rsync -vzlh --ignore-errors build/* zmrepo/$targetfolder/
|
||||
fusermount -zu zmrepo
|
||||
else
|
||||
echo
|
||||
|
|
|
@ -95,6 +95,7 @@ switch ( $_REQUEST['task'] )
|
|||
foreach ( dbFetchAll( $sql, NULL, $values ) as $log ) {
|
||||
$log['DateTime'] = preg_replace( '/^\d+/', strftime( "%Y-%m-%d %H:%M:%S", intval($log['TimeKey']) ), $log['TimeKey'] );
|
||||
$log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : '';
|
||||
$log['Message'] = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $log['Message'] );
|
||||
$logs[] = $log;
|
||||
}
|
||||
$options = array();
|
||||
|
|
|
@ -101,49 +101,66 @@ CakeLog::config('debug', array(
|
|||
'engine' => 'File',
|
||||
'types' => array('notice', 'info', 'debug'),
|
||||
'file' => 'cake_debug',
|
||||
'path' => '@ZM_LOGDIR@/'
|
||||
));
|
||||
CakeLog::config('error', array(
|
||||
'engine' => 'File',
|
||||
'types' => array('warning', 'error', 'critical', 'alert', 'emergency'),
|
||||
'file' => 'cake_error',
|
||||
'path' => '@ZM_LOGDIR@/'
|
||||
));
|
||||
CakeLog::config('custom_path', array(
|
||||
'engine' => 'File',
|
||||
'path' => '@ZM_LOGDIR@'
|
||||
'path' => '@ZM_LOGDIR@/'
|
||||
));
|
||||
|
||||
Configure::write('ZM_CONFIG', '@ZM_CONFIG@');
|
||||
Configure::write('ZM_CONFIG_SUBDIR', '@ZM_CONFIG_SUBDIR@');
|
||||
Configure::write('ZM_VERSION', '@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() {
|
||||
$configFile = Configure::read('ZM_CONFIG');
|
||||
$localConfigFile = basename($configFile);
|
||||
if ( file_exists( $localConfigFile ) && filesize( $localConfigFile ) > 0 )
|
||||
{
|
||||
if ( php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']) )
|
||||
print( "Warning, overriding installed $localConfigFile file with local copy\n" );
|
||||
else
|
||||
error_log( "Warning, overriding installed $localConfigFile file with local copy" );
|
||||
$configFile = $localConfigFile;
|
||||
}
|
||||
|
||||
$cfg = fopen( $configFile, "r") or die("Could not open config file.");
|
||||
while ( !feof($cfg) )
|
||||
{
|
||||
$str = fgets( $cfg, 256 );
|
||||
if ( preg_match( '/^\s*$/', $str ))
|
||||
continue;
|
||||
elseif ( preg_match( '/^\s*#/', $str ))
|
||||
continue;
|
||||
elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*(.*?)\s*$/', $str, $matches )) {
|
||||
Configure::write( $matches[1], $matches[2] );
|
||||
define( $matches[1], $matches[2] );
|
||||
}
|
||||
}
|
||||
fclose( $cfg );
|
||||
# Search for user created config files. If one or more are found then
|
||||
# update our config value array with those values
|
||||
$configSubFolder = Configure::read('ZM_CONFIG_SUBDIR');
|
||||
if ( is_dir($configSubFolder) ) {
|
||||
if ( is_readable($configSubFolder) ) {
|
||||
foreach ( glob("$configSubFolder/*.conf") as $filename ) {
|
||||
$configvals = array_replace($configvals, process_configfile($filename) );
|
||||
}
|
||||
} else {
|
||||
error_log( "WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on $configSubFolder." );
|
||||
}
|
||||
}
|
||||
|
||||
# Now that our array our finalized, define each key => value
|
||||
# pair in the array as a constant
|
||||
foreach( $configvals as $key => $value) {
|
||||
define( $key, $value );
|
||||
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,
|
||||
'password' => ZM_DB_PASS,
|
||||
'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' => '',
|
||||
'encoding' => 'utf8',
|
||||
);
|
||||
|
|
|
@ -176,8 +176,8 @@ if ( !empty($action) ) {
|
|||
} else {
|
||||
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 action == filter
|
||||
|
|
|
@ -42,7 +42,12 @@ function dbConnect() {
|
|||
}
|
||||
|
||||
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_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch(PDOException $ex ) {
|
||||
|
|
|
@ -2138,8 +2138,7 @@ function getSkinFile( $file ) {
|
|||
return( $skinFile );
|
||||
}
|
||||
|
||||
function getSkinIncludes( $file, $includeBase=false, $asOverride=false )
|
||||
{
|
||||
function getSkinIncludes( $file, $includeBase=false, $asOverride=false ) {
|
||||
global $skinBase;
|
||||
$skinFile = false;
|
||||
foreach ( $skinBase as $skin ) {
|
||||
|
|
152
web/index.php
152
web/index.php
|
@ -21,29 +21,26 @@
|
|||
error_reporting( E_ALL );
|
||||
|
||||
$debug = false;
|
||||
if ( $debug )
|
||||
{
|
||||
// Use these for debugging, though not both at once!
|
||||
phpinfo( INFO_VARIABLES );
|
||||
//error_reporting( E_ALL );
|
||||
if ( $debug ) {
|
||||
// Use these for debugging, though not both at once!
|
||||
phpinfo( INFO_VARIABLES );
|
||||
//error_reporting( E_ALL );
|
||||
}
|
||||
|
||||
// Use new style autoglobals where possible
|
||||
if ( version_compare( phpversion(), "4.1.0", "<") )
|
||||
{
|
||||
$_SESSION = &$HTTP_SESSION_VARS;
|
||||
$_SERVER = &$HTTP_SERVER_VARS;
|
||||
if ( version_compare( phpversion(), '4.1.0', '<') ) {
|
||||
$_SESSION = &$HTTP_SESSION_VARS;
|
||||
$_SERVER = &$HTTP_SERVER_VARS;
|
||||
}
|
||||
|
||||
// Useful debugging lines for mobile devices
|
||||
if ( false )
|
||||
{
|
||||
ob_start();
|
||||
phpinfo( INFO_VARIABLES );
|
||||
$fp = fopen( "/tmp/env.html", "w" );
|
||||
fwrite( $fp, ob_get_contents() );
|
||||
fclose( $fp );
|
||||
ob_end_clean();
|
||||
if ( false ) {
|
||||
ob_start();
|
||||
phpinfo( INFO_VARIABLES );
|
||||
$fp = fopen( '/tmp/env.html', 'w' );
|
||||
fwrite( $fp, ob_get_contents() );
|
||||
fclose( $fp );
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
require_once( 'includes/config.php' );
|
||||
|
@ -53,26 +50,23 @@ require_once( 'includes/Storage.php' );
|
|||
require_once( 'includes/Event.php' );
|
||||
require_once( 'includes/Monitor.php' );
|
||||
|
||||
if ( isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == 'on' )
|
||||
{
|
||||
$protocol = 'https';
|
||||
if ( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ) {
|
||||
$protocol = 'https';
|
||||
} else {
|
||||
$protocol = 'http';
|
||||
}
|
||||
else
|
||||
{
|
||||
$protocol = 'http';
|
||||
}
|
||||
define( "ZM_BASE_PROTOCOL", $protocol );
|
||||
define( 'ZM_BASE_PROTOCOL', $protocol );
|
||||
|
||||
// Absolute URL's are unnecessary and break compatibility with reverse proxies
|
||||
// define( "ZM_BASE_URL", $protocol.'://'.$_SERVER['HTTP_HOST'] );
|
||||
|
||||
// Use relative URL's instead
|
||||
define( "ZM_BASE_URL", "" );
|
||||
define( 'ZM_BASE_URL', '' );
|
||||
|
||||
// Check time zone is set
|
||||
if (!ini_get('date.timezone') || !date_default_timezone_set(ini_get('date.timezone'))) {
|
||||
date_default_timezone_set('UTC');
|
||||
Fatal( "ZoneMinder is not installed properly: php's date.timezone is not set to a valid timezone" );
|
||||
date_default_timezone_set('UTC');
|
||||
Fatal( "ZoneMinder is not installed properly: php's date.timezone is not set to a valid timezone" );
|
||||
}
|
||||
|
||||
if ( isset($_GET['skin']) )
|
||||
|
@ -82,7 +76,7 @@ elseif ( isset($_COOKIE['zmSkin']) )
|
|||
elseif ( defined('ZM_SKIN_DEFAULT') )
|
||||
$skin = ZM_SKIN_DEFAULT;
|
||||
else
|
||||
$skin = "classic";
|
||||
$skin = 'classic';
|
||||
|
||||
$skins = array_map( 'basename', glob('skins/*',GLOB_ONLYDIR) );
|
||||
if ( ! in_array( $skin, $skins ) ) {
|
||||
|
@ -97,7 +91,7 @@ elseif ( isset($_COOKIE['zmCSS']) )
|
|||
elseif (defined('ZM_CSS_DEFAULT'))
|
||||
$css = ZM_CSS_DEFAULT;
|
||||
else
|
||||
$css = "classic";
|
||||
$css = 'classic';
|
||||
|
||||
$css_skins = array_map( 'basename', glob('skins/'.$skin.'/css/*',GLOB_ONLYDIR) );
|
||||
if ( ! in_array( $css, $css_skins ) ) {
|
||||
|
@ -105,9 +99,9 @@ if ( ! in_array( $css, $css_skins ) ) {
|
|||
$css = $css_skins[0];
|
||||
}
|
||||
|
||||
define( "ZM_BASE_PATH", dirname( $_SERVER['REQUEST_URI'] ) );
|
||||
define( "ZM_SKIN_NAME", $skin );
|
||||
define( "ZM_SKIN_PATH", "skins/$skin" );
|
||||
define( 'ZM_BASE_PATH', dirname( $_SERVER['REQUEST_URI'] ) );
|
||||
define( 'ZM_SKIN_NAME', $skin );
|
||||
define( 'ZM_SKIN_PATH', "skins/$skin" );
|
||||
|
||||
$skinBase = array(); // To allow for inheritance of skins
|
||||
if ( !file_exists( ZM_SKIN_PATH ) )
|
||||
|
@ -117,26 +111,25 @@ $skinBase[] = $skin;
|
|||
$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)');
|
||||
session_set_cookie_params(
|
||||
$currentCookieParams["lifetime"],
|
||||
$currentCookieParams["path"],
|
||||
$currentCookieParams["domain"],
|
||||
$currentCookieParams["secure"],
|
||||
$currentCookieParams['lifetime'],
|
||||
$currentCookieParams['path'],
|
||||
$currentCookieParams['domain'],
|
||||
$currentCookieParams['secure'],
|
||||
true
|
||||
);
|
||||
|
||||
ini_set( "session.name", "ZMSESSID" );
|
||||
ini_set( 'session.name', 'ZMSESSID' );
|
||||
|
||||
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;
|
||||
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 ) {
|
||||
$_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 )
|
||||
|
@ -149,14 +142,12 @@ else
|
|||
|
||||
require_once( 'includes/lang.php' );
|
||||
require_once( 'includes/functions.php' );
|
||||
require_once( 'includes/csrf/csrf-magic.php' );
|
||||
|
||||
# Add Cross domain access headers
|
||||
CORSHeaders();
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
|
@ -177,58 +168,51 @@ isset($view) || $view = NULL;
|
|||
isset($request) || $request = NULL;
|
||||
isset($action) || $action = NULL;
|
||||
|
||||
if ( ZM_ENABLE_CSRF_MAGIC && $action != 'login' ) {
|
||||
Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\"");
|
||||
csrf_check();
|
||||
if ( ZM_ENABLE_CSRF_MAGIC && $action != 'login' && $view != 'view_video' ) {
|
||||
require_once( 'includes/csrf/csrf-magic.php' );
|
||||
Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\"");
|
||||
csrf_check();
|
||||
}
|
||||
|
||||
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 ( 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.
|
||||
# Any file/page that uses the session must re-open it.
|
||||
session_write_close();
|
||||
|
||||
if ( isset( $_REQUEST['request'] ) )
|
||||
{
|
||||
foreach ( getSkinIncludes( 'ajax/'.$request.'.php', true, true ) as $includeFile )
|
||||
{
|
||||
if ( !file_exists( $includeFile ) )
|
||||
Fatal( "Request '$request' does not exist" );
|
||||
if ( isset( $_REQUEST['request'] ) ) {
|
||||
foreach ( getSkinIncludes( 'ajax/'.$request.'.php', true, true ) as $includeFile ) {
|
||||
if ( !file_exists( $includeFile ) )
|
||||
Fatal( "Request '$request' does not exist" );
|
||||
require_once $includeFile;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
text-align: center;
|
||||
padding: 1px;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .arrowBtn {
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#monitors .imageFeed img {
|
||||
border: 2px solid #ffffff;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#monitors .imageFeed img.idle {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
.ptzControls input.ptzTextBtn {
|
||||
margin-top: 2px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel {
|
||||
|
@ -79,8 +78,8 @@
|
|||
border: 1px solid #006699;
|
||||
text-align: center;
|
||||
padding: 1px;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .arrowBtn {
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#monitors .imageFeed img {
|
||||
border: 2px solid #999999;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#monitors .imageFeed img.idle {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
.ptzControls input.ptzTextBtn {
|
||||
margin-top: 2px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel {
|
||||
|
@ -79,8 +78,8 @@
|
|||
border: 1px solid #006699;
|
||||
text-align: center;
|
||||
padding: 1px;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .arrowBtn {
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#monitors .imageFeed img {
|
||||
border: 2px solid #ffffff;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#monitors .imageFeed img.idle {
|
||||
|
|
|
@ -25,264 +25,227 @@
|
|||
var popupOptions = "resizable,scrollbars,status=no";
|
||||
|
||||
function checkSize() {
|
||||
if (window.outerHeight) {
|
||||
var w = window.outerWidth;
|
||||
var prevW = w;
|
||||
var h = window.outerHeight;
|
||||
var prevH = h;
|
||||
if (h > screen.availHeight)
|
||||
h = screen.availHeight;
|
||||
if (w > screen.availWidth)
|
||||
w = screen.availWidth;
|
||||
if (w != prevW || h != prevH)
|
||||
window.resizeTo(w, h);
|
||||
}
|
||||
if (window.outerHeight) {
|
||||
var w = window.outerWidth;
|
||||
var prevW = w;
|
||||
var h = window.outerHeight;
|
||||
var prevH = h;
|
||||
if (h > screen.availHeight)
|
||||
h = screen.availHeight;
|
||||
if (w > screen.availWidth)
|
||||
w = screen.availWidth;
|
||||
if (w != prevW || h != prevH)
|
||||
window.resizeTo(w, h);
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
function newWindow( url, name, width, height )
|
||||
{
|
||||
var windowId = window.open( url, name, popupOptions+",width="+width+",height="+height );
|
||||
function newWindow( url, name, width, height ) {
|
||||
var windowId = window.open( url, name, popupOptions+",width="+width+",height="+height );
|
||||
}
|
||||
|
||||
function getPopupSize( tag, width, height )
|
||||
{
|
||||
var popupSize = Object.clone( popupSizes[tag] );
|
||||
if ( !popupSize )
|
||||
{
|
||||
Error( "Can't find window size for tag '"+tag+"'" );
|
||||
return( { 'width': 0, 'height': 0 } );
|
||||
}
|
||||
if ( popupSize.width && popupSize.height )
|
||||
{
|
||||
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 );
|
||||
function getPopupSize( tag, width, height ) {
|
||||
var popupSize = Object.clone( popupSizes[tag] );
|
||||
if ( !popupSize ) {
|
||||
Error( "Can't find window size for tag '"+tag+"'" );
|
||||
return( { 'width': 0, 'height': 0 } );
|
||||
}
|
||||
if ( popupSize.width && popupSize.height ) {
|
||||
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 ("+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()
|
||||
{
|
||||
var zmWin = window.open( 'http://www.zoneminder.com', 'ZoneMinder' );
|
||||
if ( ! zmWin ) {
|
||||
// if popup blocking is enabled, the popup won't be defined.
|
||||
console.log("Please disable popup blocking.");
|
||||
} else {
|
||||
zmWin.focus();
|
||||
}
|
||||
function zmWindow() {
|
||||
var zmWin = window.open( 'http://www.zoneminder.com', 'ZoneMinder' );
|
||||
if ( ! zmWin ) {
|
||||
// if popup blocking is enabled, the popup won't be defined.
|
||||
console.log("Please disable popup blocking.");
|
||||
} else {
|
||||
zmWin.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function createPopup( url, name, tag, width, height )
|
||||
{
|
||||
var popupSize = getPopupSize( tag, width, height );
|
||||
var popupDimensions = "";
|
||||
if ( popupSize.width > 0 )
|
||||
popupDimensions += ",width="+popupSize.width;
|
||||
if ( popupSize.height > 0 )
|
||||
popupDimensions += ",height="+popupSize.height;
|
||||
var popup = window.open( url, name, popupOptions+popupDimensions );
|
||||
if ( ! popup ) {
|
||||
// if popup blocking is enabled, the popup won't be defined.
|
||||
console.log("Please disable popup blocking.");
|
||||
} else {
|
||||
popup.focus();
|
||||
}
|
||||
function createPopup( url, name, tag, width, height ) {
|
||||
var popupSize = getPopupSize( tag, width, height );
|
||||
var popupDimensions = "";
|
||||
if ( popupSize.width > 0 )
|
||||
popupDimensions += ",width="+popupSize.width;
|
||||
if ( popupSize.height > 0 )
|
||||
popupDimensions += ",height="+popupSize.height;
|
||||
var popup = window.open( url, name, popupOptions+popupDimensions );
|
||||
if ( ! popup ) {
|
||||
// if popup blocking is enabled, the popup won't be defined.
|
||||
console.log("Please disable popup blocking.");
|
||||
} else {
|
||||
popup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function createEventPopup( eventId, eventFilter, width, height )
|
||||
{
|
||||
var url = '?view=event&eid='+eventId;
|
||||
if ( eventFilter )
|
||||
url += eventFilter;
|
||||
var name = 'zmEvent';
|
||||
var popupSize = getPopupSize( 'event', width, height );
|
||||
var popup = window.open( url, name, popupOptions+",width="+popupSize.width+",height="+popupSize.height );
|
||||
if ( ! popup ) {
|
||||
// if popup blocking is enabled, the popup won't be defined.
|
||||
console.log("Please disable popup blocking.");
|
||||
} else {
|
||||
popup.focus();
|
||||
}
|
||||
function createEventPopup( eventId, eventFilter, width, height ) {
|
||||
var url = '?view=event&eid='+eventId;
|
||||
if ( eventFilter )
|
||||
url += eventFilter;
|
||||
var name = 'zmEvent';
|
||||
var popupSize = getPopupSize( 'event', width, height );
|
||||
var popup = window.open( url, name, popupOptions+",width="+popupSize.width+",height="+popupSize.height );
|
||||
if ( ! popup ) {
|
||||
// if popup blocking is enabled, the popup won't be defined.
|
||||
console.log("Please disable popup blocking.");
|
||||
} else {
|
||||
popup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function createFramesPopup( eventId, width, height )
|
||||
{
|
||||
var url = '?view=frames&eid='+eventId;
|
||||
var name = 'zmFrames';
|
||||
var popupSize = getPopupSize( 'frames', width, height );
|
||||
var popup = window.open( url, name, popupOptions+",width="+popupSize.width+",height="+popupSize.height );
|
||||
if ( ! popup ) {
|
||||
// if popup blocking is enabled, the popup won't be defined.
|
||||
console.log("Please disable popup blocking.");
|
||||
} else {
|
||||
popup.focus();
|
||||
}
|
||||
function createFramesPopup( eventId, width, height ) {
|
||||
var url = '?view=frames&eid='+eventId;
|
||||
var name = 'zmFrames';
|
||||
var popupSize = getPopupSize( 'frames', width, height );
|
||||
var popup = window.open( url, name, popupOptions+",width="+popupSize.width+",height="+popupSize.height );
|
||||
if ( ! popup ) {
|
||||
// if popup blocking is enabled, the popup won't be defined.
|
||||
console.log("Please disable popup blocking.");
|
||||
} else {
|
||||
popup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function createFramePopup( eventId, frameId, width, height )
|
||||
{
|
||||
var url = '?view=frame&eid='+eventId+'&fid='+frameId;
|
||||
var name = 'zmFrame';
|
||||
var popupSize = getPopupSize( 'frame', width, height );
|
||||
var popup = window.open( url, name, popupOptions+",width="+popupSize.width+",height="+popupSize.height );
|
||||
if ( ! popup ) {
|
||||
// if popup blocking is enabled, the popup won't be defined.
|
||||
console.log("Please disable popup blocking.");
|
||||
} else {
|
||||
popup.focus();
|
||||
}
|
||||
function createFramePopup( eventId, frameId, width, height ) {
|
||||
var url = '?view=frame&eid='+eventId+'&fid='+frameId;
|
||||
var name = 'zmFrame';
|
||||
var popupSize = getPopupSize( 'frame', width, height );
|
||||
var popup = window.open( url, name, popupOptions+",width="+popupSize.width+",height="+popupSize.height );
|
||||
if ( ! popup ) {
|
||||
// if popup blocking is enabled, the popup won't be defined.
|
||||
console.log("Please disable popup blocking.");
|
||||
} else {
|
||||
popup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function windowToFront()
|
||||
{
|
||||
top.window.focus();
|
||||
function windowToFront() {
|
||||
top.window.focus();
|
||||
}
|
||||
|
||||
function closeWindow()
|
||||
{
|
||||
top.window.close();
|
||||
function closeWindow() {
|
||||
top.window.close();
|
||||
}
|
||||
|
||||
function refreshWindow()
|
||||
{
|
||||
window.location.reload( true );
|
||||
function refreshWindow() {
|
||||
window.location.reload( true );
|
||||
}
|
||||
|
||||
function refreshParentWindow()
|
||||
{
|
||||
if ( window.opener )
|
||||
window.opener.location.reload( true );
|
||||
function refreshParentWindow() {
|
||||
if ( window.opener )
|
||||
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.
|
||||
function checkStreamForErrors( funcName, streamObj )
|
||||
{
|
||||
if ( !streamObj )
|
||||
{
|
||||
Error( funcName+": stream object was null" );
|
||||
return true;
|
||||
}
|
||||
if ( streamObj.result == "Error" )
|
||||
{
|
||||
Error( funcName+" stream error: "+streamObj.message );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
function checkStreamForErrors( funcName, streamObj ) {
|
||||
if ( !streamObj ) {
|
||||
Error( funcName+": stream object was null" );
|
||||
return true;
|
||||
}
|
||||
if ( streamObj.result == "Error" ) {
|
||||
Error( funcName+" stream error: "+streamObj.message );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function secsToTime( seconds )
|
||||
{
|
||||
var timeString = "--";
|
||||
if ( seconds < 60 )
|
||||
timeString = seconds.toString();
|
||||
else if ( seconds < 60*60 )
|
||||
{
|
||||
var timeMins = parseInt(seconds/60);
|
||||
var timeSecs = seconds%60;
|
||||
if ( timeSecs < 10 )
|
||||
timeSecs = '0'+timeSecs.toString().substr( 0, 4 );
|
||||
else
|
||||
timeSecs = timeSecs.toString().substr( 0, 5 );
|
||||
timeString = timeMins+":"+timeSecs;
|
||||
}
|
||||
function secsToTime( seconds ) {
|
||||
var timeString = "--";
|
||||
if ( seconds < 60 ) {
|
||||
timeString = seconds.toString();
|
||||
} else if ( seconds < 60*60 ) {
|
||||
var timeMins = parseInt(seconds/60);
|
||||
var timeSecs = seconds%60;
|
||||
if ( timeSecs < 10 )
|
||||
timeSecs = '0'+timeSecs.toString().substr( 0, 4 );
|
||||
else
|
||||
{
|
||||
var timeHours = parseInt(seconds/3600);
|
||||
var timeMins = (seconds%3600)/60;
|
||||
var timeSecs = seconds%60;
|
||||
if ( timeMins < 10 )
|
||||
timeMins = '0'+timeMins.toString().substr( 0, 4 );
|
||||
else
|
||||
timeMins = timeMins.toString().substr( 0, 5 );
|
||||
if ( timeSecs < 10 )
|
||||
timeSecs = '0'+timeSecs.toString().substr( 0, 4 );
|
||||
else
|
||||
timeSecs = timeSecs.toString().substr( 0, 5 );
|
||||
timeString = timeHours+":"+timeMins+":"+timeSecs;
|
||||
}
|
||||
return( timeString );
|
||||
timeSecs = timeSecs.toString().substr( 0, 5 );
|
||||
timeString = timeMins+":"+timeSecs;
|
||||
} else {
|
||||
var timeHours = parseInt(seconds/3600);
|
||||
var timeMins = (seconds%3600)/60;
|
||||
var timeSecs = seconds%60;
|
||||
if ( timeMins < 10 )
|
||||
timeMins = '0'+timeMins.toString().substr( 0, 4 );
|
||||
else
|
||||
timeMins = timeMins.toString().substr( 0, 5 );
|
||||
if ( timeSecs < 10 )
|
||||
timeSecs = '0'+timeSecs.toString().substr( 0, 4 );
|
||||
else
|
||||
timeSecs = timeSecs.toString().substr( 0, 5 );
|
||||
timeString = timeHours+":"+timeMins+":"+timeSecs;
|
||||
}
|
||||
return( timeString );
|
||||
}
|
||||
|
||||
function submitTab( tab )
|
||||
{
|
||||
var form = $('contentForm');
|
||||
form.action.value = "";
|
||||
form.tab.value = tab;
|
||||
form.submit();
|
||||
function submitTab( tab ) {
|
||||
var form = $('contentForm');
|
||||
form.action.value = "";
|
||||
form.tab.value = tab;
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function configureDeleteButton( element )
|
||||
{
|
||||
var form = element.form;
|
||||
var checked = element.checked;
|
||||
if ( !checked )
|
||||
{
|
||||
for ( var i = 0; i < form.elements.length; i++ )
|
||||
{
|
||||
if ( form.elements[i].name == element.name )
|
||||
{
|
||||
if ( form.elements[i].checked )
|
||||
{
|
||||
checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
function configureDeleteButton( element ) {
|
||||
var form = element.form;
|
||||
var checked = element.checked;
|
||||
if ( !checked ) {
|
||||
for ( var i = 0; i < form.elements.length; i++ ) {
|
||||
if ( form.elements[i].name == element.name ) {
|
||||
if ( form.elements[i].checked ) {
|
||||
checked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
form.deleteBtn.disabled = !checked;
|
||||
}
|
||||
form.deleteBtn.disabled = !checked;
|
||||
}
|
||||
|
||||
function confirmDelete( message )
|
||||
{
|
||||
return( confirm( message?message:'Are you sure you wish to delete?' ) );
|
||||
function confirmDelete( message ) {
|
||||
return( confirm( message?message:'Are you sure you wish to delete?' ) );
|
||||
}
|
||||
|
||||
if ( refreshParent )
|
||||
{
|
||||
refreshParentWindow();
|
||||
if ( refreshParent ) {
|
||||
refreshParentWindow();
|
||||
}
|
||||
|
||||
if ( focusWindow )
|
||||
{
|
||||
windowToFront();
|
||||
if ( focusWindow ) {
|
||||
windowToFront();
|
||||
}
|
||||
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);
|
||||
video.appendChild(track);
|
||||
}
|
||||
|
||||
|
|
|
@ -155,7 +155,11 @@ xhtmlHeaders( __FILE__, translate('Console') );
|
|||
<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="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>
|
||||
<?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>
|
||||
|
@ -248,27 +252,26 @@ foreach( $displayMonitors as $monitor ) {
|
|||
$Server = new Server( $monitor['ServerId'] );
|
||||
echo $Server->Name();
|
||||
?></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
|
||||
}
|
||||
$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++ ) {
|
||||
?>
|
||||
<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">
|
||||
<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.
|
||||
</video>
|
||||
</div>
|
||||
|
|
|
@ -214,7 +214,7 @@ foreach ( $events as $event ) {
|
|||
<td class="colThumbnail">
|
||||
<?php
|
||||
$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.'\';"/>';
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
//
|
||||
|
||||
if ( !canView( 'Events' ) )
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 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;
|
||||
$index = 0;
|
||||
$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 )
|
||||
{
|
||||
if ( !visibleMonitor( $row['Id'] ) )
|
||||
|
@ -47,14 +57,17 @@ foreach( dbFetchAll( $sql ) as $row )
|
|||
continue;
|
||||
}
|
||||
|
||||
$monitor_scale = 100;
|
||||
if ( isset( $_REQUEST['scale'] ) )
|
||||
$scale = validInt($_REQUEST['scale']);
|
||||
$monitor_scale = validInt($_REQUEST['scale']);
|
||||
else if ( isset( $_COOKIE['zmMontageScale'] ) )
|
||||
$scale = $_COOKIE['zmMontageScale'];
|
||||
$monitor_scale = $_COOKIE['zmMontageScale'];
|
||||
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 );
|
||||
if ( $maxWidth < $scaleWidth )
|
||||
$maxWidth = $scaleWidth;
|
||||
|
@ -117,14 +130,11 @@ foreach ( $monitors as $monitor )
|
|||
<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() ?> );">
|
||||
<?php
|
||||
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 ) );
|
||||
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 ) );
|
||||
outputVideoStream( "liveStream".$monitor->Id(), $streamSrc, reScale( $monitor->Width(), $scale ), reScale( $monitor->Height(), $scale ), ZM_MPEG_LIVE_FORMAT );
|
||||
}
|
||||
else
|
||||
{
|
||||
$streamSrc = $monitor->getStreamSrc( array( "mode=jpeg", "scale=".$scale, "maxfps=".ZM_WEB_VIDEO_MAXFPS ) );
|
||||
} else {
|
||||
$streamSrc = $monitor->getStreamSrc( array( 'mode'=>'jpeg', 'scale'=>$scale, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS ) );
|
||||
if ( canStreamNative() )
|
||||
{
|
||||
outputImageStream( "liveStream".$monitor->Id(), $streamSrc, reScale( $monitor->Width(), $scale ), reScale( $monitor->Height(), $scale ), validHtmlStr($monitor->Name()) );
|
||||
|
|
|
@ -95,20 +95,20 @@
|
|||
//
|
||||
|
||||
if ( !canView( 'Events' ) ) {
|
||||
$view = 'error';
|
||||
return;
|
||||
$view = 'error';
|
||||
return;
|
||||
}
|
||||
|
||||
require_once( 'includes/Monitor.php' );
|
||||
|
||||
# FIXME THere is no way to select group at this time.
|
||||
if ( !empty($_REQUEST['group']) ) {
|
||||
$group = $_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']."' ) ";
|
||||
$group = $_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']."' ) ";
|
||||
} else {
|
||||
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None'";
|
||||
$group = '';
|
||||
$monitorsSql = "SELECT * FROM Monitors WHERE Function != 'None'";
|
||||
$group = '';
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
$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)
|
||||
ELSE UNIX_TIMESTAMP(E.EndTime)
|
||||
END AS CalcEndTimeSecs, E.Length,
|
||||
|
@ -222,7 +222,7 @@ $monitors = array();
|
|||
$monitorsSql .= ' ORDER BY Sequence ASC';
|
||||
$index=0;
|
||||
foreach( dbFetchAll( $monitorsSql ) as $row ) {
|
||||
$monitors[$index] = $row;
|
||||
$monitors[$index] = new Monitor( $row );
|
||||
$index = $index + 1;
|
||||
}
|
||||
|
||||
|
@ -270,921 +270,15 @@ input[type=range]::-ms-tooltip {
|
|||
<span id="scrubright"></span>
|
||||
<span id="scruboutput"></span>
|
||||
</div>
|
||||
<div id="monitors">
|
||||
<?php
|
||||
// Monitor images - these had to be loaded after the monitors used were determined (after loading events)
|
||||
|
||||
echo '<div id="monitors">';
|
||||
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";
|
||||
?>
|
||||
|
||||
var currentScale=<?php echo $defaultScale?>;
|
||||
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>
|
||||
<p id="fps">evaluating fps</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -34,8 +34,7 @@ require_once( $skinJsPhpFile );
|
|||
<script type="text/javascript" src="<?php echo $skinJsFile ?>"></script>
|
||||
<script type="text/javascript">
|
||||
<?php
|
||||
if ( !$debug )
|
||||
{
|
||||
if ( !$debug ) {
|
||||
?>
|
||||
closeWindow();
|
||||
<?php
|
||||
|
|
|
@ -18,10 +18,9 @@
|
|||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
//
|
||||
|
||||
if ( !canEdit( 'Monitors' ) )
|
||||
{
|
||||
$view = "error";
|
||||
return;
|
||||
if ( !canEdit( 'Monitors' ) ) {
|
||||
$view = 'error';
|
||||
return;
|
||||
}
|
||||
|
||||
$cameras = array();
|
||||
|
@ -41,90 +40,81 @@ function execONVIF( $cmd ) {
|
|||
$html_output<br/><br/>
|
||||
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;
|
||||
}
|
||||
|
||||
function probeCameras( $localIp )
|
||||
{
|
||||
$cameras = array();
|
||||
$count = 0;
|
||||
if ( $lines = @execONVIF( "probe" ) )
|
||||
{
|
||||
foreach ( $lines as $line )
|
||||
{
|
||||
$line = rtrim( $line );
|
||||
if ( preg_match( '|^(.+),(.+),\s\((.*)\)$|', $line, $matches ) )
|
||||
{
|
||||
$device_ep = $matches[1];
|
||||
$soapversion = $matches[2];
|
||||
$camera = array(
|
||||
'model' => "Unknown ONVIF Camera",
|
||||
'monitor' => array(
|
||||
'Function' => "Monitor",
|
||||
'Type' => 'Ffmpeg',
|
||||
'Host' => $device_ep,
|
||||
'SOAP' => $soapversion,
|
||||
),
|
||||
);
|
||||
foreach ( preg_split('|,\s*|', $matches[3]) as $attr_val ) {
|
||||
if( preg_match( '|(.+)=\'(.*)\'|', $attr_val, $tokens ) )
|
||||
{
|
||||
if($tokens[1] == "hardware") {
|
||||
$camera['model'] = $tokens[2];
|
||||
}
|
||||
elseif($tokens[1] == "name") {
|
||||
$camera['monitor']['Name'] = $tokens[2];
|
||||
}
|
||||
elseif($tokens[1] == "location") {
|
||||
// $camera['location'] = $tokens[2];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
$cameras[$count ++] = $camera;
|
||||
function probeCameras( $localIp ) {
|
||||
$cameras = array();
|
||||
if ( $lines = @execONVIF( 'probe' ) ) {
|
||||
foreach ( $lines as $line ) {
|
||||
$line = rtrim( $line );
|
||||
if ( preg_match( '|^(.+),(.+),\s\((.*)\)$|', $line, $matches ) ) {
|
||||
$device_ep = $matches[1];
|
||||
$soapversion = $matches[2];
|
||||
$camera = array(
|
||||
'model' => 'Unknown ONVIF Camera',
|
||||
'monitor' => array(
|
||||
'Function' => 'Monitor',
|
||||
'Type' => 'Ffmpeg',
|
||||
'Host' => $device_ep,
|
||||
'SOAP' => $soapversion,
|
||||
),
|
||||
);
|
||||
foreach ( preg_split('|,\s*|', $matches[3]) as $attr_val ) {
|
||||
if ( preg_match( '|(.+)=\'(.*)\'|', $attr_val, $tokens ) ) {
|
||||
if ( $tokens[1] == 'hardware' ) {
|
||||
$camera['model'] = $tokens[2];
|
||||
} elseif ( $tokens[1] == 'name' ) {
|
||||
$camera['monitor']['Name'] = $tokens[2];
|
||||
} elseif ( $tokens[1] == 'location' ) {
|
||||
// $camera['location'] = $tokens[2];
|
||||
} else {
|
||||
Logger::Debug('Unknown token ' . $tokens[1] );
|
||||
}
|
||||
}
|
||||
}
|
||||
return( $cameras );
|
||||
}
|
||||
} // end foreach token
|
||||
$cameras[] = $camera;
|
||||
}
|
||||
} // end foreach line
|
||||
}
|
||||
return( $cameras );
|
||||
}
|
||||
|
||||
function probeProfiles( $device_ep, $soapversion, $username, $password )
|
||||
{
|
||||
$profiles = array();
|
||||
$count = 0;
|
||||
if ( $lines = @execONVIF( "profiles $device_ep $soapversion $username $password" ) )
|
||||
{
|
||||
foreach ( $lines as $line )
|
||||
{
|
||||
$line = rtrim( $line );
|
||||
if ( preg_match( '|^(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+)\s*$|', $line, $matches ) )
|
||||
{
|
||||
$stream_uri = $matches[7];
|
||||
// add user@pass to URI
|
||||
if( preg_match( '|^(\S+://)(.+)$|', $stream_uri, $tokens ) )
|
||||
{
|
||||
$stream_uri = $tokens[1].$username.':'.$password.'@'.$tokens[2];
|
||||
}
|
||||
|
||||
$profile = array( # 'monitor' part of camera
|
||||
'Type' => 'Ffmpeg',
|
||||
'Width' => $matches[4],
|
||||
'Height' => $matches[5],
|
||||
'MaxFPS' => $matches[6],
|
||||
'Path' => $stream_uri,
|
||||
// local-only:
|
||||
'Profile' => $matches[1],
|
||||
'Name' => $matches[2],
|
||||
'Encoding' => $matches[3],
|
||||
|
||||
);
|
||||
$profiles[$count ++] = $profile;
|
||||
}
|
||||
}
|
||||
function probeProfiles( $device_ep, $soapversion, $username, $password ) {
|
||||
$profiles = array();
|
||||
if ( $lines = @execONVIF( "profiles $device_ep $soapversion $username $password" ) ) {
|
||||
foreach ( $lines as $line ) {
|
||||
$line = rtrim( $line );
|
||||
if ( preg_match( '|^(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+),\s*(.+)\s*$|', $line, $matches ) ) {
|
||||
$stream_uri = $matches[7];
|
||||
// add user@pass to URI
|
||||
if ( preg_match( '|^(\S+://)(.+)$|', $stream_uri, $tokens ) ) {
|
||||
$stream_uri = $tokens[1].$username.':'.$password.'@'.$tokens[2];
|
||||
}
|
||||
|
||||
$profile = array( # 'monitor' part of camera
|
||||
'Type' => 'Ffmpeg',
|
||||
'Width' => $matches[4],
|
||||
'Height' => $matches[5],
|
||||
'MaxFPS' => $matches[6],
|
||||
'Path' => $stream_uri,
|
||||
// local-only:
|
||||
'Profile' => $matches[1],
|
||||
'Name' => $matches[2],
|
||||
'Encoding' => $matches[3],
|
||||
|
||||
);
|
||||
$profiles[] = $profile;
|
||||
} else {
|
||||
Logger::Debug("Line did not match preg: $line");
|
||||
}
|
||||
}
|
||||
return( $profiles );
|
||||
}
|
||||
return( $profiles );
|
||||
}
|
||||
|
||||
|
||||
|
@ -137,25 +127,19 @@ xhtmlHeaders(__FILE__, translate('MonitorProbe') );
|
|||
if( !isset($_REQUEST['step']) || ($_REQUEST['step'] == "1")) {
|
||||
|
||||
$monitors = array();
|
||||
foreach ( dbFetchAll( "select Id, Name, Host from Monitors where Type = 'Remote' order by Host" ) as $monitor )
|
||||
{
|
||||
if ( preg_match( '/^(.+)@(.+)$/', $monitor['Host'], $matches ) )
|
||||
{
|
||||
foreach ( dbFetchAll( "select Id, Name, Host from Monitors where Type = 'Remote' order by Host" ) as $monitor ) {
|
||||
if ( preg_match( '/^(.+)@(.+)$/', $monitor['Host'], $matches ) ) {
|
||||
//echo "1: ".$matches[2]." = ".gethostbyname($matches[2])."<br/>";
|
||||
$monitors[gethostbyname($matches[2])] = $monitor;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
//echo "2: ".$monitor['Host']." = ".gethostbyname($monitor['Host'])."<br/>";
|
||||
$monitors[gethostbyname($monitor['Host'])] = $monitor;
|
||||
}
|
||||
}
|
||||
|
||||
$detcameras = probeCameras( '' );
|
||||
foreach ( $detcameras as $camera )
|
||||
{
|
||||
if ( preg_match( '|([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)|', $camera['monitor']['Host'], $matches ) )
|
||||
{
|
||||
foreach ( $detcameras as $camera ) {
|
||||
if ( preg_match( '|([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)|', $camera['monitor']['Host'], $matches ) ) {
|
||||
$ip = $matches[1];
|
||||
}
|
||||
$host = $ip;
|
||||
|
@ -230,10 +214,8 @@ else if($_REQUEST['step'] == "2")
|
|||
#empty($_REQUEST['password']) )
|
||||
|
||||
$probe = unserialize(base64_decode($_REQUEST['probe']));
|
||||
foreach ( $probe as $name=>$value )
|
||||
{
|
||||
if ( isset($value) )
|
||||
{
|
||||
foreach ( $probe as $name=>$value ) {
|
||||
if ( isset($value) ) {
|
||||
$monitor[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
@ -242,8 +224,7 @@ else if($_REQUEST['step'] == "2")
|
|||
//print $monitor['Host'].", ".$_REQUEST['username'].", ".$_REQUEST['password']."<br/>";
|
||||
|
||||
$detprofiles = probeProfiles( $monitor['Host'], $monitor['SOAP'], $_REQUEST['username'], $_REQUEST['password']);
|
||||
foreach ( $detprofiles as $profile )
|
||||
{
|
||||
foreach ( $detprofiles as $profile ) {
|
||||
$monitor = $camera['monitor'];
|
||||
|
||||
$sourceString = "${profile['Name']} : ${profile['Encoding']}" .
|
||||
|
|
|
@ -18,14 +18,13 @@
|
|||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
//
|
||||
|
||||
if ( !canEdit( 'System' ) )
|
||||
{
|
||||
$view = "error";
|
||||
return;
|
||||
if ( !canEdit( 'System' ) ) {
|
||||
$view = 'error';
|
||||
return;
|
||||
}
|
||||
$running = daemonCheck();
|
||||
|
||||
$states = dbFetchAll( "select * from States" );
|
||||
$states = dbFetchAll( 'SELECT * FROM States' );
|
||||
$focusWindow = true;
|
||||
|
||||
xhtmlHeaders(__FILE__, translate('RunState') );
|
||||
|
@ -36,34 +35,29 @@ xhtmlHeaders(__FILE__, translate('RunState') );
|
|||
<h2><?php echo translate('RunState') ?></h2>
|
||||
</div>
|
||||
<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
|
||||
if ( empty($_REQUEST['apply']) )
|
||||
{
|
||||
if ( empty($_REQUEST['apply']) ) {
|
||||
?>
|
||||
<input type="hidden" name="view" value="<?php echo $view ?>"/>
|
||||
<input type="hidden" name="action" value=""/>
|
||||
<input type="hidden" name="apply" value="1"/>
|
||||
<p>
|
||||
<select name="runState" onchange="checkState( this );">
|
||||
<select name="runState" onchange="checkState(this);">
|
||||
<?php
|
||||
if ( $running )
|
||||
{
|
||||
if ( $running ) {
|
||||
?>
|
||||
<option value="stop" selected="selected"><?php echo translate('Stop') ?></option>
|
||||
<option value="restart"><?php echo translate('Restart') ?></option>
|
||||
<?php
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
?>
|
||||
<option value="start" selected="selected"><?php echo translate('Start') ?></option>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<?php
|
||||
foreach ( $states as $state )
|
||||
{
|
||||
foreach ( $states as $state ) {
|
||||
?>
|
||||
<option value="<?php echo $state['Name'] ?>"><?php echo $state['Name'] ?></option>
|
||||
<?php
|
||||
|
@ -75,9 +69,7 @@ if ( empty($_REQUEST['apply']) )
|
|||
<tbody>
|
||||
<tr>
|
||||
<th scope="row"><?php echo translate('NewState') ?></th>
|
||||
<!-- PP - added oninput so that changes are detected immediately -->
|
||||
<!-- PP - retained onchange for older browsers -->
|
||||
<td><input type="text" name="newState" value="" size="16" oninput="checkState( this );" onchange="checkState(this);"/></td>
|
||||
<td><input type="text" name="newState" value="" size="16" oninput="checkState(this);" onchange="checkState(this);"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -88,9 +80,7 @@ if ( empty($_REQUEST['apply']) )
|
|||
<input type="button" value="<?php echo translate('Cancel') ?>" onclick="closeWindow()"/>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
?>
|
||||
<input type="hidden" name="view" value="none"/>
|
||||
<input type="hidden" name="action" value="state"/>
|
||||
|
|
|
@ -18,91 +18,82 @@
|
|||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
//
|
||||
|
||||
if ( !canView( 'Events' ) )
|
||||
{
|
||||
$view = "error";
|
||||
return;
|
||||
if ( !canView( 'Events' ) ) {
|
||||
$view = "error";
|
||||
return;
|
||||
}
|
||||
|
||||
require_once('includes/Event.php');
|
||||
|
||||
$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_values = array( $eid );
|
||||
|
||||
if ( $user['MonitorIds'] ) {
|
||||
$monitor_ids = explode( ',', $user['MonitorIds'] );
|
||||
$sql .= ' AND MonitorId IN (' .implode( ',', array_fill(0,count($monitor_ids),'?') ) . ')';
|
||||
$sql_values = array_merge( $sql_values, $monitor_ids );
|
||||
$monitor_ids = explode( ',', $user['MonitorIds'] );
|
||||
$sql .= ' AND MonitorId IN (' .implode( ',', array_fill(0,count($monitor_ids),'?') ) . ')';
|
||||
$sql_values = array_merge( $sql_values, $monitor_ids );
|
||||
}
|
||||
$event = dbFetchOne( $sql, NULL, $sql_values );
|
||||
|
||||
if ( isset( $_REQUEST['rate'] ) )
|
||||
$rate = validInt($_REQUEST['rate']);
|
||||
$rate = validInt($_REQUEST['rate']);
|
||||
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'] ) )
|
||||
$scale = validInt($_REQUEST['scale']);
|
||||
$scale = validInt($_REQUEST['scale']);
|
||||
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();
|
||||
$ffmpegFormats = preg_split( '/\s+/', ZM_FFMPEG_FORMATS );
|
||||
foreach ( $ffmpegFormats as $ffmpegFormat )
|
||||
{
|
||||
if ( preg_match( '/^([^*]+)(\*\*?)$/', $ffmpegFormat, $matches ) )
|
||||
{
|
||||
$videoFormats[$matches[1]] = $matches[1];
|
||||
if ( !isset($videoFormat) && $matches[2] == "*" )
|
||||
{
|
||||
$videoFormat = $matches[1];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$videoFormats[$ffmpegFormat] = $ffmpegFormat;
|
||||
foreach ( $ffmpegFormats as $ffmpegFormat ) {
|
||||
if ( preg_match( '/^([^*]+)(\*\*?)$/', $ffmpegFormat, $matches ) ) {
|
||||
$videoFormats[$matches[1]] = $matches[1];
|
||||
if ( !isset($videoFormat) && $matches[2] == '*' ) {
|
||||
$videoFormat = $matches[1];
|
||||
}
|
||||
} else {
|
||||
$videoFormats[$ffmpegFormat] = $ffmpegFormat;
|
||||
}
|
||||
}
|
||||
|
||||
$videoFiles = array();
|
||||
if ( $dir = opendir( $eventPath ) )
|
||||
{
|
||||
while ( ($file = readdir( $dir )) !== false )
|
||||
{
|
||||
$file = $eventPath.'/'.$file;
|
||||
if ( is_file( $file ) )
|
||||
{
|
||||
if ( preg_match( '/\.(?:'.join( '|', $videoFormats ).')$/', $file ) )
|
||||
{
|
||||
$videoFiles[] = $file;
|
||||
}
|
||||
}
|
||||
if ( $dir = opendir( $eventPath ) ) {
|
||||
while ( ($file = readdir( $dir )) !== false ) {
|
||||
$file = $eventPath.'/'.$file;
|
||||
if ( is_file( $file ) ) {
|
||||
if ( preg_match( '/\.(?:'.join( '|', $videoFormats ).')$/', $file ) ) {
|
||||
$videoFiles[] = $file;
|
||||
}
|
||||
}
|
||||
closedir( $dir );
|
||||
}
|
||||
closedir( $dir );
|
||||
}
|
||||
|
||||
if ( isset($_REQUEST['deleteIndex']) )
|
||||
{
|
||||
$deleteIndex = validInt($_REQUEST['deleteIndex']);
|
||||
unlink( $videoFiles[$deleteIndex] );
|
||||
unset( $videoFiles[$deleteIndex] );
|
||||
if ( isset($_REQUEST['deleteIndex']) ) {
|
||||
$deleteIndex = validInt($_REQUEST['deleteIndex']);
|
||||
unlink( $videoFiles[$deleteIndex] );
|
||||
unset( $videoFiles[$deleteIndex] );
|
||||
}
|
||||
|
||||
if ( isset($_REQUEST['downloadIndex']) )
|
||||
{
|
||||
$downloadIndex = validInt($_REQUEST['downloadIndex']);
|
||||
header( "Pragma: public" );
|
||||
header( "Expires: 0" );
|
||||
header( "Cache-Control: must-revalidate, post-check=0, pre-check=0" );
|
||||
header( "Cache-Control: private", false ); // required by certain browsers
|
||||
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-Transfer-Encoding: binary" );
|
||||
header( "Content-Type: application/force-download" );
|
||||
header( "Content-Length: ".filesize($videoFiles[$downloadIndex]) );
|
||||
readfile( $videoFiles[$downloadIndex] );
|
||||
exit;
|
||||
if ( isset($_REQUEST['downloadIndex']) ) {
|
||||
$downloadIndex = validInt($_REQUEST['downloadIndex']);
|
||||
header( "Pragma: public" );
|
||||
header( "Expires: 0" );
|
||||
header( "Cache-Control: must-revalidate, post-check=0, pre-check=0" );
|
||||
header( "Cache-Control: private", false ); // required by certain browsers
|
||||
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-Transfer-Encoding: binary" );
|
||||
header( "Content-Type: application/force-download" );
|
||||
header( "Content-Length: ".filesize($videoFiles[$downloadIndex]) );
|
||||
readfile( $videoFiles[$downloadIndex] );
|
||||
exit;
|
||||
}
|
||||
|
||||
$focusWindow = true;
|
||||
|
@ -119,19 +110,16 @@ xhtmlHeaders(__FILE__, translate('Video') );
|
|||
</div>
|
||||
<div id="content">
|
||||
<?php
|
||||
if ( isset($_REQUEST['showIndex']) )
|
||||
{
|
||||
$showIndex = validInt($_REQUEST['showIndex']);
|
||||
preg_match( '/([^\/]+)\.([^.]+)$/', $videoFiles[$showIndex], $matches );
|
||||
$name = $matches[1];
|
||||
$videoFormat = $matches[2];
|
||||
if ( isset($_REQUEST['showIndex']) ) {
|
||||
$showIndex = validInt($_REQUEST['showIndex']);
|
||||
preg_match( '/([^\/]+)\.([^.]+)$/', $videoFiles[$showIndex], $matches );
|
||||
$name = $matches[1];
|
||||
$videoFormat = $matches[2];
|
||||
?>
|
||||
<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>
|
||||
<?php
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
?>
|
||||
<form name="contentForm" id="contentForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>">
|
||||
<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 } ?>/>
|
||||
</form>
|
||||
<?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>
|
||||
<?php
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
?>
|
||||
<h2 id="videoProgress" class="hidden warnText"><span id="videoProgressText"><?php echo translate('GeneratingVideo') ?></span><span id="videoProgressTicker"></span></h2>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
<h2 id="videoFilesHeader"><?php echo translate('VideoGenFiles') ?></h2>
|
||||
<?php
|
||||
if ( count($videoFiles) == 0 )
|
||||
{
|
||||
if ( count($videoFiles) == 0 ) {
|
||||
?>
|
||||
<h3 id="videoNoFiles"><?php echo translate('VideoGenNoFiles') ?></h3>
|
||||
<?php
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
?>
|
||||
<table id="videoTable" class="major" cellspacing="0">
|
||||
<thead>
|
||||
|
@ -194,32 +176,24 @@ else
|
|||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$index = 0;
|
||||
foreach ( $videoFiles as $file )
|
||||
{
|
||||
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 ) )
|
||||
{
|
||||
$rate = (int)(100 * preg_replace( '/_/', '.', $temp_matches[1] ) );
|
||||
$rateText = isset($rates[$rate])?$rates[$rate]:($rate."x");
|
||||
}
|
||||
elseif ( preg_match( '/^F(.+)$/', $matches[2], $temp_matches ) )
|
||||
{
|
||||
$rateText = $temp_matches[1]."fps";
|
||||
}
|
||||
if ( preg_match( '/^s(.+)$/', $matches[3], $temp_matches ) )
|
||||
{
|
||||
$scale = (int)(100 * preg_replace( '/_/', '.', $temp_matches[1] ) );
|
||||
$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'];
|
||||
$index = 0;
|
||||
foreach ( $videoFiles as $file ) {
|
||||
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 ) ) {
|
||||
$rate = (int)(100 * preg_replace( '/_/', '.', $temp_matches[1] ) );
|
||||
$rateText = isset($rates[$rate])?$rates[$rate]:($rate."x");
|
||||
} elseif ( preg_match( '/^F(.+)$/', $matches[2], $temp_matches ) ) {
|
||||
$rateText = $temp_matches[1]."fps";
|
||||
}
|
||||
if ( preg_match( '/^s(.+)$/', $matches[3], $temp_matches ) ) {
|
||||
$scale = (int)(100 * preg_replace( '/_/', '.', $temp_matches[1] ) );
|
||||
$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>
|
||||
<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>
|
||||
</tr>
|
||||
<?php
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
//
|
||||
|
||||
// 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 ***
|
||||
# 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"
|
||||
# under the @ZM_CONFIG_SUBDIR@ subfolder containing your desired modifications.
|
||||
#
|
||||
# To make custom changes to the variables below, create a new configuration
|
||||
# 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
|
||||
ZM_PATH_DATA=@PKGDATADIR@
|
||||
|
@ -50,43 +49,19 @@ ZM_DB_USER=@ZM_DB_USER@
|
|||
# ZoneMinder database password
|
||||
ZM_DB_PASS=@ZM_DB_PASS@
|
||||
|
||||
# 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@
|
||||
# SSL CA certificate for ZoneMinder database
|
||||
ZM_DB_SSL_CA_CERT=@ZM_DB_SSL_CA_CERT@
|
||||
|
||||
# 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@
|
||||
# SSL client key for ZoneMinder database
|
||||
ZM_DB_SSL_CLIENT_KEY=@ZM_DB_SSL_CLIENT_KEY@
|
||||
|
||||
# Foldername under the webroot where ZoneMinder looks for optional sound files
|
||||
# to play when an alarm is detected.
|
||||
ZM_DIR_SOUNDS=@ZM_DIR_SOUNDS@
|
||||
# SSL client cert for ZoneMinder database
|
||||
ZM_DB_SSL_CLIENT_CERT=@ZM_DB_SSL_CLIENT_CERT@
|
||||
|
||||
# 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@
|
||||
# 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=
|
||||
|
||||
|
|
Loading…
Reference in New Issue